シストレどうですか

  Algorithmic Trading for Dummies

OANDA API 解説編 第13回 決済注文 その1

前回まででオーダーについて簡単に学びましたので今度はそのポジションを決済する方法を考えてみたいと思います。
OANDA REST-v20の開発ガイド(英語版)はこちら

決済の方法いくつか

保有しているポジションを閉じる場合でもその方法はいくつかあるのでそれについて調べてみました。

反対取引をする

まずは今あるトレードに反対のポジションを持つの新規のオーダーを出して決済させる方法です。

jantzen.hatenablog.com

下の例では、ロングポジションがあるところに新規でショートの成行注文をしました。
以下のデータが戻り無事オーダーが通りポジションが相殺されなくなりました。

{
  "orderCreateTransaction": {
    "id": "1185",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1185",
    "requestID": "60788282476870863",
    "time": "2020-12-16T21:27:26.320630214Z",
    "type": "MARKET_ORDER",
    "instrument": "USD_JPY",
    "units": "1",
    "timeInForce": "FOK",
    "positionFill": "DEFAULT",
    "reason": "CLIENT_ORDER"
  },
  "orderFillTransaction": {
    "id": "1186",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1185",
    "requestID": "60788282476870863",
    "time": "2020-12-16T21:27:26.320630214Z",
    "type": "ORDER_FILL",
    "orderID": "1185",
    "instrument": "USD_JPY",
    "units": "1",
    "requestedUnits": "1",
    "price": "103.490",
    "pl": "-0.0002",
    "quotePL": "-0.016",
    "financing": "0.0000",
    "baseFinancing": "0.00000000000000",
    "commission": "0.0000",
    "accountBalance": "98891.7161",
    "gainQuoteHomeConversionFactor": "0.009615198836",
    "lossQuoteHomeConversionFactor": "0.009711834",
    "guaranteedExecutionFee": "0.0000",
    "quoteGuaranteedExecutionFee": "0",
    "halfSpreadCost": "0.0001",
    "fullVWAP": "103.490",
    "reason": "MARKET_ORDER",
    "tradesClosed": [        👈
      {
        "tradeID": "1184",
        "units": "1",
        "realizedPL": "-0.0002",
        "financing": "0.0000",
        "baseFinancing": "0.00000000000000",
        "price": "103.490",
        "guaranteedExecutionFee": "0.0000",
        "quoteGuaranteedExecutionFee": "0",
        "halfSpreadCost": "0.0001"
      }
    ],
   以下省略

戻り値に"tradesClosed"キーが含まれていますのでポジションが相殺されているのは確認できます。
ただすべて相殺されたのかはわかりにくいですね。

利確・損切注文を使って決済する

新規のオーダーに利確・損切注文を追加しておき、そのプライスになったら決済させる方法が一番簡単だと思われますが、注文を追加する方法もいくつかあります。

新規オーダー時に同時発注

以前にも解説しましたので、詳しくは書きませんが、PUT orderの際にtakeProfit/StopLoss/TrailingStopのエリアを使ってあらかじめプライスをセットする方法。

jantzen.hatenablog.com

data引数のうち、takeProfitOnFill(利確)とstopLossOnFill, traillingStopOnFill(損切)の部分のところのみ表示

名前 必須 説明
takeProfitOnFill price number Y 利確プライス
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。
stopLossOnFill price number Y 損切プライス
distance number 例:USD/JPYの時、1であれば±1円で損切プライスを設定
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定する。 yyyy-mm-ddThh:mm:ssZ
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。
trailingStopOnFill distance number Y 例:USD/JPYの時、1であれば±1円で損切プライスを設定
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定する。 yyyy-mm-ddThh:mm:ssZ
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。

成行注文時では注文時に約定価格がわかりませんので、ディスタンスで利確や損切のプライス設定する場合は良いですが、プライスで指定する場合は要注意です。

TAKE_PROFIT, STOP_LOSS、TRAILING_STOP_LOSSオーダーを追加

新規の指値や成行注文と同様に、すでにあるトレードIDをもとに利確・損切注文を追加で注文できます。

POST /v3/accounts/{accountID}/orders

data部分

名前 必須 説明
type string Y TAKE_PROFIT, STOP_LOSS, TRAILING_STOP_LOSS
tradeID string Y トレードID  利確損切をセットしたい元のtradeID
またはそのclietnTradeIDのどちらかを指定
clientTradeID string 拡張用トレードID
price number Y プライス どちらかに入力
typeが"TAKE_PROFIT"の時、price (distanceも使えないことはない)
"STOP_LOSS"の時、priceかdistanceどちらか
"TRAILING_STOP"の時、distanceのみ
distance number ディスタンス
timeInForce string Y GTC 注文の有効期限 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間   timeInForce="GTD"の時のみ指定。
triggerCondition string Y DEFAULT  初期値=DEFAULT(BID or ASK), INVERSE, BID, ASK, MID
clientExtensions string   拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。

data引数に以下のようにセットして実行すると

data_TP = {
        "order": {
                "type": "TAKE_PROFIT",
                "tradeID": "1157",
                "price": "104.65",
                }
        }    

戻り値の例としては以下のような値を取得できます。

{
  "orderCreateTransaction": {
    "id": "1158",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1158",
    "requestID": "24757037715201085",
    "time": "2020-12-10T03:20:58.753438370Z",
    "type": "TAKE_PROFIT_ORDER",
    "tradeID": "1157",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "price": "104.650",
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "1158"
  ],
  "lastTransactionID": "1158"
}

次に、Take Profit Orderがすでに元のトレードに設定されているにもかかわず、追加で発注した場合ですが、

{
  "orderRejectTransaction": {
    "id": "1159",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1159",
    "requestID": "78800234582098036",
    "time": "2020-12-10T03:26:17.678158357Z",
    "type": "TAKE_PROFIT_ORDER_REJECT",
    "rejectReason": "TAKE_PROFIT_ORDER_ALREADY_EXISTS",
    "tradeID": "1157",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "price": "104.6",
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "1159"
  ],
  "lastTransactionID": "1159",
  "errorMessage": "A Take Profit Order for the specified Trade already exists",
  "errorCode": "TAKE_PROFIT_ORDER_ALREADY_EXISTS"
}

"orderRejectTransaction"が戻ってきてしまい、上書きや変更ではなくてエラーになってしますので注意が必要です。
→ "errorMessage": "A Take Profit Order for the specified Trade already exists" (既に存在している。) この事から、追加用としてのみ使用可能のようです。

ちなみに存在しない番号を与えた場合は

  "rejectReason": "TRADE_DOESNT_EXIST",
  "errorMessage": "The Trade specified does not exist",
  "errorCode": "TRADE_DOESNT_EXIST"

"番号が存在しません。"というエラーが返ってくるのが確認できました。

特定のトレードを変更

トレードIDをもとにトレードを変更する方法

PUT /v3/accounts/{accountID}/trades/{tradeSpecifier}/orders

約定が確定した後にそのトレードIDをもとにトレード内容(利確・損切情報)の追加・変更ができます。

URL path引数

名前 必須 説明
tradeSpecifier string Y tradeIDまたはclientTradeID 自分でつけたclientTradeIDの時は前に@をつける

data引数

名前 必須 説明
takeProfit price number Y 利確プライス
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。
stopLoss price number Y 損切プライス
distance number ディスタンス 例:USD/JPYの時、1であれば±1円で損切プライスを設定
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定する。 yyyy-mm-ddThh:mm:ssZ
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。
trailingStop distance number Y ディスタンス 例:USD/JPYの時、1であれば±1円で損切プライスを設定
timeInForce string Y GTC 初期値:GTC, GTD, GFD
gtdTime DateTime UTC時間 timeInForceがGTDの時、有効期限を指定する。 yyyy-mm-ddThh:mm:ssZ
clientExtensions 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。

data引数に以下の値をセットして実行すると

data_ModifyTrade = {
        "takeProfit": {
                "price": "105.00"
                 }
        }

以下のような値が戻ります。

{
  "takeProfitOrderTransaction": {
    "id": "1173",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1173",
    "requestID": "60788256587173561",
    "time": "2020-12-16T19:44:33.582455242Z",
    "type": "TAKE_PROFIT_ORDER",
    "tradeID": "1172",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "price": "105.000",
    "reason": "CLIENT_ORDER"
  },
    "1173"
  ],
  "lastTransactionID": "1173"
}

更にこのエンドポイントでは、追加だけでなく取消・変更もできるようですので、両方試してみました。 まずは変更ですが、追加と同じように新しい値をセットするだけです。

data_ModifyTrade = {
        "takeProfit": {
                "price": "104.00"
                 }
        }

戻ってくる値は、現在セットされているTPのオーダーがキャンセルされ新しいプライスを持っているオーダーが作成される形になります。

{
  "takeProfitOrderCancelTransaction": {    👈いままでセットされていた値を取消
    "id": "1174",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1174",
    "requestID": "60788261946521791",
    "time": "2020-12-16T20:05:51.068767834Z",
    "type": "ORDER_CANCEL",
    "orderID": "1173",
    "replacedByOrderID": "1175",
    "reason": "CLIENT_REQUEST_REPLACED"
  },
  "takeProfitOrderTransaction": {     👈新しい値を追加する
    "id": "1175",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1174",
    "requestID": "60788261946521791",
    "time": "2020-12-16T20:05:51.068767834Z",
    "type": "TAKE_PROFIT_ORDER",
    "tradeID": "1172",
    "timeInForce": "GTC",
    "triggerCondition": "DEFAULT",
    "price": "104.000",
    "reason": "REPLACEMENT",
    "replacesOrderID": "1173",
    "cancellingTransactionID": "1174"
  },
  "relatedTransactionIDs": [
    "1174",
    "1175"
  ],
  "lastTransactionID": "1175"
}

変更する事はあっても取消をする事はめったにないと思われますが、NULL値を設定することにより取消可能とありますので念のため取消の場合も試してみました。
詳細の事例がなかったのでいくつか試したところ以下のように”None”を指定することにより、無事キャンセルされました。

data_ModifyTrade = {
        "takeProfit": None  
        }
{
  "takeProfitOrderCancelTransaction": {
    "id": "1180",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1180",
    "requestID": "60788271380787412",
    "time": "2020-12-16T20:43:20.907405756Z",
    "type": "ORDER_CANCEL",
    "orderID": "1179",
    "reason": "CLIENT_REQUEST"
  "relatedTransactionIDs": [
    "1180"
  ],
  "lastTransactionID": "1180"
}

特定のオープントレードをクローズする

今までは利確・損切注文による決済でしたが、次はトレードそのものをクローズする方法についてです。
先ずはトレードIDをもとにその取引を決済する方法

PUT /v3/accounts/{accountID}/trades/{tradeSpecifier}/close

現在あるオープントレードの内、そのトレードIDをもとにすべて(または一部)を決済できます。

data引数

名前 必須 説明
units string Y ALLまたは数値 ALLはすべてクローズ。マイナスは認めない。ポジションを超えてはいけない。

全額決済させたい場合は、以下のように”ALL"をdata引数の"units"にセットしてオーダーすると

data_Close = {
        "units":  "ALL"
        }  

反対取引でクローズした場合と同じようなフォーマットの戻り値が返って来ます。
"reason"は"MARKET_ORDER"ではなく、"MARKET_ORDER_TRADE_CLOSE"ですね。

{
  "orderCreateTransaction": {
    "id": "1181",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1181",
    "requestID": "60788280420502788",
    "time": "2020-12-16T21:19:15.828235399Z",
    "type": "MARKET_ORDER",
    "instrument": "USD_JPY",
    "units": "-1",
    "timeInForce": "FOK",
    "positionFill": "REDUCE_ONLY",
    "reason": "TRADE_CLOSE",
    "tradeClose": {
      "units": "ALL",
      "tradeID": "1172"
    }
  },
  "orderFillTransaction": {
    "id": "1182",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1181",
    "requestID": "60788280420502788",
    "time": "2020-12-16T21:19:15.828235399Z",
    "type": "ORDER_FILL",
    "orderID": "1181",
    "instrument": "USD_JPY",
    "units": "-1",
    "requestedUnits": "-1",
    "price": "103.482",
    "pl": "-0.0016",
    "quotePL": "-0.167",
    "financing": "0.0000",
    "baseFinancing": "0.00000000000000",
    "commission": "0.0000",
    "accountBalance": "98891.7163",
    "gainQuoteHomeConversionFactor": "0.00961454845",
    "lossQuoteHomeConversionFactor": "0.009711177078",
    "guaranteedExecutionFee": "0.0000",
    "quoteGuaranteedExecutionFee": "0",
    "halfSpreadCost": "0.0001",
    "fullVWAP": "103.482",
    "reason": "MARKET_ORDER_TRADE_CLOSE", 👈
    "tradesClosed": [  👈
      {
        "tradeID": "1172",
        "units": "-1",
        "realizedPL": "-0.0016",
        "financing": "0.0000",
        "baseFinancing": "0.00000000000000",
        "price": "103.482",
        "guaranteedExecutionFee": "0.0000",
        "quoteGuaranteedExecutionFee": "0",
        "halfSpreadCost": "0.0001"
      }
    ],
以下省略

特定の銘柄のポジションをクローズする

PUT /v3/accounts/{accountID}/positions/{instrument}/close

銘柄(通貨組)毎にそのポジションをすべて(または一部)決済する方法

data引数

名前 必須 説明
longUnits string ALLまたは数値 買ポジション決済用
ALLはすべてクローズ。マイナスは認めない。ポジションを超えてはいけない。
longClientExtensions string 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。
shortUnits string ALLまたは数値 売ポジション決済用
ALLはすべてクローズ。マイナスは認めない。ポジションを超えてはいけない。
shortClientExtensions string 拡張領域 独自のid, tag, extensionをセットできる  *MT4を使用の場合は使ってはいけない。

ロングでポジションを保有しているときに、data引数に以下のようにセットして実行すると

data_PositionClose = {
        "longUnits":  "ALL"
        }  

Keyの名前は一部違いますが、トレードをクローズさせた時と同じような値が返されます。
("longOrderCreateTransaction", "longOrderFillTransaction")

{
  "longOrderCreateTransaction": {
    "id": "1190",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1190",
    "requestID": "78802699802302719",        
    "time": "2020-12-16T22:42:12.064244756Z",
    "type": "MARKET_ORDER",
    "instrument": "USD_JPY",
    "units": "-1",
    "timeInForce": "FOK",
    "positionFill": "REDUCE_ONLY",
    "reason": "POSITION_CLOSEOUT",
    "longPositionCloseout": {
      "instrument": "USD_JPY",
      "units": "ALL"
    }
  },
  "longOrderFillTransaction": {
    "id": "1191",
    "accountID": "999-999-99999999-999",
    "userID": 99999999,
    "batchID": "1190",
    "requestID": "78802699802302719",
    "time": "2020-12-16T22:42:12.064244756Z",
    "type": "ORDER_FILL",
    "orderID": "1190",
    "instrument": "USD_JPY",
    "units": "-1",
    "requestedUnits": "-1",
    "price": "103.416",
    "pl": "-0.0003",
    "quotePL": "-0.034",
    "financing": "0.0000",
    "baseFinancing": "0.00000000000000",
    "commission": "0.0000",
    "accountBalance": "98891.7158",
    "gainQuoteHomeConversionFactor": "0.009619847086",
    "lossQuoteHomeConversionFactor": "0.009716528966",
    "guaranteedExecutionFee": "0.0000",
    "quoteGuaranteedExecutionFee": "0",
    "halfSpreadCost": "0.0002",
    "fullVWAP": "103.416",
    "reason": "MARKET_ORDER_POSITION_CLOSEOUT",
    "tradesClosed": [
      {
        "tradeID": "1188",
        "units": "-1",
        "realizedPL": "-0.0003",
        "financing": "0.0000",
        "baseFinancing": "0.00000000000000",
        "price": "103.416",
        "guaranteedExecutionFee": "0.0000",
        "quoteGuaranteedExecutionFee": "0",
        "halfSpreadCost": "0.0002"
      }
    ],
    "fullPrice": {
      "closeoutBid": "103.416",
      "closeoutAsk": "103.448",
      "timestamp": "2020-12-16T22:42:01.918227455Z",
      "bids": [
        {
          "price": "103.416",
          "liquidity": "10000000"
        }
      ],
      "asks": [
        {
          "price": "103.448",
          "liquidity": "10000000"
        }
      ]
    },
    "homeConversionFactors": {
      "gainQuoteHome": {
        "factor": "0.00961984708610"
      },
      "lossQuoteHome": {
        "factor": "0.00971652896636"
      },
      "gainBaseHome": {
        "factor": "1"
      },
      "lossBaseHome": {
        "factor": "1"
      }
    }
  },
  "relatedTransactionIDs": [
    "1190",
    "1191"
  ],
  "lastTransactionID": "1191"
}

当然ですが、ロング保有の時に"shortUnits"を使ったり、"longUnits"と"shortUnits"を両方指定したり、ポジションがない時に呼び出した場合は"CLOSEOUT_POSITION_DOESNT_EXIST"エラーになりました。


まとめ

今回は決済の仕方についてあれこれ試してみました。

利確・損切注文でクローズさせる場合は、トレードIDからそのトレードを変更(PUT /v3/accounts/{accountID}/trades/{tradeSpecifier}/orders)する方法が一番汎用性がありそうです。
ポジションをオープンする際に同時発注する場合は、スリッページを考慮したプライスが必要ですし、追加でオーダーする場合はすでにセットされているかどうか必ず確認が必要になります。
新規発注時に広めに利確・損切を同時注文してその結果をみて再度適切な価格を設定しなおすという方法も考えられます。

トレードやポジションそのものでクローズする場合は、自分で独自のIDを予めつけておけば、tradeIDを覚えておかなくても決済のタイミングでそのIDを呼び出しクローズが出来ます。
また、もしその口座で複数のシストレや裁量取引が混ざらず、その銘柄をすべてクローズしても問題ないシステムであれば、その銘柄のポジションを丸ごと”ALL"で決済させれば、同様にトレードIDを記録しておく必要がないのでシンプルな仕組みになりそうです。

決済させるだけでもいろいろなシナリオが考えられますね。