シストレどうですか

  Algorithmic Trading for Dummies

OANDA API 解説編 第10回 オーダー その1

今年の春から在宅勤務でいつも家にいて家の壊れている所や汚くなってしまった部分を毎日見つめているとだんだんと気になってしょうがなくなり、ついに前から考えていた家のリフォームをしたのですが、 リフォーム中に仕事用に使っていた部屋がなくなってしまい、加えて台所も使えなくなったりしたのでしばらく家族全員で寝室でご飯を食べていたりと、ブログどころではなくなってしまいました。 2か月経ってようやく落ちついてきた今日このごろです。

家の近所でもリフォームしている家が多いみたいで、やはり在宅勤務でずーっと家にいると、他の人も同じように壊れたりよごれたりしている部分が気になってつい直したくなってしまうようですね。 ペイントの匂いもようやく落ち着いた今日この頃ですが、ここアメリカではまた新型コロナウイルスが拡大中で1日あたりの新規感染者数が15万人を超えてきてしまいました。
直したところは素敵になったのですが、先行き不安のところでこんなにお金を使ってしまって大丈夫かと思いもあって、半分嬉しくも半分憂鬱な気分です・・・。


さて今回は、いままでGET命令を使ってOANDAのサーバーからいろいろなデータの取得方法についてはいろいろ学んできましたので、今度は注文の方法について学んでみたいと思います。一番簡単な成行注文から考えてみたいと思います。
OANDA REST-v20の開発ガイド(英語版)はこちら





新しい注文を作成する

新しい注文を作成する


postの場合はheadersに加えてdataという引数を使ってrequestを行います。 しかしながらdata引数の内容はとても種類が多くて理解するのに一苦労です。 ですからまず一番単純な成行注文(MARKET Order)について考えてみたいと思います。

引数の説明

名前 場所 必須 説明
Authorization header string Y 署名なしトークンID
Accept-Datetime-Format header string 時刻フォーマットの指定
初期値:RFC3339 (yyyy-mm-ddThh:mm:ss.nnnnnnnnnZ形式)
その他:UNIX (12345678.000000123形式)
Content-Type header string Y application/json data引数のフォーマットの指定
OANDA社のマニュアルには説明がありませんがこのキーがないと415(クライアントエラー)になります。
accountID path AccountID Y 自分の口座番号

data部分

名前 必須 説明
type string Y MARKET 成行注文、指値注文等の指定。指定なしはMARKET(成行)
instrument string Y 銘柄名(通貨ペア)  例:USD_JPY
units number Y 注文数 買いは正数、売りは負数(例:-10)
timeInForce string Y FOK (or IOC)  注文の有効期限 成行の場合はFOKか IOCのみ
FOK=即全額約定しない場合はキャンセル、IOC=即部分約定もしなければキャンセル
priceBound number   約定レートの下限(または上限)
positionFill string Y DEFAULT  初期値=DEFAULT(REDUCE_FIRST), OPEN_ONLY, REDUCE_FIRST, REDUCE_ONLY
clientExtensions string   ID,タグ、コメント設定領域
MT4を使っている場合は使用禁止
takeProfitOnFill   テイクプロフィット同時発注用領域
stopLossOnFill   ストップロス同時発注用領域
guaranteedStopLossOnFill   Guranteed Stop Loss用エリア
trailingStopLossOnFill   トレーリングストップロス同時発注用領域
tradeClientExtensionsl string   約定が成立した際のTrade用のID,タグ、コメント設定領域
MT4を使っている場合は使用禁止

takeProfitOnFill以下は新規オーダーと同時に利確、損切注文を同時に出せる引数ですが、ややこしくなるので今回はスキップします。

成行注文の場合のプログラミングと戻り値の例


まずはオーダーしたらどんな結果が返ってくるか試してみます。

# 必要なモジュールの読み込み
import requests
import json
  
