シストレどうですか

  Algorithmic Trading for Dummies

OANDA API 解説編 第11回 オーダー その2 利確や損切の同時注文

今日11月26日はアメリカではThanksgiving Dayで祭日です。ほとんどのお店はお休みなので街中が閑散としています。
その日は親戚や家族で集まって食事をするのがこちらの習慣ですが、今年は大人数で集まってパーティなどしないように自粛が叫ばれているので、みんなどうしているのでしょうか?
ちなみに定番は七面鳥のローストですが、我が家ではみんなそのパサパサ感が苦手なので今晩は七面鳥のひき肉でハンバーグ(つくね?)です。

さて、前回の続きで注文の方法について学んでみたいと思います。
OANDA REST-v20の開発ガイド(英語版)はこちら




新規注文時に利確や損切を同時に注文する


前回はややこしくなるので省略しましたが成行注文時に利確と損切を同時に注文するパターンについて考えてみたいと思います。

jantzen.hatenablog.com


引数の説明


takeProfitOnFill(利確)とstopLossOnFill(損切)の部分のところのみ表示

名前 必須 説明
takeProfitOnFill price 数値 Y TPレート Pipsで指定することは可能? そうでなければ変換が必要
timeInForce string Y GTC 初期値:GTC GTD GFD FOK IOC
gtdTime 時刻 2020-12-01T13:00:00Z (UTCタイム) timeInForceがGTDの時、有効期限を指定する。 RFC 3339 かUNIXフォーマット yyyy-mm-ddThh:mm:ssZ ローカルタイムで指定できるか?
extensions 省略
stopLossOnFill price 数値 Y SLレート Pipsで指定することは可能? そうでなければ変換が必要
distance 数値 例:USD/JPYの時、1であれば±1円で損切プライスを設定
timeInForce string Y GTC 初期値:GTC GTD GFD FOK IOC
gtdTime 時刻 2020-12-01T13:00:00Z (UTCタイム) timeInForceがGTDの時、有効期限を指定する。 RFC 3339 かUNIXフォーマット yyyy-mm-ddThh:mm:ssZ ローカルタイムで指定できるか?
extensions 省略



プログラムの例は省略しますが、data引数に以下のような値をセットします。 (ドル円成行買いで¥105.55で利確、¥105.25で損切)

### data
data = {
        "order": {
                "units": "1",
                "instrument": "USD_JPY",
                "timeInForce": "FOK",
                "type": "MARKET",
                "positionFill": "DEFAULT",
                #TP
            👉 "takeProfitOnFill" : {
                                "price": "105.55",
                                "timeInForce": "GTC"   #省略可
                                 },
                #SL
             👉 "stopLossOnFill" : {
                                "price": "105.25",
                                "timeInForce": "GTC"   #省略可
                                },
                }
        }

もちろん利確のみ損切のみの場合はどちらか片方だけ指定すればよいという事になります。
ここでひとつ気づかれた方もいらっしゃるかもしれませんが、成行注文の時にはあらかじめ正確な約定価格がわかっているわけではありません。その前に損切や利確をプライスで指定するのはリスクがありますね。スリップしたりして現在の価格よりも大きくずれて約定してしまう可能性がある以上、せっかくの設定が意味をなさなくなります。


Pipsで利確や損切のプライスを決める

ではプライスではなく、約定価格からの距離("distance")で価格設定を行う場合を考えてみます。
携帯アプリなどでは損切や利確を入力する際にPipsで入力することが可能なのですが、APIではなぜか損切サイド("stopLossOnFill")のみ差額で入力できる引数("distance")があるようです。

f:id:jantzen:20201124120727p:plain:w200:h300

なぜ損切だけなのか!とも思いますが、マーケットの動きが激しい時にでもなるべく安全に取引してほしいという親心でしょうか? 便利なラッパーであるoandapyv20でも"distance"は使えなかったようですが、昨年から損切用の"distance"は使えるようになったようです。
という訳ですが、せっかくあるものですし、成行注文の時にとりあえず損切を設定しておいてあとで変更することも可能なので、まずはこれが使えるかどうか試してみます。

distance?

例としてdataの引数部分に以下のような値を設定します。"price"の代わりに”distance”を指定しその値が"1"ですので、ショートならば約定価格+1円で損切が設定されるはずです。

