sharp-trading/bittrex/BittrexConnector.cs

316 lines
7.9 KiB
C#

using System;
using sharp.extensions;
using System.Collections.Generic;
using sharp.json;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Globalization;
using sharp.trading.cache;
/**
* https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=BTC-WAVES&tickInterval=day
*
**/
namespace sharp.trading.bittrex
{
public class BittrexConnector : TradingConnection
{
HMACSHA512 hmac;
ASCIIEncoding ascii = new ASCIIEncoding();
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
JSONMarket[] activeMarkets;
BittrexCurrency[] currentCurrencies;
public List<BittrexOrder> orderCache = new List<BittrexOrder>();
UInt64 nextNonce = 1;
public BittrexConnector()
:base("bittrex.com")
{
ApiKey = "";
ApiSecret = "";
initialize();
}
public BittrexConnector(string apiKey,string apiSecret)
:base("bittrex.com")
{
ApiKey = apiKey;
ApiSecret = apiSecret;
hmac = new HMACSHA512(Encoding.UTF8.GetBytes(apiSecret));
initialize();
}
public override object[] getConstructorArguments()
{
return new object[]{
ApiKey,
ApiSecret
};
}
private void initialize(){
currentCurrencies = _getCurrencies();
fetchActiveMarkets();
nextNonce = (ulong)DateTime.UtcNow.Ticks;
nextNonce *= 100;
}
private JSONMarket jsonMarket(string marketSymbol,string baseSymbol){
foreach (JSONMarket jm in activeMarkets){
if (jm.MarketCurrency.Equals(marketSymbol) && jm.BaseCurrency.Equals(baseSymbol)){
return jm;
}
}
throw new IndexOutOfRangeException(String.Format("Market {0}-{1} not found.",marketSymbol,baseSymbol));
}
private void fetchActiveMarkets(){
List<JSONMarket> markets = new List<JSONMarket>();
JSON reply = JSONWebRequest.Call("https://bittrex.com/api/v1.1/public/getmarkets");
if (reply["success"].Boolean == true){
foreach (JSON market in reply["result"]){
JSONMarket jm = market.To<JSONMarket>();
if (jm.IsActive){
markets.Add(jm);
}
}
activeMarkets = markets.ToArray();
} else {
throw new Exception("Failed to read active markets.");
}
}
public override Pair<string>[] listMarkets()
{
Pair<string>[] pairs = new Pair<string>[activeMarkets.Length];
for (int n = 0; n < pairs.Length;n++){
pairs[n] = new Pair<string>(activeMarkets[n].BaseCurrency,activeMarkets[n].MarketCurrency);
}
return pairs;
}
public override Market openMarket(string tradedSymbol, string payingSymbol)
{
JSONMarket jm = jsonMarket(tradedSymbol, payingSymbol);
BittrexMarket m = new BittrexMarket(this, jm);
return m;
}
public override Balance[] getBalances()
{
JSON balances = authenticatedCall("https://bittrex.com/api/v1.1/account/getbalances");
return balances.To<BittrexBalance[]>();
}
public override Order[] getOpenOrders()
{
JSON openorders = authenticatedCall("https://bittrex.com/api/v1.1/market/getopenorders");
return openorders.To<BittrexOrder[]>();
}
public override Order[] getHistoricOrders()
{
JSON historicorders = authenticatedCall("https://bittrex.com/api/v1.1/account/getorderhistory");
Order[] orders = historicorders.To<BittrexOrder[]>();
foreach (Order o in orders){
o.IsOpen = false;
}
return orders;
}
public void UpdateOrderCache(){
orderCache.Clear();
//orderCache.AddRange((BittrexOrder[])getOpenOrders());
//orderCache.AddRange((BittrexOrder[])getHistoricOrders());
}
public BittrexOrder findCachedOrder(string orderID){
foreach (BittrexOrder o in orderCache){
if (o.OrderID.Equals(orderID)){
return o;
}
}
return null;
}
public override Order createOrder(OrderType orderType, OrderTarget orderTarget, string marketSymbol, string baseSymbol, double amount, double price)
{
if (orderTarget != OrderTarget.LIMIT){
throw new NotImplementedException("Only Limit orders are currently supported");
}
string marketname = string.Format("{0}-{1}", baseSymbol, marketSymbol);
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("market",marketname),
new KeyValuePair<string, string>("quantity",amount.ToString(CultureInfo.InvariantCulture)),
new KeyValuePair<string, string>("rate",price.ToString(CultureInfo.InvariantCulture))
};
string url = null;
switch (orderType)
{
case OrderType.BUY:
url = "https://bittrex.com/api/v1.1/market/buylimit";
break;
case OrderType.SELL:
url = "https://bittrex.com/api/v1.1/market/selllimit";
break;
}
JSON result = authenticatedCall(url, parms);
return getOrder(result["uuid"].String);
}
public override Currency[] getCurrencies()
{
return this.currentCurrencies.Segment(0);
}
private BittrexOrder getOrderOnline(string orderID)
{
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("uuid",orderID)
};
JSON json = authenticatedCall("https://bittrex.com/api/v1.1/account/getorder", parms);
return json.To<BittrexOrder>();
}
public override Order getOrder(string orderID)
{
if (orderCache.Count == 0){
UpdateOrderCache();
}
BittrexOrder cached = findCachedOrder(orderID);
if (cached != null){
return cached;
}
return getOrderOnline(orderID);
}
public override void refreshOrder(Order order){
BittrexOrder refresh = getOrderOnline(order.OrderID);
order.FilledVolume = refresh.FilledVolume;
order.PayedFees = refresh.PayedFees;
order.PayedPrice = refresh.PayedPrice;
order.OrderState = refresh.OrderState;
order.IsOpen = refresh.IsOpen;
}
public override void cancelOrder(string orderID)
{
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("uuid",orderID)
};
authenticatedCall("https://bittrex.com/api/v1.1/market/cancel", parms);
}
private BittrexCurrency[] _getCurrencies()
{
JSON jcurrencies = publicCall("https://bittrex.com/api/v1.1/public/getcurrencies");
return jcurrencies.To<BittrexCurrency[]>();
}
public JSON publicCall(string url){
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[0];
return publicCall(url, parms);
}
public JSON publicCall(string url, KeyValuePair<string, string>[] parms)
{
StringBuilder sb = new StringBuilder();
sb.Append(url);
sb.Append("?");
foreach (KeyValuePair<string, string> parm in parms)
{
sb.Append(Uri.EscapeDataString(parm.Key));
sb.Append("=");
sb.Append(Uri.EscapeDataString(parm.Value));
sb.Append("&");
}
string finalurl = sb.ToString();
finalurl = finalurl.Substring(0, finalurl.Length-1);
JSON result = JSONWebRequest.Call(finalurl);
if (!result["success"].Boolean)
{
throw new BittrexException(result);
}
return result["result"];
}
private JSON authenticatedCall(string url)
{
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[0];
return authenticatedCall(url, parms);
}
private JSON authenticatedCall(string url, KeyValuePair<string, string>[] parms)
{
StringBuilder sb = new StringBuilder();
sb.Append(url);
sb.Append("?");
foreach (KeyValuePair<string, string> parm in parms)
{
sb.Append(Uri.EscapeDataString(parm.Key));
sb.Append("=");
sb.Append(Uri.EscapeDataString(parm.Value));
sb.Append("&");
}
sb.Append("apikey=");
sb.Append(ApiKey);
sb.Append("&nonce=");
sb.Append(nextNonce++);
string finalurl = sb.ToString();
string apisign = HexString.toString(hmac.ComputeHash(Encoding.ASCII.GetBytes(finalurl))).ToLower();
Dictionary<string, string> headers = new Dictionary<string, string>();
headers["apisign"] = apisign;
//Console.WriteLine("CALL: {0}", finalurl);
JSON result = JSONWebRequest.Call(finalurl, null, headers);
if (!result["success"].Boolean)
{
throw new BittrexException(result);
}
return result["result"];
}
}
}