# 口座情報の設定
API_Token = '********************************-********************************'
API_AccountID = '999-999-99999999-999'
  
# URLの設定 (デモ口座用非ストリーミングURL)
API_URL =  "https://api-fxpractice.oanda.com"
    
# 注文用URLの変数の設定
url = API_URL + "/v3/accounts/%s/orders" % str(API_AccountID)
  
# ヘッダー情報の変数の設定
headers = {
               "Content-Type" : "application/json", 
               "Authorization" : "Bearer " + API_Token
        }  
#データ情報の変数の設定
data_Market = {
        "order": {
                "units": "1",
                "instrument": "USD_JPY",
                "timeInForce": "FOK",
                "type": "MARKET",
                "positionFill": "DEFAULT",
                }
        }  

# data引数に渡す値をJSONへ変換
data = json.dumps(data_Market)

  
# サーバーへの要求
Response_Body = requests.post(url, headers=headers, data=data)
    
# 受け取り結果の表示と編集
print(json.dumps(Response_Body.json(), indent=2))

実行すると以下のようなデータを受け取ります。

約定できた場合の戻り値

{
  "orderCreateTransaction": { 👈
    "id": "896",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "896",
    "requestID": "78789617374788804",
    "time": "2020-11-10T20:17:18.588283243Z",
    "type": "MARKET_ORDER",
    "instrument": "USD_JPY",
    "units": "1",
    "timeInForce": "FOK",
    "positionFill": "REDUCE_FIRST",
    "reason": "CLIENT_ORDER"
  },
  "orderFillTransaction": { 👈
    "id": "897",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "896",
    "requestID": "78789617374788804",
    "time": "2020-11-10T20:17:18.588283243Z",
    "type": "ORDER_FILL",
    "orderID": "896",
    "instrument": "USD_JPY",
    "units": "1",
    "requestedUnits": "1",
    "price": "105.329",
    "pl": "0.0000",
    "quotePL": "0",
    "financing": "0.0000",
    "baseFinancing": "0",
    "commission": "0.0000",
    "accountBalance": "98891.7260",
    "gainQuoteHomeConversionFactor": "0.009447129338",
    "lossQuoteHomeConversionFactor": "0.009542075361",
    "guaranteedExecutionFee": "0.0000",
    "quoteGuaranteedExecutionFee": "0",
    "halfSpreadCost": "0.0001",
    "fullVWAP": "105.329",
    "reason": "MARKET_ORDER",
    "tradeOpened": {                    👈
      "price": "105.329",
      "tradeID": "897",
      "units": "1",
      "guaranteedExecutionFee": "0.0000",
      "quoteGuaranteedExecutionFee": "0",
      "halfSpreadCost": "0.0001",
      "initialMarginRequired": "0.0500"
    },
    "fullPrice": {
      "closeoutBid": "105.317",
      "closeoutAsk": "105.329",
      "timestamp": "2020-11-10T20:17:18.503003744Z",
      "bids": [
        {
          "price": "105.317",
          "liquidity": "10000000"
        }
      ],
      "asks": [
        {
          "price": "105.329",
          "liquidity": "10000000"
        }
      ]
    },
    "homeConversionFactors": {
      "gainQuoteHome": {
        "factor": "0.00944712933765"
      },
      "lossQuoteHome": {
        "factor": "0.00954207536115"
      },
      "gainBaseHome": {
        "factor": "1"
      },
      "lossBaseHome": {
        "factor": "1"
      }
    }
  },
  "relatedTransactionIDs": [ 👈
    "896",
    "897"
  ],
  "lastTransactionID": "897" 
}


とりあえあず使えそうな値についての説明。

Key 名前 説明
orderCreateTransaction id transaction ID /v3/accounts/{accountID}/transactions/...で検索可能
orderFillTransaction id transaction ID 同上
orderID オーダーID /v3/accounts/{accountID}/orders/...で検索取消変更可能
tradeOpened price 約定価格
tradeID トレードID /v3/accounts/{accountID}/trades/...でで検索変更可能
units 約定数
relatedTransactionIDs この注文に関連するすべてのtransactionID



