1 module trading.bitfinex; 2 3 import vibe.d; 4 5 /// 6 alias TickerResult = float[10]; 7 8 /// 9 @path("/v2/") 10 interface BitfinexPublicAPIv2 11 { 12 /// 13 @method(HTTPMethod.GET) 14 @path("ticker/:symbol") 15 TickerResult ticker(string _symbol); 16 } 17 18 /// 19 @path("/v1/") 20 interface BitfinexPublicAPI 21 { 22 /// 23 @method(HTTPMethod.GET) 24 @path("symbols") 25 string[] symbols(); 26 } 27 28 /// 29 struct OrderHistoryItem 30 { 31 Json exchange; 32 bool is_cancelled; 33 string avg_execution_price; 34 string timestamp; 35 string price; 36 bool is_live; 37 bool is_hidden; 38 string type; 39 string executed_amount; 40 string src; 41 long cid; 42 string cid_date; 43 long id; 44 string symbol; 45 bool was_forced; 46 string original_amount; 47 string side; 48 Json gid; 49 Json oco_order; 50 string remaining_amount; 51 } 52 53 /// 54 alias OrderHistoryResult = OrderHistoryItem[]; 55 56 /// 57 struct BFXOrderStatus 58 { 59 string exchange; 60 string avg_execution_price; 61 bool is_live; 62 bool is_cancelled; 63 bool is_hidden; 64 bool was_forced; 65 long id; 66 long cid; 67 string remaining_amount; 68 string executed_amount; 69 string timestamp; 70 string price; 71 string type; 72 string src; 73 string cid_date; //"2018-01-16" 74 string symbol; //"xrpusd" 75 string original_amount; 76 string side; 77 Json gid; 78 Json oco_order; 79 } 80 81 /// 82 interface BitfinexPrivateAPI 83 { 84 /// 85 Json accountInfos(); 86 /// 87 OrderHistoryResult orderHistory(); 88 /// 89 BFXOrderStatus newOrder(string symbol, string amount, string price, 90 string side, string ordertype); 91 /// 92 BFXOrderStatus orderStatus(long id); 93 } 94 95 /// 96 final class Bitfinex : BitfinexPublicAPI, BitfinexPublicAPIv2, BitfinexPrivateAPI 97 { 98 private static immutable API_URL = "https://api.bitfinex.com"; 99 100 private string key; 101 private string secret; 102 103 private BitfinexPublicAPI publicApi; 104 private BitfinexPublicAPIv2 publicApiV2; 105 106 /// 107 this(string key, string secret) 108 { 109 this.key = key; 110 this.secret = secret; 111 publicApiV2 = new RestInterfaceClient!BitfinexPublicAPIv2(API_URL); 112 publicApi = new RestInterfaceClient!BitfinexPublicAPI(API_URL); 113 } 114 115 /// 116 TickerResult ticker(string symbol) 117 { 118 return publicApiV2.ticker(symbol); 119 } 120 121 /// 122 string[] symbols() 123 { 124 return publicApi.symbols(); 125 } 126 127 /// 128 Json accountInfos() 129 { 130 static immutable METHOD_URL = "/v1/account_infos"; 131 132 return request!Json(METHOD_URL, Json.emptyObject); 133 } 134 135 /// 136 OrderHistoryResult orderHistory() 137 { 138 static immutable METHOD_URL = "/v1/orders/hist"; 139 140 return request!OrderHistoryResult(METHOD_URL, Json.emptyObject); 141 } 142 143 /// 144 BFXOrderStatus newOrder(string symbol, string amount, string price, 145 string side, string ordertype) 146 { 147 148 static immutable METHOD_URL = "/v1/order/new"; 149 150 Json params = Json.emptyObject; 151 params["symbol"] = symbol; 152 params["amount"] = amount; 153 params["price"] = price; 154 params["exchange"] = "bitfinex"; 155 params["side"] = side; 156 params["type"] = ordertype; 157 158 return request!BFXOrderStatus(METHOD_URL, params); 159 } 160 161 /// 162 BFXOrderStatus orderStatus(long id) 163 { 164 static immutable METHOD_URL = "/v1/order/status"; 165 166 Json params = Json.emptyObject; 167 params["order_id"] = id; 168 169 return request!BFXOrderStatus(METHOD_URL, params); 170 } 171 172 private auto request(T)(string path, Json postData = Json.emptyObject) 173 { 174 import std.digest.sha : SHA384, toHexString, LetterCase; 175 import std.conv : to; 176 import std.base64 : Base64; 177 import std.digest.hmac : hmac; 178 import std.string : representation; 179 180 postData["request"] = path; 181 postData["nonce"] = Clock.currStdTime().to!string; 182 183 auto res = requestHTTP(API_URL ~ path, (scope HTTPClientRequest req) { 184 185 string bodyData = postData.toString; 186 string payload = Base64.encode(cast(ubyte[]) bodyData); 187 188 //logInfo("payload: %s", payload); 189 190 string signature = payload.representation.hmac!SHA384(secret.representation) 191 .toHexString!(LetterCase.lower).idup; 192 193 req.method = HTTPMethod.POST; 194 req.headers["X-BFX-APIKEY"] = key; 195 req.headers["X-BFX-PAYLOAD"] = payload; 196 req.headers["X-BFX-SIGNATURE"] = signature; 197 req.headers["Content-Type"] = "application/json"; 198 req.headers["Content-Length"] = (bodyData.length).to!string; 199 200 req.bodyWriter.write(bodyData); 201 }); 202 scope (exit) 203 { 204 res.dropBody(); 205 } 206 207 if (res.statusCode == 200) 208 { 209 auto json = res.readJson(); 210 211 //logInfo("Response: %s", json); 212 213 return deserializeJson!T(json); 214 } 215 else 216 { 217 logDebug("API Error: %s", res.bodyReader.readAllUTF8()); 218 logError("API Error Code: %s", res.statusCode); 219 throw new Exception("API Error"); 220 } 221 } 222 }