### data
data = {
        "order": {
                "units": "-1",
                "instrument": "USD_JPY",
                "timeInForce": "FOK",
                "type": "MARKET",
                "positionFill": "DEFAULT",
            
               #SL
               "stopLossOnFill" : {
                                    "distance": "1"  👈
                                     },
                }
        }


結果は左図の通り、ショートで約定価格104.417円 損切価格105.417円になりました。










takeProfitOnFillでdistanceを試してみる


OANDA APIのマニュアル上には記載されていませんが、実は"takeProfitOnFill"でも"distance"が使えるようです。

   "takeProfitOnFill": {
                        "distance": "1"  👈
                       },

"stopLossOnFill"に代わって"takeProfitOnFill"で試してみたところ・・・

f:id:jantzen:20201126051243j:plain:w300:h250:left


👈 ロングの約定価格¥104.421に対して1円上乗せの¥105.421でTake Profitの価格が設定されました!?







しかしながらこれには注意が必要です。oandapyv20の管理者がOANDAに問い合わせたところ、このパラメータは公式にはサポートしていないそうです。(2019年7月時点)
この件の詳細に関してはgithubにあるoandapyv20のサイトを参照して下さい。

github.com

I raised a question at OANDA regarding this: docs wrong or is it not implemented (yet)?

2019-07-17: update from OANDA: they are looking into this ...

2019-07-29: update from OANDA: this parameter is not public yet and may contain bugs. So it is not recommended to be used by any script or third-party application.

「このパラメータはいまだ公開されておらずバグがあるかもしれません。従いましていかなるスクリプトサードパーティアプリでの使用は推奨しません。」とあります。 管理者がこの問い合わせを行った時点で、自分でこのパラメータを試したところ利確でのdistanceはエラーになったとも書いてありましたが、現在はオーダーが通るようですので、ひょとしたら?使えるようになっている可能性もあります。

いずれにせよ"takeProfitOnFill"で"distance"をどうしても使ってみたいという方は事前にOANDA社に確認して下さい。
従いまして、この引数を使用する場合は、あくまでも自己責任の範囲内でお願い致します。


Pipsからプライス("price")を計算


今度は指定したPipsから価格を計算する方法を考えてみます。

例として、USD/JPY 指値買 ¥104.45、利確 50pips、損切 30pipsの場合

#データ情報の変数の設定

Order_units = 1 
Entry_price = 104.45
Pip_location = -2
TP_pips = 50 #pips
SL_pips = 30 #pips

if Order_units > 0: 
        TP_price = Entry_price + TP_pips * (10**Pip_location)         
        SL_price = Entry_price - SL_pips * (10**Pip_location)         
else:
        TP_price = Entry_price - TP_pips * (10**Pip_location)         
        SL_price = Entry_price + SL_pips * (10**Pip_location)         

data = {
        "order": {
                "units": Order_units,
                "price": Entry_price,
                "instrument": "USD_JPY",
                "timeInForce": "GTC",
                "type": "LIMIT",
                "positionFill": "DEFAULT",
                #TP
                "takeProfitOnFill" : {
                                "price": TP_price,
                                "timeInForce": "GTC"  
                                 },
                #SL
                "stopLossOnFill" : {
                                "price": SL_price,
                                "timeInForce": "GTC" 
                                },
                }
        }    

このような注文時のそれぞれの価格は、
TP = 104.45 + (50 * 0.01) = 104.95
SL = 104.45 - (30 * 0.01) = 104.15
になりますが、注文画面をみると期待通りの値が表示されています。

f:id:jantzen:20201125085842j:plain:w300:h200:left










Pipsの換算は、固定通貨ペアで取引しているようなシストレでは、上の例のような”Pip_location”をつかわずに固定値で0.01のように指定しても構いませんが、 いろいろな通貨ペアに対応できるようにする場合は、GET /v3/account/{account name}/instrumentsを使用して戻ってきた値"pipLocation"からからそれぞれの通貨ペア毎の数値を元に、10-nの形で算出するほうがかっこいいですね。

jantzen.hatenablog.com


注文が約定した後に利確や損切のプライスを設定する


成行注文時にPipsから利確のプライスをセットする