約定できずエラーになった場合の戻り値

では続いて約定できずにエラーになった場合を検証してみます。
わざとエラーを発生させるために間違えた値を渡してみます。 例としてポジションを保有していない時に、positionFill引数に"REDUCED_ONLY"をセットしてポジションを解消させるようなオーダーを行ってみました。

{
  "orderCreateTransaction": { 
    "id": "894",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "894",
    "requestID": "24746421567000288",
    "time": "2020-11-10T20:16:11.993088073Z",
    "type": "MARKET_ORDER",
    "instrument": "USD_JPY",
    "units": "1",
    "timeInForce": "FOK",
    "positionFill": "REDUCE_ONLY",
    "reason": "CLIENT_ORDER"
  },
  "orderCancelTransaction": {   👈
    "id": "895",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "894",
    "requestID": "24746421567000288",
    "time": "2020-11-10T20:16:11.993088073Z",
    "type": "ORDER_CANCEL",
    "orderID": "894",
    "reason": "NO_POSITION_TO_REDUCE" 👈
  },
  "relatedTransactionIDs": [ 
    "894",
    "895"
  ],
  "lastTransactionID": "895" 
}

"orderFillTransaction"の代わりに "orderCancelTransaction"というキーが返されました。エラーの理由は"reason"に記載されており、"減らすポジションがない”という事になります。

これらを考慮して、上記のプログラミング例にエラー発生時の処理を加えてみます。

プログラミング例(エラー発生時の処理付き)

# 必要なモジュールの読み込み
import requests
import json
  
# 口座情報の設定
API_Token = '********************************-********************************'
API_AccountID = '999-999-99999999-999'
  
# URLの設定 (デモ口座用非ストリーミングURL)
API_URL =  "https://api-fxpractice.oanda.com"

# 注文用URLの変数の設定
url = API_URL + "/v3/accounts/%s/orders" % str(API_AccountID)
  
# ヘッダー情報の変数の設定
headers = {
               "Content-Type" : "application/json", 
               "Authorization" : "Bearer " + API_Token
        }  
# データ情報の変数の設定
data_Market = {
        "order": {
                "units": "1",
                "instrument": "USD_JPY",
                "timeInForce": "FOK",
                "type": "MARKET",
                "positionFill": "DEFAULT",
                }
        }  
  
# data引数に渡す値をJSONへ変換
data = json.dumps(data_Market)
    
# サーバーへの要求
try:
        # サーバーへの要求
        Response_Body = requests.post(url, headers=headers, data=data)
        # エラー発生時に例外処理へ飛ばす
        Response_Body.raise_for_status()
        

       
        #オーダーが通らなった場合の処理
        if 'orderCancelTransaction' in Response_Body.json().keys():  👈
                print("Reason for Cancel : %s" %Response_Body.json()['orderCancelTransaction']['reason'])
               
        else:
                pass
                #オーダーが通った場合の処理
          
        #結果の表示
        print(json.dumps(Response_Body.json(), indent=2))
  
#例外処理
except Exception as e:
        if "Response_Body" in locals(): # or vars()
                print("Status Error from Server(raise) : %s" %Response_Body.text)
        print("Error(e) : %s" %e)
        


利確と損切を同時注文した場合の戻り値


次に細かい説明は省略しましたが、成行注文時に同時決済注文を加えた場合の戻り値について調べてみました。

