シストレどうですか

  Algorithmic Trading for Dummies

OANDA API 解説編 第12回 オーダー その3 指値・逆指値注文

アメリカではThanksgivingが終わると次はクリスマスの準備です。我が家でもThanksgivingの後の週末にクリスマスツリーの飾りつけを終えました。 壊れた電飾があったので新しいものを買おうと思って探したのですが、今年はみな外出を控えているせいでする事がないからか、クリスマス関係の装飾が早くから品切れになってしまい買う事ができませんでした。 今年はマンダロリアンにハマったのでベイビーヨーダ(ザ・チャイルド)のオーナメントでも安売りしていたら買おうかとも思っていたのですがそれどころではないようです。 それとクリスマスツリーも今年は本物の木を買っている人が増えているそうです。 やはり家の中で閉じこもっている時間が長いので、せめて家の中で快適に過ごせるようにと思っている人が多いのでしょうね。
日本ではもうこたつの時期でしょうか?

さて本題ですが、またまた注文の方法について学んでみたいと思います。 今回は指値や逆指値についてですが、API指値の注文について解説したものはいろいろありますが、その注文が約定した後のトレード情報についての取得方法について探してもほとんど見当たりませんでしたので、自分で考えてみました。

OANDA REST-v20の開発ガイド(英語版)はこちら




指値・逆指値注文

指値や逆指値注文時の値の指定について考えてみたいと思います。
(これらに関連するタイプであるLIMIT/STOP/MARKET_IF_TOUCHED)

引数の説明

ヘッダーの部分は省略

data部分

名前 必須 説明
type string Y LIMIT, STOP,MARKET_IF_TOUCHED LIMIT(指値)、STOP(逆指値)、MARKET_IF_TOUCHED(MIT注文、指値に近い成行注文)指定なしはMARKET(成行)
instrument string Y 銘柄名(通貨ペア)  例:USD_JPY
units number Y 注文数 買いは正数、売りは負数(例:-10)
price number Y 指値・逆指値価格
timeInForce string Y GTC, GTD, GFD  注文の有効期限
初期値:GTC=キャンセルするまで有効 GTD=gtdTimeの指定する時間まで有効 GFD=NY時間のPM5:00mまで
gtdTime DateTime 2020-12-01T13:00:00Z (UTCタイム)  timeInForceがGTDの時、有効期限を指定する。 RFC 3339 かUNIXフォーマット yyyy-mm-ddThh:mm:ssZ
priceBound number   約定レートの下限(または上限)
LIMITの時は指定できない。?
positionFill string Y DEFAULT  初期値=DEFAULT(REDUCE_FIRST),
OPEN_ONLY, REDUCE_FIRST, REDUCE_ONLY
triggerCondition string Y DEFAULT  初期値=DEFAULT, INVERSE, BID, ASK, MID
clientExtensions string   ID,タグ、コメント設定領域
MT4を使っている場合は使用禁止
takeProfitOnFill   テイクプロフィット同時発注用領域
stopLossOnFill   ストップロス同時発注用領域
guaranteedStopLossOnFill   Guranteed Stop Loss用エリア
trailingStopLossOnFill   トレーリングストップロス同時発注用領域
tradeClientExtensionsl string   約定が成立した際のTrade用のID,タグ、コメント設定領域
MT4を使っている場合は使用禁止



戻り値

それでは早速指値注文した場合の戻り値について確認してみます。 前回のプログラミング例のような形で注文してみます。

# 必要なモジュールの読み込み
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
        }  
  
#データ情報の変数の設定
Order_units = 1 
Entry_price = 104.50
  
data_LIMIT = {
        "order": {
                "units": Order_units,
                "price": str(Entry_price),
                "instrument": "USD_JPY",
                "type": "LIMIT",
                }
        }    


# data引数に渡す値をJSONへ変換
data = json.dumps(data_LIMIT)
  
# サーバーへの要求
Response_Body = requests.post(url, headers=headers, data=data)
      
# 受け取り結果の表示と編集
print(json.dumps(Response_Body.json(), indent=2))
  


正常時

無事発注できた場合は、以下のようなデータを受け取ります。

{
  "orderCreateTransaction": {
    "id": "1098",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1098",
    "requestID": "24754361486100868",
    "time": "2020-12-02T18:06:36.315366116Z",
    "type": "LIMIT_ORDER",
    "instrument": "USD_JPY",
    "units": "1",
    "price": "104.500",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "partialFill": "DEFAULT",
    "positionFill": "DEFAULT",
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "1098"
  ],
  "lastTransactionID": "1098"
}