最後に成行注文でも約定価格を基準に損切や利確のプライスを設定できないかを考えてみます。 残念ながら前述した通り、公式には新規注文時に差額("distance")で指定する引数は損切用のものしかありませんので、利確でこれを実装したい場合は新規注文後すぐにそのトレードを変更するという方法しかないようです。
方法としては ”POST /v3/accounts/{accountID}/orders” で約定価格を取得後すぐに ”PUT /v3/accounts/{accountID}/trades/{tradeSpecifier}/orders” で利確のプライスをすかさず変更します。当然損切もこの時点で設定することが可能です。

プログラミングの一例です。先程の例と同様にTP=50pips,SL=30pipsで、新規発注後利確のみ設定すると想定します。
(細かいエラー処理は入っていませが、新規注文はOKだがトレード変更でエラーがでた場合どのような対応をするかという考慮も必要です。)

# 必要なモジュールの読み込み
import requests
import json
  
# 口座情報の設定
API_Token = '********************************-********************************'
API_AccountID = '999-999-99999999-999'
  
# URLの設定 (デモ口座用非ストリーミングURL)
API_URL =  "https://api-fxpractice.oanda.com"
    
# 注文用URLの変数の設定 その1
url = API_URL + "/v3/accounts/%s/orders" % str(API_AccountID)
  
# ヘッダー情報の変数の設定
headers = {
               "Content-Type" : "application/json", 
               "Authorization" : "Bearer " + API_Token
        }  
  
#データ情報の変数の設定
  
Order_units = 1
Pip_location = -2
TP_pips = 50 #pips
TP_distance = TP_pips * (10**Pip_location)
SL_pips = 30 #pips
SL_distance = SL_pips * (10**Pip_location)         
  
data_Market = {
                "order": {
                        "units": Order_units,
                        "instrument": "USD_JPY",
                        "timeInForce": "FOK",
                        "type": "MARKET",
                        "positionFill": "DEFAULT",
                        #SL
                        "stopLossOnFill" : {
                                "distance": str(SL_distance),
                                "timeInForce": "GTC" 
                                },
                        }
                }  
  
data = json.dumps(data_Market)
      
try:
        # サーバーへの要求
        Response_Body = requests.post(url, headers=headers, data=data)
        # エラー発生時に例外処理へ飛ばす
        Response_Body.raise_for_status()
               
        #約定されたトレード情報の取得
        #トレードID
        Trade_no = str(Response_Body.json()['orderFillTransaction']['tradeOpened']['tradeID'])
        #売買判定
        Trade_units = float(Response_Body.json()['orderFillTransaction']['tradeOpened']['units'])
        #約定価格
        Trade_price = float(Response_Body.json()['orderFillTransaction']['tradeOpened']['price'])
  
        #TPプライスの計算  
        if Trade_units > 0: 
                TP_price = Trade_price + TP_distance         
        else:
                TP_price = Trade_price - TP_distance         
              
        # トレード変更用URL変数の設定
        url = API_URL + "/v3/accounts/%s/trades/%s/orders" % (str(API_AccountID), Trade_no)
          
        # データ情報の変数の設定
        data_Modify = {
                    #TP
               👉   "takeProfit" : {
                                "price": str(TP_price),
                                "timeInForce": "GTC"  
                                },
                        }
  
        data = json.dumps(data_Modify)
        Response_Body = requests.put(url, headers=headers, data=data)
        # エラー発生時に例外処理へ飛ばす
        Response_Body.raise_for_status()
          
        #結果の表示
        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)
        


f:id:jantzen:20201127071518p:plain:w300:h350:left




👈結果として、損切だけでなく利確についても約定価格からの任意な価格で設定できました。








まとめ

注文はいろいろ奥が深いですね。調べていくほどいろいろな注意点が浮かびあがってきます。
成行注文で利確と損切を同時発注すれば後は決済まではただ待つのみみたいな感じで儲かるシステムは作れないようです。また、float型の数値を扱う際も誤差等で思わないところでサーバーからエラーが返ってきたりします。(Decimalは面倒くさいからできれば使いたくないのですが・・・)
バグや仕様ミスによる誤発注で大事な資金をなくさないように慎重に設計しないといけない部分です。

Happy Thanksgiving!