仮想通貨の自動売買プログラムを作る過程を紹介しています。
今実装しようとしている以下のアルゴリズムの 1. を前回のコード追加でできるようになりました。(成行注文を入れれるようになった)
- 成行注文を入れる (枚数は変更できるようにする)
- 成行注文が確約した値段を確認する
- 確約した値段 + x で指値注文を入れる (x は後で変えられるようにする)
- 指値注文が通るのを待つ。完了したら再度 1. から繰り返す。
今回は 2. の処理を実装していきます。成行注文の ID から確約した値段を調べる処理を追加します。
確約した成行注文の ID を取得
成り行き注文が完了すると、レスポンスとしてこんな感じの文字列が返されます。
1 |
{ "child_order_acceptance_id":"JRF20180314-130354-490305"} |
この JRF から始まる値を GET /v1/me/getchildorders 命令の child_order_acceptance_id に指定して送ります。このレスポンスに含まれる average_price がその注文が確約した価格を表します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[ { "id": 264109112, "child_order_id": "JFX20180314-130355-987199F", "product_code": "FX_BTC_JPY", "side": "BUY", "child_order_type": "MARKET", "price": 0, "average_price": 969023, "size": 1, "child_order_state": "COMPLETED", "expire_date": "2018-04-13T13:03:54", "child_order_date": "2018-03-14T13:03:54", "child_order_acceptance_id": "JRF20180314-130354-490305", "outstanding_size": 0, "cancel_size": 0, "executed_size": 1, "total_commission": 0 } ] |
前回の作業で成行注文のレスポンスを取得する所までできているので、そのレスポンスから ID 部分を抜き取ります。やり方は以前 JSON 形式のメッセージを分解した方法と同じで、まずレスポンスの JSON の型を定義します。
1 2 3 4 5 |
///v1/me/sendchildorder で受け取るレスポンスの型を定義 public class JsonSendChildOrder { public string child_order_acceptance_id; } |
次に成行注文のレスポンスをこの型に当てはめて ID だけを取得。
1 2 |
var DesirializedResponse = JsonConvert.DeserializeObject<JsonSendChildOrder>(response); var ChildOrderAcceptanceID = DesirializedResponse.child_order_acceptance_id; |
これで成行注文に使われた ID を取得できたので、次はその ID から確約した値段を調べます。
成行注文の ID から確約した値段を調べる
GET /v1/me/getchildorders 命令のレスポンスから average_price を抜き出す必要があるので、このレスポンスの型も定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
///v1/me/getchildorders public class JsonGetChildOrders { public double id { get; set; } public string child_order_id { get; set; } public string product_code { get; set; } public string side { get; set; } public string child_order_type { get; set; } public double price { get; set; } public double average_price { get; set; } public double size { get; set; } public string child_order_state { get; set; } public DateTime expire_date { get; set; } public DateTime child_order_date { get; set; } public string child_order_acceptance_id { get; set; } public double outstanding_size { get; set; } public double cancel_size { get; set; } public double executed_size { get; set; } public double total_commission { get; set; } } |
指値注文をする関数を作ります。汎用性を持たせるために引数で ID を指定できるようにしました。
前回と同様、繰り返し使う事になる処理なので汎用性を持たせるためにレスポンスの処理は呼び出し側に任せるようにする。(つまり、ここでは単純にレスポンスを戻り値とする)
成行注文がまだ確約していない間は response が [] になる。この場合は後でリトライする必要があるので、ERROR_NO_RESPONSE を返すようにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public async Task<string> GetOrderInformationWithID(string ChildOrderAcceptanceID) { var method = "GET"; var path = "/v1/me/getchildorders"; var query = "?product_code=FX_BTC_JPY&child_order_acceptance_id=" + ChildOrderAcceptanceID; textBox1.AppendText(query + System.Environment.NewLine); using (var client = new HttpClient()) using (var request = new HttpRequestMessage(new HttpMethod(method), path + query)) { client.BaseAddress = endpointUri; var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var data = timestamp + method + path + query; var hash = SignWithHMACSHA256(data, apiSecret); request.Headers.Add("ACCESS-KEY", apiKey); request.Headers.Add("ACCESS-TIMESTAMP", timestamp); request.Headers.Add("ACCESS-SIGN", hash); var message = await client.SendAsync(request); var response = await message.Content.ReadAsStringAsync(); if (response == "[]") { textBox1.AppendText("GetOrderInformationWithID():OUT returning ERROR_NO_RESPONSE" + System.Environment.NewLine); return "ERROR_NO_RESPONSE"; } return response; } } |
呼び出し側ではこのレスポンスを分解して、average_price の値を取得。ついでに枚数の Size と確約状態の ChildOrderState も取っておく。あと、ERROR_NO_RESPONSE が返されている間は 10 秒間隔でリトライするようにした。
これが呼び出し側のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
string ResponseGetOrderInformationWithID = await GetOrderInformationWithID(ChildOrderAcceptanceID); while (ResponseGetOrderInformationWithID == "ERROR_NO_RESPONSE") { await Task.Delay(10000); ResponseGetOrderInformationWithID = await GetOrderInformationWithID(ChildOrderAcceptanceID); } //レスポンスから average_price と size を取り出す。 //念のため child_order_state も記録しておく。 var DesirializedResponse2 = JsonConvert.DeserializeObject<JsonGetChildOrders>(ResponseGetOrderInformationWithID.Substring(1, ResponseGetOrderInformationWithID.Length - 2)); var ChildOrderAveragePrice = DesirializedResponse2.average_price; var ChildOrderSize = DesirializedResponse2.size; var ChildOrderState = DesirializedResponse2.child_order_state; textBox1.AppendText(" ChildOrderAveragePrice: " + ChildOrderAveragePrice + System.Environment.NewLine); textBox1.AppendText(" ChildOrderSize: " + ChildOrderSize + System.Environment.NewLine); textBox1.AppendText(" ChildOrderState: " + ChildOrderState + System.Environment.NewLine); |
これからプログラムをもっと書いていくのに注文 ID の確認ができると便利なので、テキストボックスに child order acceptance id を入れてボタンを押せば情報が表示されるような UI も追加。
自動売買プログラムの作成状況
今のプログラムの見た目がこんな感じ。
これがコードの全容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json; using System.Security.Cryptography; namespace BtcFxStep1 { public partial class Form1 : Form { static readonly Uri endpointUri = new Uri("https://api.bitflyer.jp"); static readonly string apiKey = "xxxxxxxxxxxxxxxxxxx"; static readonly string apiSecret = "xxxxxxxxxxxxxxxxxxx"; //成行注文を入れる // Param: // sSide: "BUY" か "SELL" // iSize: 枚数 public async Task<string> MakeMarketOrder(string sSide, double iSize) { var method = "POST"; var path = "/v1/me/sendchildorder"; var query = ""; var body = @"{ ""product_code"": ""FX_BTC_JPY"", ""child_order_type"": ""MARKET"", ""side"": """ + sSide + @""", ""price"": 0, ""size"": " + numericUpDownMarketOrder.Value + @", ""minute_to_expire"": 43200, ""time_in_force"": ""GTC"" }"; using (var client = new HttpClient()) using (var request = new HttpRequestMessage(new HttpMethod(method), path + query)) using (var content = new StringContent(body)) { client.BaseAddress = endpointUri; content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); request.Content = content; var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var data = timestamp + method + path + query + body; var hash = SignWithHMACSHA256(data, apiSecret); request.Headers.Add("ACCESS-KEY", apiKey); request.Headers.Add("ACCESS-TIMESTAMP", timestamp); request.Headers.Add("ACCESS-SIGN", hash); var message = await client.SendAsync(request); var response = await message.Content.ReadAsStringAsync(); if (response == "[]") { Console.WriteLine("Error: MakeMarketOrder() returning ERROR_NO_RESPONSE"); return "ERROR_NO_RESPONSE"; } return response; } } public async Task<string> GetOrderInformationWithID(string ChildOrderAcceptanceID) { var method = "GET"; var path = "/v1/me/getchildorders"; var query = "?product_code=FX_BTC_JPY&child_order_acceptance_id=" + ChildOrderAcceptanceID; using (var client = new HttpClient()) using (var request = new HttpRequestMessage(new HttpMethod(method), path + query)) { client.BaseAddress = endpointUri; var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var data = timestamp + method + path + query; var hash = SignWithHMACSHA256(data, apiSecret); request.Headers.Add("ACCESS-KEY", apiKey); request.Headers.Add("ACCESS-TIMESTAMP", timestamp); request.Headers.Add("ACCESS-SIGN", hash); var message = await client.SendAsync(request); var response = await message.Content.ReadAsStringAsync(); if (response == "[]") { Console.WriteLine("GetOrderInformationWithID():OUT returning ERROR_NO_RESPONSE"); return "ERROR_NO_RESPONSE"; } return response; } } static string SignWithHMACSHA256(string data, string secret) { using (var encoder = new HMACSHA256(Encoding.UTF8.GetBytes(secret))) { var hash = encoder.ComputeHash(Encoding.UTF8.GetBytes(data)); return ToHexString(hash); } } static string ToHexString(byte[] bytes) { var sb = new StringBuilder(bytes.Length * 2); foreach (var b in bytes) { sb.Append(b.ToString("x2")); } return sb.ToString(); } ///v1/me/getchildorders public class JsonGetChildOrders { public double id { get; set; } public string child_order_id { get; set; } public string product_code { get; set; } public string side { get; set; } public string child_order_type { get; set; } public double price { get; set; } public double average_price { get; set; } public double size { get; set; } public string child_order_state { get; set; } public DateTime expire_date { get; set; } public DateTime child_order_date { get; set; } public string child_order_acceptance_id { get; set; } public double outstanding_size { get; set; } public double cancel_size { get; set; } public double executed_size { get; set; } public double total_commission { get; set; } } ///v1/me/sendchildorder で受け取るレスポンスの型を定義 public class JsonSendChildOrder { public string child_order_acceptance_id; } ///v1/ticker で受け取るレスポンスの型を定義 // GetCurrentBtcFxPrice() で使う public class JsonTicker { public string product_code { get; set; } public DateTime timestamp { get; set; } public int tick_id { get; set; } public double best_bid { get; set; } public double best_ask { get; set; } public double best_bid_size { get; set; } public double best_ask_size { get; set; } public double total_bid_depth { get; set; } public double total_ask_depth { get; set; } public double ltp { get; set; } public double volume { get; set; } public double volume_by_product { get; set; } } //採取取引価格を TextBoxPrice に出力する public async Task GetCurrentBtcFxPrice() { while (true) { var method = "GET"; var path = "/v1/ticker"; var query = "?product_code=FX_BTC_JPY"; using (var client = new HttpClient()) using (var request = new HttpRequestMessage(new HttpMethod(method), path + query)) { client.BaseAddress = endpointUri; var message = await client.SendAsync(request); var response = await message.Content.ReadAsStringAsync(); var DesirializedResponse = JsonConvert.DeserializeObject<JsonTicker>(response); var CurrentPrice = String.Format("{0:#,0}", DesirializedResponse.ltp); textBoxPrice.Text = CurrentPrice; } await Task.Delay(1000); } } public Form1() { InitializeComponent(); Task JobUpdatePrice = GetCurrentBtcFxPrice(); } private void button1_Click(object sender, EventArgs e) { Task JobButton1 = RunThisWhenButton1IsClicked(); } public async Task RunThisWhenButton1IsClicked() { //for testing purposes } private void buttonMarketOrder_Click(object sender, EventArgs e) { Task JobMakeMarketOrder = RunThisWhenButtonMarketOrderIsClicked(); } public async Task RunThisWhenButtonMarketOrderIsClicked() { textBox1.AppendText("MakeMarketOrder() Start." + System.Environment.NewLine); string ResponseMakeMarketOrder = await MakeMarketOrder("BUY", (double)numericUpDownMarketOrder.Value); var DesirializedResponse = JsonConvert.DeserializeObject<JsonSendChildOrder>(ResponseMakeMarketOrder); var ChildOrderAcceptanceID = DesirializedResponse.child_order_acceptance_id; textBox1.AppendText("MakeMarketOrder() succeeded with ID: " + ChildOrderAcceptanceID + System.Environment.NewLine); } private void buttonCheckOrderFromID_Click(object sender, EventArgs e) { Task Job1 = RunThisWhenButtonCheckOrderFromIDIsClicked(); } public async Task RunThisWhenButtonCheckOrderFromIDIsClicked() { //注文 ID の情報を取得。早すぎるとエラーになるのでその場合には再チャレンジ string ResponseGetOrderInformationWithID = await GetOrderInformationWithID(textBoxCheckOrderFromID.Text); for (int i=0; (ResponseGetOrderInformationWithID == "ERROR_NO_RESPONSE") && (i<100); i++) { textBox1.AppendText("Waiting for response. Re-check in 10sec."); await Task.Delay(10000); ResponseGetOrderInformationWithID = await GetOrderInformationWithID(textBoxCheckOrderFromID.Text); } //レスポンス (JsonGetChildOrders) を表示。 var DesirializedResponse = JsonConvert.DeserializeObject<JsonGetChildOrders>(ResponseGetOrderInformationWithID.Substring(1, ResponseGetOrderInformationWithID.Length - 2)); textBox1.AppendText("GetOrderInformationWithID succeeded for ID: " + textBoxCheckOrderFromID.Text + System.Environment.NewLine); textBox1.AppendText(" id: " + DesirializedResponse.id + System.Environment.NewLine); textBox1.AppendText(" child_order_id: " + DesirializedResponse .child_order_id+ System.Environment.NewLine); textBox1.AppendText(" product_code: " + DesirializedResponse.product_code + System.Environment.NewLine); textBox1.AppendText(" side: " + DesirializedResponse.side + System.Environment.NewLine); textBox1.AppendText(" child_order_type: " + DesirializedResponse.child_order_type + System.Environment.NewLine); textBox1.AppendText(" price: " + DesirializedResponse.price + System.Environment.NewLine); textBox1.AppendText(" average_price: " + DesirializedResponse.average_price + System.Environment.NewLine); textBox1.AppendText(" size: " + DesirializedResponse.size + System.Environment.NewLine); textBox1.AppendText(" child_order_state: " + DesirializedResponse.child_order_state + System.Environment.NewLine); textBox1.AppendText(" expire_date: " + DesirializedResponse.expire_date + System.Environment.NewLine); textBox1.AppendText(" child_order_date: " + DesirializedResponse.child_order_date + System.Environment.NewLine); textBox1.AppendText(" child_order_acceptance_id: " + DesirializedResponse.child_order_acceptance_id + System.Environment.NewLine); textBox1.AppendText(" outstanding_size: " + DesirializedResponse.outstanding_size + System.Environment.NewLine); textBox1.AppendText(" cancel_size: " + DesirializedResponse.cancel_size + System.Environment.NewLine); textBox1.AppendText(" executed_size: " + DesirializedResponse.executed_size + System.Environment.NewLine); textBox1.AppendText(" total_commission: " + DesirializedResponse.total_commission + System.Environment.NewLine); } } } |