type="MARKET"(成行)の時は、 "orderCreateTransaction"と "orderFillTransaction"(または "orderCancelTransaction")というキーが返りましたが、どうやら 指値時は"orderCreateTransaction"のみが返ってくるようです。


エラー発生時

次にエラーがあった場合を試してみましょう。 下の例では、指値のプライスが浮動小数の扱いで変になってしまいました。の場合ですが、

{
  "orderRejectTransaction": {
    "id": "1117",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1117",
    "requestID": "42768785490698312",
    "time": "2020-12-02T19:47:54.243623763Z",
    "type": "LIMIT_ORDER_REJECT",
    "instrument": "USD_JPY",
    "units": "-1",
    "price": "104.54999999999999", 👈
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "partialFill": "DEFAULT",
    "positionFill": "OPEN_ONLY",
    "reason": "CLIENT_ORDER",
    "rejectReason": "PRICE_PRECISION_EXCEEDED" 👈
  },
  "relatedTransactionIDs": [
    "1117"
  ],
  "lastTransactionID": "1117",
  "errorCode": "PRICE_PRECISION_EXCEEDED"
}

"orderCreateTransaction"のキーの代わりに"orderRejectTransaction"というキーが戻されたのが確認できました。


MARKET_IF_TOUCHED オーダー

念のためtypeが"MAKET_IF_TOUCHED"オーダーの場合も試してみましたが、特に変わりがないようです。
デモ口座の場合は実際に相対の取引相手がいるわけではありませんので、”MARKET_IF_TOUCHED"のほうが検証用には使いやすいかもしれませんね。 (携帯アプリからリーブオーダー(Entry Order)をした場合、APIでその取引情報を検索するとtypeが"MARKET_IF_TOUCHED_ORDER"だったりします。)

{
  "orderCreateTransaction": {
    "id": "1139",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1139",
    "requestID": "78799781707480508",
    "time": "2020-12-08T21:26:44.070357629Z",
    "type": "MARKET_IF_TOUCHED_ORDER",
    "instrument": "USD_JPY",
    "units": "1",
    "price": "104.180",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "partialFill": "DEFAULT",
    "positionFill": "OPEN_ONLY",
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "1139"
  ],
  "lastTransactionID": "1139"
}



指値注文の例

戻り値が確認できたのでこれをもとに指値注文時の例を作成してみました。


プログラムサンプル

以下の例ですが、指値でオーダー後、約定された事が確認できたらトレード内容の変更や決済ができるようにそのトレードIDを取り出すような流れになっています。
正常に注文が通った場合はそのオーダーIDを元に、オーダー情報を定期的に読み込んでそのステータスをチェックします。
LIMITでも逆指値の状態(またはSTOPなのに指値)でオーダーしてしまうと成行で約定してしまうので、オーダーを出してすぐにトレードIDが返ってきた場合も想定しておきます。

# 必要なモジュールの読み込み
import requests
import json
import time
  
# 口座情報の設定
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
        }  
  

#データ情報の変数の設定
Order_units = 1
Entry_price = 104.44
  
data_Limit = {
        "order": {
                "units": Order_units,
                "price": str(Entry_price),
                "instrument": "USD_JPY",
                "timeInForce": "GTC",
                "type": "LIMIT",
                "positionFill": "DEFAULT",
                }
        }    

# data引数に渡す値をJSONへ変換
data = json.dumps(data_Limit)
    