{
  "orderCreateTransaction": { 
    "type": "MARKET_ORDER",   
    "instrument": "USD_JPY",  
    "units": "1",
    "timeInForce": "FOK",     
    "positionFill": "DEFAULT",
    "takeProfitOnFill": {     
      "price": "105.550",
      "timeInForce": "GTC"
    },
    "stopLossOnFill": {
      "price": "105.250",
      "timeInForce": "GTC"
    },
    "reason": "CLIENT_ORDER",
    "id": "843",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "843",
    "requestID": "24726203362278571",
    "time": "2020-09-16T01:16:16.970797067Z"
  },
  "orderFillTransaction": {
    "type": "ORDER_FILL",
    "orderID": "843",
    "instrument": "USD_JPY",
    "units": "1",
    "requestedUnits": "1",
    "price": "105.339",
    "pl": "0.0000",
    "financing": "0.0000",
    "commission": "0.0000",
    "accountBalance": "98891.7375",
    "gainQuoteHomeConversionFactor": "0.009493160178",
    "lossQuoteHomeConversionFactor": "0.009494422027",
    "guaranteedExecutionFee": "0.0000",
    "halfSpreadCost": "0.0001",
    "fullVWAP": "105.339",
    "reason": "MARKET_ORDER",
    "tradeOpened": {
      "price": "105.339",
      "tradeID": "844",
      "units": "1",
      "guaranteedExecutionFee": "0.0000",
      "halfSpreadCost": "0.0001",
      "initialMarginRequired": "0.0500"
    },
    "fullPrice": {
      "closeoutBid": "105.325",
      "closeoutAsk": "105.339",
      "timestamp": "2020-09-16T01:16:10.864683024Z",
      "bids": [
        {
          "price": "105.325",
          "liquidity": "10000000"
        }
      ],
      "asks": [
        {
          "price": "105.339",
          "liquidity": "10000000"
        }
      ]
    },
    "id": "844",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "843",
    "requestID": "24726203362278571",
    "time": "2020-09-16T01:16:16.970797067Z"
  },
  "relatedTransactionIDs": [ 👈
    "843",
    "844",
    "845",
    "846"
  ],
  "lastTransactionID": "846"
}

この場合、利確損切の分の明細情報は戻ってこないようです。relatedTransactionIDsに取引番号が返されるだけです。もしこれらの内容を知りたい場合はこのIDをもとに検索を行う必要がありますね。

利確と損切を同時注文してエラーが返ってきた場合の戻り値


最後に同時決済注文がうまくいかなかった場合の戻り値を確認してみます。

{
  "orderCreateTransaction": {  
    "type":"MARKET_ORDER",
    "instrument":"USD_JPY",
    "units":"1",
    "timeInForce":"FOK",
    "positionFill":"DEFAULT",
    "takeProfitOnFill": {
      "price":"105.550",
      "timeInForce":"GTC"
    },
    "stopLossOnFill": {
      "price":"105.250",
      "timeInForce":"GTC"
    },
    "reason":"CLIENT_ORDER",
    "id":"839",
    "accountID":"999-999-99999999-999"
    "userID":99999999,
    "batchID":"839",
    "requestID":"60754998307168278",
    "time":"2020-09-16T01:08:01.621613314Z"
  },
  "orderCancelTransaction": {
    "type":"ORDER_CANCEL",
    "orderID":"839",
    "reason":"FIFO_VIOLATION_SAFEGUARD_VIOLATION",
    "id":"840",
    "accountID":"999-999-99999999-999",
    "userID":99999999,
    "batchID":"839",
    "requestID":"60754998307168278",
    "time":"2020-09-16T01:08:01.621613314Z"
  },
  "relatedTransactionIDs": [
    "839",
    "840"
  ],
  "lastTransactionID":"840"
}




まとめ

注文の方法ですが、引数が多くかつ発注のタイプによって使う引数や渡す値が変わってきますので、理解するのがとても大変です。
成行注文の場合は、原則すぐに結果が返ってきますので戻ってきた値を扱いやすく、更に新規注文と同時に決済の分も発注しておけば、あとは決済するまではやることがなくなるのでプログラミングもシンプルになります。 ところが指値注文になると発注した取引が約定したかどうかはしばらくしないとわかりませんのでそれを監視する仕組みも必要になってきます。