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 unittest 128 { 129 auto api = new Bitfinex("", ""); 130 auto res = api.symbols(); 131 assert(res.length > 0); 132 } 133 134 /// 135 Json accountInfos() 136 { 137 static immutable METHOD_URL = "/v1/account_infos"; 138 139 return request!Json(METHOD_URL, Json.emptyObject); 140 } 141 142 /// 143 OrderHistoryResult orderHistory() 144 { 145 static immutable METHOD_URL = "/v1/orders/hist"; 146 147 return request!OrderHistoryResult(METHOD_URL, Json.emptyObject); 148 } 149 150 /// 151 BFXOrderStatus newOrder(string symbol, string amount, string price, 152 string side, string ordertype) 153 { 154 155 static immutable METHOD_URL = "/v1/order/new"; 156 157 Json params = Json.emptyObject; 158 params["symbol"] = symbol; 159 params["amount"] = amount; 160 params["price"] = price; 161 params["exchange"] = "bitfinex"; 162 params["side"] = side; 163 params["type"] = ordertype; 164 165 return request!BFXOrderStatus(METHOD_URL, params); 166 } 167 168 /// 169 BFXOrderStatus orderStatus(long id) 170 { 171 static immutable METHOD_URL = "/v1/order/status"; 172 173 Json params = Json.emptyObject; 174 params["order_id"] = id; 175 176 return request!BFXOrderStatus(METHOD_URL, params); 177 } 178 179 private auto request(T)(string path, Json postData = Json.emptyObject) 180 { 181 import std.digest.sha : SHA384, toHexString, LetterCase; 182 import std.conv : to; 183 import std.base64 : Base64; 184 import std.digest.hmac : hmac; 185 import std.string : representation; 186 187 postData["request"] = path; 188 postData["nonce"] = Clock.currStdTime().to!string; 189 190 auto res = requestHTTP(API_URL ~ path, (scope HTTPClientRequest req) { 191 192 string bodyData = postData.toString; 193 string payload = Base64.encode(cast(ubyte[]) bodyData); 194 195 //logInfo("payload: %s", payload); 196 197 string signature = payload.representation.hmac!SHA384(secret.representation) 198 .toHexString!(LetterCase.lower).idup; 199 200 req.method = HTTPMethod.POST; 201 req.headers["X-BFX-APIKEY"] = key; 202 req.headers["X-BFX-PAYLOAD"] = payload; 203 req.headers["X-BFX-SIGNATURE"] = signature; 204 req.headers["Content-Type"] = "application/json"; 205 req.headers["Content-Length"] = (bodyData.length).to!string; 206 207 req.bodyWriter.write(bodyData); 208 }); 209 scope (exit) 210 { 211 res.dropBody(); 212 } 213 214 if (res.statusCode == 200) 215 { 216 auto json = res.readJson(); 217 218 //logInfo("Response: %s", json); 219 220 return deserializeJson!T(json); 221 } 222 else 223 { 224 logDebug("API Error: %s", res.bodyReader.readAllUTF8()); 225 logError("API Error Code: %s", res.statusCode); 226 throw new Exception("API Error"); 227 } 228 } 229 }