unageanu/jiji2

View on GitHub
src/jiji/model/securities/internal/oanda/ordering.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'jiji/model/securities/internal/utils/converter'

module Jiji::Model::Securities::Internal::Oanda
  module Ordering
    include Jiji::Errors
    include Jiji::Model::Trading
    include Jiji::Model::Trading::Utils
    include Jiji::Model::Securities::Internal::Utils

    def order(pair_name, sell_or_buy, units, type = :market, options = {})
      options = Converter.convert_option_to_oanda(options)
      @order_validator.validate(pair_name, sell_or_buy, units, type, options)
      response = @client.account(@account['id']).order({
        order: {
          instrument: Converter.convert_pair_name_to_instrument(pair_name),
          type:       Converter.convert_order_type_to_oanda(type),
          units:      sell_or_buy === :buy ? units : units * -1
        }.merge(options)
      }).create
      convert_response_to_order_result(response, type)
    end

    def retrieve_orders(count = 500, pair_name = nil, max_id = nil)
      param = { count: count }
      if pair_name
        param[:instrument] =
          Converter.convert_pair_name_to_instrument(pair_name)
      end
      param[:max_id] = max_id if max_id
      @client.account(@account['id'])
        .orders(param).show['orders'].reject do |item|
        %w[TRAILING_STOP_LOSS TAKE_PROFIT STOP_LOSS].include?(item['type'])
      end.map do |item|
        convert_response_to_order(item)
      end
    end

    def retrieve_order_by_id(internal_id)
      response = @client.account(@account['id'])
        .order(internal_id).show
      convert_response_to_order(response['order'])
    end

    def modify_order(internal_id, options = {})
      order = retrieve_order_by_id(internal_id)
      options = Converter.convert_option_to_oanda(options)
      options[:type] = Converter.convert_order_type_to_oanda(order.type)
      options[:instrument] = Converter.convert_pair_name_to_instrument(order.pair_name)
      options[:price] = options[:price] || order.price
      options[:units] = options[:units] || order.units
      @order_validator.validate(order.pair_name, order.sell_or_buy, options[:units] || order.units, order.type, options)
      options[:units] = order.sell_or_buy === :buy ? options[:units] : options[:units] * -1
      response = @client.account(@account['id'])
        .order(internal_id, { order: options }).update
      convert_response_to_order(response['orderCreateTransaction'])
    end

    def cancel_order(internal_id)
      order = retrieve_order_by_id(internal_id)
      response = @client.account(@account['id'])
        .order(internal_id).cancel
      order
    end

    private

    def convert_response_to_order_result(res, type)
      order_opened = res['orderFillTransaction'] ? nil : convert_response_to_order(res['orderCreateTransaction'], type)
      trade_opened = nil
      trade_reduced = nil
      trades_closed = []
      tx = res['orderFillTransaction']
      if tx
        if tx['tradeOpened']
          trade_opened = retrieve_trade_by_id(tx['tradeOpened']['tradeID'])
          trade_opened.update_price(retrieve_current_tick, account_currency)
        end
        if tx['tradeReduced']
          trade_reduced = convert_response_to_reduced_position(tx['tradeReduced'], tx['time'])
        end
        if tx['tradesClosed']
          trades_closed = tx['tradesClosed'].map do |r|
            convert_response_to_closed_position(r, tx['time'])
          end
        end
      end
      OrderResult.new(order_opened, trade_opened, trade_reduced, trades_closed)
    end

    def convert_response_to_order(res, type = nil)
      pair_name = res['instrument'] ? Converter.convert_instrument_to_pair_name(res['instrument']) : nil
      t = type || Converter.convert_order_type_from_oanda(res['type'])
      order = Order.new(pair_name, res['id'].to_s,
        PricingUtils.detect_sell_or_buy(res['units']), t, Time.parse(res['time'] || res['createTime']))
      copy_options(order, res, t)
      order
    end

    def convert_trade_opened_to_position(trade_opened, type = nil)
      pair_name = Converter.convert_instrument_to_pair_name(trade_opened['instrument'])
      t = type || Converter.convert_order_type_from_oanda(res['type'])
      order = Order.new(pair_name, res['id'].to_s,
        PricingUtils.detect_sell_or_buy(res['units']), t, Time.parse(res['time']))
      copy_options(order, res, t)
      order
    end

    def convert_response_to_reduced_position(detail, time)
      # trade_reducedからは損益は取得できない。ローカルで計算した近似値を使う
      ReducedPosition.new(detail['tradeID'],
        detail['units'].to_i.abs, BigDecimal(detail['price'], 10), Time.parse(time), nil)
    end

    def convert_response_to_closed_position(detail, time)
      # trade_closedからは損益は取得できない。ローカルで計算した近似値を使う
      ClosedPosition.new(detail['tradeID'],
        detail['units'].to_i.abs, BigDecimal(detail['price'], 10), Time.parse(time), nil)
    end

    def copy_options(order, detail, type)
      order.units = detail['units'].to_i.abs
      %w[
        timeInForce positionFill triggerCondition
        clientExtensions takeProfitOnFill stopLossOnFill
        trailingStopLossOnFill tradeClientExtensions gtdTime
        priceBound price
      ].each do |key|
        order.send("#{key.underscore.downcase}=",
          Converter.convert_option_value_from_oanda(key, detail[key]))
      end
    end
  end
end