ã¬ã³ã¸ãã¬ã¤ã¯ææ³ã§ã®ãã¬ã¼ããã¢ã·ã¹ãããBotã®ãµã³ãã«
FXã·ã¹ãã ãã¬ã¼ããã¬ã¼ã ã¯ã¼ã¯ãJijiã ã®ãµã³ãã«ãã®3ã
ã¬ã³ã¸ãã¬ã¤ã¯ææ³ã使ã£ããã¬ã¼ããã¢ã·ã¹ãããBotãä½ã£ã¦ã¿ã¾ããã
FX Wroks ããã®ãµã¤ã ã«æ²è¼ããã¦ãããã¬ã³ã¸ãã¬ã¤ã¯ãçãã·ã³ãã«ãªé å¼µããææ³ãããã®ã¾ã¾Jijiã«ç§»æ¤ãã¦ã¿ããã®ã§ãã
åä½
以ä¸ã®ãããªåä½ããã¾ãã
- 1) Botãã¬ã¼ããç£è¦ããã¬ã³ã¸ãã¬ã¤ã¯ããã§ãã¯
- æ¡ä»¶ã¯ããµã¤ãã®å 容ã¨åçã8æéã¬ã¼ãã100pipså ã§æ¨ç§»ãããã¨ãä¸orä¸ã«æãããã¨ãã¾ããã
- å¾ ã¤æéãpipsã¯ããã©ã¡ã¼ã¿ã§èª¿æ´ã§ããããã«ãã¦ãã¾ãã
- 2) ã¬ã³ã¸ãã¬ã¤ã¯ãæ¤åºããããã¹ããã«éç¥ãéä¿¡ãã¾ã
- ããããå¤ãã®ã§ãä»åã¯éç¥ãéã£ã¦å¤æããå½¢ã«ã
- 3) éç¥ãåãã¦æçµå¤æãè¡ãããã¬ã¼ããå®è¡ã
- éç¥ã«ãããã¿ã³ãæ¼ããã¨ã§ã売orè²·ã§æè¡æ³¨æãå®è¡ã§ããããã«ãã¦ãã¾ãã
- 決æ¸ã¯ããã¬ã¼ãªã³ã°ã¹ãããã§ã
軽ãåããã¦ã¿ãææ³
軽ããã¹ããã¦ã¿ã¾ããããæã£ãããããããã«å¼ã£ãããæãã§ããã
ããã¯ãã¾ãã¾ãã
ããã¯ããã¬ã¤ã¯ã¨å¤å®ãããæç¹ã§ä¸ããçµãã£ã¦ããã»ã»ã
ããã¯ãä¸åº¦ä¸ã«ãã¬ã¤ã¯ãããã¨ãéæ¹åã«é²ãã§ãã¾ãã»ã»ã»ã
ãã¬ã¤ã¯ã®æ¡ä»¶ã調æ´ãã¦ã¿ãã移åå¹³åã§ã®ãã¬ã³ããã§ãã¯ã¨çµã¿åããããªã©ãã«ã¹ã¿ãã¤ãºãã¦ä½¿ã£ã¦ã¿ã¦ãã ããã
ã³ã¼ã
# === ã¬ã³ã¸ãã¬ã¤ã¯ã§ãã¬ã¼ããè¡ãã¨ã¼ã¸ã§ã³ã class RangeBreakAgent include Jiji::Model::Agents::Agent def self.description <<-STR ã¬ã³ã¸ãã¬ã¤ã¯ã§ãã¬ã¼ããè¡ãã¨ã¼ã¸ã§ã³ãã - æå®æé(ããã©ã«ãã¯8æé)ã®ã¬ã¼ããä¸å®ã®pipsã«åã¾ã£ã¦ããç¶æ ããã ã¬ã³ã¸ãæããã¿ã¤ãã³ã°ã§éç¥ãéä¿¡ã - éç¥ãããã¬ã¼ãå¯å¦ãå¤æããåå¼ãå®è¡ã§ãã¾ãã - 決æ¸ã¯ãã¬ã¼ãªã³ã°ã¹ãããã§è¡ãã¾ãã STR end # UIããè¨å®å¯è½ãªããããã£ã®ä¸è¦§ def self.property_infos [ Property.new('target_pair', '対象ã¨ããé貨ãã¢', 'USDJPY'), Property.new('range_period', 'ã¬ã³ã¸ãå¤å®ããæé(å)', 60 * 8), Property.new('range_pips', 'ã¬ã³ã¸ç¸å ´ã¨ã¿ãªãå¤å¹ (pips)', 100), Property.new('trailing_stop_pips', 'ãã¬ã¼ã«ã¹ãããã§æ±ºæ¸ããå¤å¹ (pips)', 30), Property.new('trade_units', 'åå¼æ°é', 1) ] end def post_create pair = broker.pairs.find { |p| p.name == @target_pair.to_sym } @checker = RangeBreakChecker.new( pair, @range_period.to_i, @range_pips.to_i) end def next_tick(tick) # ã¬ã³ã¸ãã¬ã¤ã¯ãããã©ãããã§ã㯠result = @checker.check_range_break(tick) # ãã¬ã¤ã¯ãã¦ãããéç¥ãéã send_notification(result) if result[:state] != :no end def execute_action(action) case action when 'range_break_buy' then buy when 'range_break_sell' then sell else 'ä¸æãªã¢ã¯ã·ã§ã³ã§ã' end end def state { checker: @checker.state } end def restore_state(state) @checker.restore_state(state[:checker]) if state[:checker] end private def sell broker.sell(@target_pair.to_sym, @trade_units.to_i, :market, { trailing_stop: @trailing_stop_pips.to_i }) '売注æãå®è¡ãã¾ãã' end def buy broker.buy(@target_pair.to_sym, @trade_units.to_i, :market, { trailing_stop: @trailing_stop_pips.to_i }) '買注æãå®è¡ãã¾ãã' end def send_notification(result) message = "#{@target_pair} #{result[:price]}" \ + ' ãã¬ã³ã¸ãã¬ã¤ã¯ãã¾ãããåå¼ãã¾ãã?' @notifier.push_notification(message, [create_action(result)]) logger.info "#{message} #{result[:state]} #{result[:time]}" end def create_action(result) if result[:state] == :break_high { 'label' => '買注æãå®è¡', 'action' => 'range_break_buy' } else { 'label' => '売注æãå®è¡', 'action' => 'range_break_sell' } end end end class RangeBreakChecker def initialize(pair, period, range_pips) @pair = pair @range_pips = range_pips @candles = Candles.new(period * 60) end def check_range_break(tick) tick_value = tick[@pair.name] result = check_state(tick_value, tick.timestamp) @candles.reset unless result == :no # ä¸åº¦ãã¬ã¤ã¯ããããä¸æ¦ç¶æ ããªã»ãããã¦æ¬¡ã®ãã¬ã¤ã¯ãå¾ ã¤ @candles.update(tick_value, tick.timestamp) { state: result, price: tick_value.bid, time: tick.timestamp } end def state @candles.state end def restore_state(state) @candles.restore_state(state) end private # ã¬ã³ã¸ãã¬ã¤ã¯ãã¦ãããã©ããå¤å®ãã def check_state(tick_value, time) highest = @candles.highest lowest = @candles.lowest return :no if highest.nil? || lowest.nil? return :no unless over_period?(time) diff = highest - lowest return :no if diff >= @range_pips * @pair.pip calculate_state( tick_value, highest, diff ) end def calculate_state( tick_value, highest, diff ) center = highest - diff / 2 pips = @range_pips / 2 * @pair.pip if tick_value.bid >= center + pips return :break_high elsif tick_value.bid <= center - pips return :break_low end :no end def over_period?(time) oldest_time = @candles.oldest_time return false unless oldest_time (time.to_i - oldest_time.to_i) >= @candles.period end end class Candles attr_reader :period def initialize(period) @candles = [] @period = period @next_update = nil end def update(tick_value, time) time = Candles.normalize_time(time) if @next_update.nil? || time > @next_update new_candle(tick_value, time) else @candles.last.update(tick_value, time) end end def highest high = @candles.max_by { |c| c.high } high.nil? ? nil : BigDecimal.new(high.high, 10) end def lowest low = @candles.min_by { |c| c.low } low.nil? ? nil : BigDecimal.new(low.low, 10) end def oldest_time oldest = @candles.min_by { |c| c.time } oldest.nil? ? nil : oldest.time end def reset @candles = [] @next_update = nil end def new_candle(tick_value, time) limit = time - period @candles = @candles.reject { |c| c.time < limit } @candles << Candle.new @candles.last.update(tick_value, time) @next_update = time + (60 * 5) end def state { candles: @candles.map { |c| c.to_h }, next_update: @next_update } end def restore_state(state) @candles = state[:candles].map { |s| Candle.from_h(s) } @next_update = state[:next_update] end def self.normalize_time(time) Time.at((time.to_i / (60 * 5)).floor * 60 * 5) end end class Candle attr_reader :high, :low, :time def initialize(high = nil, low = nil, time = nil) @high = high @low = low @time = time end def update(tick_value, time) price = extract_price(tick_value) @high = price if @high.nil? || @high < price @low = price if @low.nil? || @low > price @time = time if @time.nil? end def to_h { high: @high, low: @low, time: @time } end def self.from_h(hash) Candle.new(hash[:high], hash[:low], hash[:time]) end private def extract_price(tick_value) tick_value.bid end end