1 module trading.kraken; 2 3 import vibe.d; 4 5 /// 6 struct BalanceResult 7 { 8 /// 9 @optional string[string] result; 10 11 /// 12 @optional Json error; 13 } 14 15 /// 16 enum OrderStatus 17 { 18 pending, // = order pending book entry 19 open, // = open order 20 closed, // = closed order 21 canceled, // = order canceled 22 expired, // = order expired 23 } 24 25 /// 26 enum OrderSide 27 { 28 buy, 29 sell 30 } 31 32 unittest 33 { 34 assert(to!string(OrderSide.buy) == "buy"); 35 } 36 37 /// 38 struct OrderDesc 39 { 40 string pair; 41 @byName OrderSide type; 42 string ordertype; 43 string price; 44 string price2; 45 string leverage; 46 string order; 47 @optional string close; 48 } 49 50 /// 51 struct OrderInfo 52 { 53 @optional int userref; 54 float opentm; 55 float starttm; 56 float expiretm; 57 @optional float closetm; 58 string vol; 59 string vol_exec; 60 OrderDesc descr; 61 string fee; 62 string price; 63 @optional string refid; 64 string oflags; 65 @byName OrderStatus status; 66 string misc; 67 @optional string reason; 68 string cost; 69 } 70 71 /// 72 struct QueryOrdersResult 73 { 74 /// 75 @optional OrderInfo[string] result; 76 77 /// 78 @optional Json error; 79 } 80 81 /// 82 struct AddOrderResult 83 { 84 /// 85 struct Result 86 { 87 /// 88 Json descr; 89 /// 90 string[] txid; 91 } 92 93 /// 94 @optional Result result; 95 96 /// 97 @optional Json error; 98 } 99 100 /// 101 struct TickerInfo 102 { 103 /// current (last trade) 104 string[] c; 105 /// bid price 106 string[] b; 107 /// ask price 108 string[] a; 109 /// low 110 string[] l; 111 /// high 112 string[] h; 113 /// volume 114 string[2] v; 115 } 116 117 /// 118 struct TickerResult 119 { 120 /// 121 TickerInfo[string] result; 122 } 123 124 /// 125 struct OrderBook 126 { 127 /// 128 Json[] asks; 129 /// 130 Json[] bids; 131 } 132 133 /// 134 struct OrderBookResult 135 { 136 /// 137 OrderBook[string] result; 138 } 139 140 /// 141 interface KrakenAPI 142 { 143 /// 144 TickerResult Ticker(string pair); 145 146 /// 147 OrderBookResult OrderBook(string pair); 148 149 /// 150 BalanceResult Balance(); 151 152 /// 153 QueryOrdersResult QueryOrders(string txid); 154 155 /// 156 AddOrderResult AddOrder(string pair, OrderSide type, string ordertype, 157 string price, string volume); 158 } 159 160 /// 161 final class Kraken : KrakenAPI 162 { 163 static immutable API_URL = "https://api.kraken.com"; 164 165 private string key; 166 private string secret; 167 168 this(string key, string secret) 169 { 170 this.key = key; 171 this.secret = secret; 172 } 173 174 TickerResult Ticker(string pair) 175 { 176 static immutable METHOD_URL = "/0/public/Ticker"; 177 178 Json params = Json.emptyObject; 179 params["pair"] = pair; 180 181 return request!TickerResult(METHOD_URL, params); 182 } 183 184 unittest 185 { 186 auto api = new Kraken("", ""); 187 auto res = api.Ticker("XRPUSD"); 188 assert(res.result.length > 0); 189 } 190 191 OrderBookResult OrderBook(string pair) 192 { 193 static immutable METHOD_URL = "/0/public/Depth"; 194 195 Json params = Json.emptyObject; 196 params["pair"] = pair; 197 198 return request!OrderBookResult(METHOD_URL, params); 199 } 200 201 BalanceResult Balance() 202 { 203 static immutable METHOD_URL = "/0/private/Balance"; 204 205 return request!BalanceResult(METHOD_URL); 206 } 207 208 QueryOrdersResult QueryOrders(string txid) 209 { 210 static immutable METHOD_URL = "/0/private/QueryOrders"; 211 212 Json params = Json.emptyObject; 213 params["txid"] = txid; 214 215 return request!QueryOrdersResult(METHOD_URL, params); 216 } 217 218 AddOrderResult AddOrder(string pair, OrderSide type, string ordertype, 219 string price, string volume) 220 { 221 static immutable METHOD_URL = "/0/private/AddOrder"; 222 223 Json params = Json.emptyObject; 224 params["pair"] = pair; 225 params["type"] = to!string(type); 226 params["ordertype"] = ordertype; 227 params["volume"] = volume; 228 params["trading_agreement"] = "agree"; 229 if (price.length > 0) 230 params["price"] = price; 231 232 return request!AddOrderResult(METHOD_URL, params); 233 } 234 235 private auto request(T)(string path, Json postData = Json.emptyObject) 236 { 237 import std.digest.sha : sha256Of, SHA512; 238 import std.conv : to; 239 import std.base64 : Base64; 240 import std.digest.hmac : hmac; 241 242 auto nonce = Clock.currStdTime(); 243 244 postData["nonce"] = nonce; 245 246 auto res = requestHTTP(API_URL ~ path, (scope HTTPClientRequest req) { 247 248 string payload = postData.toString; 249 250 auto nonceAndData = nonce.to!string ~ payload; 251 252 //logInfo("payload: %s",payload); 253 254 auto signature = (path.representation ~ sha256Of(nonceAndData)).hmac!SHA512( 255 Base64.decode(secret)); 256 257 req.method = HTTPMethod.POST; 258 req.headers["API-Key"] = key; 259 req.headers["API-Sign"] = Base64.encode(signature); 260 req.headers["Content-Type"] = "application/json"; 261 req.headers["Content-Length"] = payload.length.to!string; 262 263 req.bodyWriter.write(payload); 264 }); 265 scope (exit) 266 { 267 res.dropBody(); 268 } 269 270 if (res.statusCode == 200) 271 { 272 auto json = res.readJson(); 273 274 //logInfo("Response: %s", json); 275 276 return deserializeJson!T(json); 277 } 278 else 279 { 280 logDebug("API Error: %s", res.bodyReader.readAllUTF8()); 281 logError("API Error Code: %s", res.statusCode); 282 throw new Exception("API Error"); 283 } 284 } 285 }