# サーバーへの要求
try:
        # サーバーへの要求
        Response_Body = requests.post(url, headers=headers, data=data)
        # エラー発生時に例外処理へ飛ばす
        print(json.dumps(Response_Body.json(), indent=2))
        Response_Body.raise_for_status()
       
        
        
        #約定までしてしまった場合
        if 'orderFillTransaction' in Response_Body.json().keys(): 👈LIMITで逆指値になったような場合
                Trade_No = Response_Body.json()['orderFillTransaction']['tradeOpened']['tradeID']
  
        #発注OK        
        elif 'orderCreateTransaction' in Response_Body.json().keys(): 👈成功したらOrder IDをもとに検索
  
                Order_No = Response_Body.json()['orderCreateTransaction']['id']
                url = API_URL + "/v3/accounts/%s/orders/%s" %(str(API_AccountID), Order_No)
    
                while True:
                        Response_Body = requests.get(url, headers=headers)
                        Response_Body.raise_for_status()
  
                        #内容確認 
                        response = Response_Body.json()
                        
                        if response["order"]["state"] == "PENDING":
                                print("待機中!!!")
                        else:
                                if response["order"]["state"] == "CANCELLED":
                                        print("取消!!!")
                                elif response["order"]["state"] == "FILLED":    👈約定したのでトレードIDを取得する
                                        print("約定!!!")
                                        Trade_No = response["order"]['tradeOpenedID']
                                else:
                                        print("その他の場合: %s" %(response["order"]["state"]))                                
                                break
                                
                        time.sleep(60)
                #Loop Return                
                
        #オーダーが通らなった場合の処理
        elif 'orderCancelTransaction' in Response_Body.json().keys():
                print("Reason for Cancel : %s" %Response_Body.json()['orderCancelTransaction']['reason'])
        elif 'orderRejectTransaction' in Response_Body.json().keys():
                print("Reason for Reject : %s" %Response_Body.json()['orderRejectTransaction']['reason'])
        else:
                print("その他(エラー?))
  
except Exception as e:
        if "Response_Body" in locals(): #vars()
                print("Pattern3(raise) : %s" %Response_Body.text)
                l = locals()
                v = vars()
        print(e)

今回はオーダー情報の監視を”time.sleep”関数を使って永久ループさせていますが、取引情報(Transaction)をストリーミングさせて監視させるという方法もあるようですので、機会があればこの方法についても試してみたいと思います。

トレードIDを取得する代わりにtradeClinetExtensionを使えば、オーダー情報を定期的に読み込んで約定するのを待つ代わりにトレード情報をtradeClientExtensionにセットした任意の番号(または文字)で検索して、そのIDが読み込めるようになるのを待つという方法も考えられますが、Extensionについてはまたの機会に触れることにします。


結果

上のサンプルを走らせると、注文に問題がなければ約定するまでは以下のようにステータスが"PENDING"の状態が続きますが、

{
  "order": {
    "id": "1132",
    "createTime": "2020-12-02T22:41:56.416174000Z",
    "type": "LIMIT",
    "instrument": "USD_JPY",
    "units": "1",
    "timeInForce": "GTC",
    "price": "104.440",
    "triggerCondition": "DEFAULT",
    "partialFill": "DEFAULT_FILL",
    "positionFill": "DEFAULT",
    "state": "PENDING"        👈
  },
  "lastTransactionID": "1132"
}
待機中!!!

ステータスが"FILLED"になればトレードIDを取得しプログラムを終了します。

{
  "order": {
    "id": "1132",
    "createTime": "2020-12-02T22:41:56.416174000Z",
    "type": "LIMIT",
    "instrument": "USD_JPY",
    "units": "1",
    "timeInForce": "GTC",
    "price": "104.440",
    "triggerCondition": "DEFAULT",
    "partialFill": "DEFAULT_FILL",
    "positionFill": "DEFAULT",
    "state": "FILLED",          👈
    "fillingTransactionID": "1133",
    "filledTime": "2020-12-02T22:45:18.577662833Z",
    "tradeOpenedID": "1133"   👈約定されたトレードID
  },
  "lastTransactionID": "1133"
}
約定!!! 

トレードIDが取得できれば、次の処理としてはその取引を変更したり決済したりできるようになります。




まとめ

今回は指値(逆指値)の場合のオーダーの取扱について考えてみました。成行注文と違ってすぐにトレード情報が返ってきませんので何らかの監視をする仕組みが必要になります。 それに伴い想定されるエラーの取扱も複雑になって来ます。
指値(逆指値)で注文する際になにか間違いがあっても注文は通ってしまい約定しようとした際にエラーが発生して注文がキャンセルされる場合もあります。 今回の例では、あくまでもいくつか自分で発生させることができるエラーについてのみの検証ですので、それ以外のエラー(または正常のステータス)も考えれますのでされに対応できるような処理の追加も必要そうです。
また、約定したトレードの情報を取得する方法もオーダーやトランザクションやポジションからといろいろなやり方で出来そうです。

注文のやり方も理解できてきましたので、そろそろbotの作成にも挑戦していきたいと思いますが、ストリーミングやらもう少し覚えることがあるのでいつのことになるやら・・・。