master
Harald Wolff 2017-11-23 13:03:36 +01:00
parent 72af6108e5
commit e675cb243a
30 changed files with 1502 additions and 13 deletions

19
Balance.cs 100644
View File

@ -0,0 +1,19 @@
using System;
namespace sharp.trading
{
public class Balance : MarshalByRefObject
{
public virtual string Currency { get; set; }
public virtual Double CurrentBalance { get; set; }
public virtual Double AvailableBalance { get; set; }
public virtual Double PendingBalance { get; set; }
public virtual string WalletAddress { get; set; }
public override string ToString()
{
return string.Format("[Balance: Currency={0}, CurrentBalance={1}, AvailableBalance={2}, PendingBalance={3}, WalletAddress={4}]", Currency, CurrentBalance, AvailableBalance, PendingBalance, WalletAddress);
}
}
}

11
Currency.cs 100644
View File

@ -0,0 +1,11 @@
using System;
namespace sharp.trading
{
public class Currency : MarshalByRefObject
{
public String Symbol { get; protected set; }
public String Name { get; protected set; }
public double WithdrawalFee { get; protected set; }
public string BaseAddress { get; protected set; }
}
}

35
HistoricTrade.cs 100644
View File

@ -0,0 +1,35 @@
using System;
using System.Collections;
namespace sharp.trading
{
public class HistoricTrade : MarshalByRefObject, IComparable
{
public Int64 UniqueID;
public DateTime TimeStamp;
public Double Volume;
public Double Price;
public Double TotalPrice;
public OrderType OrderType;
public HistoricTrade()
{
}
public override string ToString()
{
return string.Format("[HistoricTrade: UniqueID={0}, TimeStamp={1}, Volume={2,14:#0.00000000}, Price={3,14:#0.00000000}, TotalPrice={4,14:#0.00000000}, OrderType={5}]", UniqueID, TimeStamp, Volume, Price, TotalPrice, OrderType);
}
public override bool Equals(object obj)
{
return this.UniqueID.Equals(((HistoricTrade)obj).UniqueID);
}
public int CompareTo(object obj)
{
return -this.UniqueID.CompareTo(((HistoricTrade)obj).UniqueID);
}
}
}

31
Market.cs 100644
View File

@ -0,0 +1,31 @@
using System;
using System.Net.WebSockets;
namespace sharp.trading
{
public abstract class Market : MarshalByRefObject
{
public string TradedSymbol { get; private set; }
public string PayingSymbol { get; private set; }
public TradingConnection Connection { get; private set; }
public double MinimumTradeVolume { get; protected set; }
public Market(TradingConnection c,string tradedSymbol,string payingSymbol)
{
this.Connection = c;
this.PayingSymbol = payingSymbol;
this.TradedSymbol = tradedSymbol;
}
public abstract OrderBook getOrderBook();
public abstract Order[] getOrders();
public abstract void Close();
public abstract HistoricTrade[] getHistoricTrades();
public abstract Tick[] getTicks();
}
}

View File

@ -1,12 +1,31 @@
using System;
using sharp.json.attributes;
namespace sharp.trading
{
public class Order
[JSONClassPolicy( Policy = JSONPolicy.PUBLIC)]
public class Order : MarshalByRefObject
{
public Double OrderVolume { get; set; }
public Double FilledVolume { get; set; }
public Double LimitPrice { get; set; }
public OrderType OrderType { get; set; }
public OrderTarget OrderTarget { get; set; }
public OrderState OrderState { get; set; }
public virtual Double OrderVolume { get; set; }
public virtual Double FilledVolume { get; set; }
public virtual Double LimitPrice { get; set; }
public virtual Double PayedPrice { get; set; }
public virtual Double PayedFees { get; set; }
public bool IsOpen { get; set; } = true;
public string OrderID { get; set; }
public override bool Equals(object obj)
{
if (obj == null){
return false;
}
return OrderID.Equals(((Order)obj).OrderID);
}
}
}

View File

@ -1,13 +1,111 @@
using System;
using System.ComponentModel;
namespace sharp.trading
{
public class OrderBook
public abstract class OrderBook : MarshalByRefObject
{
public VolumeRate[] Bids { get; set; }
public VolumeRate[] Asks { get; set; }
public VolumeRate[] Bids { get; protected set; }
public VolumeRate[] Asks { get; protected set; }
public OrderBook()
public Market Market { get; private set; }
public OrderBook(Market market)
{
Market = market;
}
public abstract void Refresh();
public string MarketName { get; private set; }
public string MarketCurrency { get; private set; }
public string BaseCurrency { get; private set; }
public double CurrentBid
{
get { return this.Bids[this.Bids.Length - 1].Price; }
}
public double CurrentAsk
{
get { return this.Asks[0].Price; }
}
public Tuple<double,double> getVolumePrice(double volume){
double volbuy = volume;
double volsell = volume;
double pricebuy = 0;
double pricesell = 0;
for (int n = 0; ;n++){
double avail;
if (volbuy > 0)
{
avail = volbuy < Asks[n].Volume ? volbuy : Asks[n].Volume;
pricebuy += avail * Asks[n].Price;
volbuy -= avail;
}
if (volsell > 0)
{
avail = volsell < Bids[Bids.Length - 1 - n].Volume ? volsell : Bids[Bids.Length - 1 - n].Volume;
pricesell += avail * Bids[Bids.Length - 1 - n].Price;
volsell -= avail;
}
if ((volbuy <= 0) && (volsell <= 0))
{
break;
}
}
return new Tuple<double, double>(pricebuy, pricesell);
}
public OrderbookVolume[] calculateVolumes(double[] volumes)
{
OrderbookVolume[] ovl = new OrderbookVolume[volumes.Length];
int bidend = Bids.Length - 1;
for (int vn = 0; vn < volumes.Length; vn++){
ovl[vn] = new OrderbookVolume();
}
for (int n = 0; ; n++)
{
bool done = true;
for (int vn = 0; vn < volumes.Length; vn++)
{
double toBuy = volumes[vn] - ovl[vn].VolumeBuy;
double toSell = volumes[vn] - ovl[vn].VolumeSell;
if ((toBuy <= 0) && (toSell <= 0)){
continue;
}
if (toBuy > Asks[n].Volume){
toBuy = Asks[n].Volume;
}
if (toSell > Bids[bidend - n].Volume){
toSell = Bids[bidend - n].Volume;
}
ovl[vn].PriceBuy += Asks[n].Price * toBuy;
ovl[vn].VolumeBuy += toBuy;
ovl[vn].PriceSell += Bids[bidend - n].Price * toSell;
ovl[vn].VolumeSell += toSell;
done = false;
}
if (done){
break;
}
}
return ovl;
}
}
}

8
OrderState.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace sharp.trading
{
public enum OrderState
{
INVALID, UNTOUCHED, PARTLYFILLED, FILLED, CANCELED
}
}

8
OrderTarget.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace sharp.trading
{
public enum OrderTarget
{
UNKNOWN, MARKET, LIMIT
}
}

8
OrderType.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace sharp.trading
{
public enum OrderType
{
BUY, SELL, UNKOWN
}
}

24
OrderbookVolume.cs 100644
View File

@ -0,0 +1,24 @@
using System;
namespace sharp.trading
{
public class OrderbookVolume : MarshalByRefObject
{
public double VolumeBuy;
public double PriceBuy;
public double VolumeSell;
public double PriceSell;
public double AverageUnitPriceBuy {
get { return PriceBuy / VolumeBuy; }
}
public double AverageUnitPriceSell
{
get { return PriceSell / VolumeSell; }
}
public OrderbookVolume()
{
}
}
}

20
Tick.cs 100644
View File

@ -0,0 +1,20 @@
using System;
namespace sharp.trading
{
public class Tick : MarshalByRefObject
{
public DateTime TimeStamp;
public double OpenAt;
public double CloseAt;
public double High;
public double Low;
public double BaseVolume;
public Tick()
{
}
}
}

View File

@ -1,15 +1,60 @@
using System;
using sharp.extensions;
using System.CodeDom;
using System.Collections.Generic;
namespace sharp.trading
{
public abstract class TradingConnection
public abstract class TradingConnection : MarshalByRefObject
{
public TradingConnection()
public String UniqueProviderName { get; private set; }
public TradingConnection(string providerID)
{
UniqueProviderName = providerID;
}
public abstract OrderBook getOrderBook();
public abstract object[] getConstructorArguments();
public abstract Currency[] getCurrencies();
public abstract Balance[] getBalances();
public abstract Market openMarket(string tradedSymbol, string payingSymbol);
public abstract Pair<string>[]
listMarkets();
public abstract Order[] getOpenOrders();
public abstract Order[] getHistoricOrders();
public abstract Order createOrder(OrderType orderType, OrderTarget orderTarget, string marketSymbol, string baseSymbol, double amount, double price);
public abstract Order getOrder(string orderID);
public abstract void cancelOrder(string orderID);
public abstract void refreshOrder(Order order);
public Currency getCurrency(string symbol){
foreach (Currency c in getCurrencies()){
if (c.Symbol.Equals(symbol)){
return c;
}
}
throw new NotSupportedException(String.Format("Currency '{0}' not supported by Bittrex",symbol));
}
public Market openMarket(Pair<string> pair){
return openMarket(pair.Item2, pair.Item1);
}
public Pair<string>[] getMarketsForSymbol(string symbol){
List<Pair<string>> r = new List<Pair<string>>();
foreach (Pair<string> p in listMarkets()){
if (p.Item1.Equals(symbol) || p.Item2.Equals(symbol)){
r.Add(p);
}
}
return r.ToArray();
}
public void cancelOrder(Order order){
cancelOrder(order.OrderID);
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using sharp.extensions;
namespace sharp.trading
{
public static class TradingEnvironment
{
private static string _datadirectory = initialDataDirectory();
public static string DataDirectory {
get { return _datadirectory; }
set {
_datadirectory = value;
DirectoryExtensions.EnsureDirectoryExists(_datadirectory);
}
}
public static TradingConnection DefaultConnection { get; set; }
private static string initialDataDirectory(){
string p = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".sharp.trading");
DirectoryExtensions.EnsureDirectoryExists(p);
return p;
}
}
}

View File

@ -1,9 +1,14 @@
using System;
using sharp.json;
using sharp.json.attributes;
namespace sharp.trading
{
public struct VolumeRate
[JSONClassPolicy(Policy = JSONPolicy.ATTRIBUTED)]
public struct VolumeRate : IComparable
{
[JSONField(Alias = "Quantity")]
public Double Volume { get; set; }
[JSONField(Alias = "Rate")]
public Double Price { get; set; }
public VolumeRate(Double Volume,Double Price)
@ -12,5 +17,13 @@ namespace sharp.trading
this.Price = Price;
}
public int CompareTo(object obj)
{
return Price.CompareTo(((VolumeRate)obj).Price);
}
public double UnityPrice {
get { if (this.Volume != 0) { return this.Price / this.Volume; } return 0; }
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using sharp.json;
using sharp.json.attributes;
namespace sharp.trading.bittrex
{
public class BittrexBalance : Balance
{
[JSONField( Alias = "Balance")]
public override Double CurrentBalance { get; set; }
[JSONField(Alias = "Available")]
public override Double AvailableBalance { get; set; }
[JSONField(Alias = "Pending")]
public override Double PendingBalance { get; set; }
[JSONField(Alias = "CryptoAddress")]
public override string WalletAddress { get; set; }
}
}

View File

@ -0,0 +1,315 @@
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"];
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using sharp.json;
namespace sharp.trading.bittrex
{
public class BittrexCurrency : Currency
{
public BittrexCurrency(JSON json)
{
this.Symbol = json["Currency"].String;
this.Name = json["CurrencyLong"].String;
this.WithdrawalFee = json["TxFee"].Double;
this.BaseAddress = json["BaseAddress"].String;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using sharp.json;
namespace sharp.trading.bittrex
{
public class BittrexException : Exception
{
public JSON JSONResult { get; private set; }
public BittrexException(JSON result)
:base(result["message"].To<string>())
{
JSONResult = result;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using sharp.json;
namespace sharp.trading.bittrex
{
public class BittrexHistoricTrade : HistoricTrade
{
public BittrexHistoricTrade(JSON json)
{
UniqueID = json["Id"].Integer;
TimeStamp = DateTime.Parse(json["TimeStamp"].String);
Price = json["Price"].Double;
TotalPrice = json["Total"].Double;
Volume = json["Quantity"].Double;
string ot = json["OrderType"].String;
switch (ot){
case "BUY":
OrderType = OrderType.BUY;
break;
case "SELL":
OrderType = OrderType.SELL;
break;
}
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using sharp.json;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
namespace sharp.trading.bittrex
{
public class BittrexMarket : Market
{
BittrexConnector connector;
JSONMarket market;
OrderBook orderBook;
public BittrexMarket(BittrexConnector connector, JSONMarket market)
: base(connector, market.MarketCurrency, market.BaseCurrency)
{
this.market = market;
this.connector = connector;
MinimumTradeVolume = market.MinTradeSize;
orderBook = new BittrexOrderbook(this);
}
public override void Close()
{
/** TODO: Nothing to free just now **/
}
public override OrderBook getOrderBook()
{
orderBook.Refresh();
return this.orderBook;
}
public override Order[] getOrders()
{
throw new NotImplementedException();
}
public string MarketName { get { return market.MarketName; } }
public string MarketCurrency { get { return market.MarketCurrency; } }
public string BaseCurrency { get { return market.BaseCurrency; } }
public override string ToString()
{
return string.Format("[BittrexMarket: MarketName={0}, MarketCurrency={1}, BaseCurrency={2}]", MarketName, MarketCurrency, BaseCurrency);
}
public override HistoricTrade[] getHistoricTrades()
{
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("market",MarketName)
};
JSON result = connector.publicCall("https://bittrex.com/api/v1.1/public/getmarkethistory", parms);
return result.To<BittrexHistoricTrade[]>();
}
public override Tick[] getTicks()
{
KeyValuePair<string, string>[] parms = new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("marketName",MarketName),
new KeyValuePair<string, string>("tickInterval","day")
};
JSON jticks = connector.publicCall("https://bittrex.com/Api/v2.0/pub/market/GetTicks",parms);
return jticks.To<BittrexTick[]>();
}
}
}

View File

@ -0,0 +1,108 @@
using System;
using sharp.json;
using sharp.json.attributes;
namespace sharp.trading.bittrex
{
[JSONClassPolicy( Policy = JSONPolicy.ALL)]
public class BittrexOrder : Order
{
BittrexConnector Connector { get; set; }
public BittrexOrder(BittrexConnector connector)
{
this.Connector = connector;
}
public BittrexOrder()
{
}
public override string ToString()
{
return string.Format("[BittrexOrder: MarketName={0}, Limit={1}, OrderVolume={2}, FilledVolume={3}]", MarketName, LimitPrice, OrderVolume, FilledVolume);
}
[JSONField(Alias = "Exchange")]
string MarketName { get; set; }
[JSONField(Alias = "Type")]
private string __type
{
get {
return null;
}
set
{
BittrexOrderType = value;
}
}
[JSONField( Alias = "OrderType")]
private string BittrexOrderType {
set {
if (value.Equals("LIMIT_SELL")){
OrderType = OrderType.SELL;
OrderTarget = OrderTarget.LIMIT;
} else if (value.Equals("LIMIT_BUY")){
OrderType = OrderType.BUY;
OrderTarget = OrderTarget.LIMIT;
}
}
}
[JSONField( Alias = "Quantity")]
private double _volume {
get { return OrderVolume; }
set {
OrderVolume = value;
}
}
[JSONField(Alias = "QuantityRemaining")]
double OrderVolumeRemaining
{
get; set;
}
public override double FilledVolume {
get { return this.OrderVolume - this.OrderVolumeRemaining; }
set { this.OrderVolumeRemaining = this.OrderVolume - value; }
}
[JSONField(Alias = "Limit")]
private double _limit
{
get { return this.LimitPrice; }
set { this.LimitPrice = value; }
}
[JSONField(Alias = "Price")]
private double _price
{
get { return this.PayedPrice; }
set { this.PayedPrice = value; }
}
[JSONField(Alias = "Commission")]
private double _fee
{
get { return this.PayedFees; }
set { this.PayedFees = value; }
}
[JSONField(Alias = "CommissionPaid")]
private double _fee2
{
get { return this.PayedFees; }
set { this.PayedFees = value; }
}
[JSONField]
private string OrderUuid {
get { return this.OrderID; }
set { this.OrderID = value; }
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using sharp.json;
using System.Collections.Generic;
using System.IO;
namespace sharp.trading.bittrex
{
public class BittrexOrderbook : OrderBook
{
BittrexMarket bmarket;
public BittrexOrderbook(BittrexMarket market)
:base(market)
{
bmarket = market;
}
public override void Refresh()
{
JSON reply = JSONWebRequest.Call(String.Format("https://bittrex.com/api/v1.1/public/getorderbook?market={0}&type=both",bmarket.MarketName));
if (!reply["success"].Boolean){
throw new IOException("Orderbook Refresh failed: " + reply["message"].String);
}
VolumeRate[] bids = reply["result"]["buy"].To<VolumeRate[]>();
VolumeRate[] asks = reply["result"]["sell"].To<VolumeRate[]>();
Array.Sort(bids);
Array.Sort(asks);
Bids = bids;
Asks = asks;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using sharp.json;
namespace sharp.trading.bittrex
{
public class BittrexTick : Tick
{
public BittrexTick(JSON json)
{
TimeStamp = json["T"].To<DateTime>();
OpenAt = json["O"].To<double>();
CloseAt = json["C"].To<double>();
High = json["H"].To<double>();
Low = json["L"].To<double>();
BaseVolume = json["BV"].To<double>();
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace sharp.trading.bittrex
{
public struct JSONMarket
{
public string MarketName { get; private set; }
public string MarketCurrency { get; private set; }
public string BaseCurrency { get; private set; }
public string MarketCurrencyLong { get; private set; }
public string BaseCurrencyLong { get; private set; }
public bool IsActive { get; private set; }
public Double MinTradeSize { get; private set; }
public override string ToString()
{
return string.Format("[JSONMarket: MarketName={0}, MarketCurrency={1}, BaseCurrency={2}, MarketCurrencyLong={3}, BaseCurrencyLong={4}, IsActive={5}, MinTradeSize={6}]", MarketName, MarketCurrency, BaseCurrency, MarketCurrencyLong, BaseCurrencyLong, IsActive, MinTradeSize);
}
}
}

160
cache/HistoryCache.cs vendored 100644
View File

@ -0,0 +1,160 @@
using System;
using System.IO;
using System.Net;
using sharp.json;
using System.Collections.Generic;
using System.Linq;
namespace sharp.trading.cache
{
public class HistoryCache
{
public static string BaseDataDirectory { get { return Path.Combine(TradingEnvironment.DataDirectory, "cache", "history"); } }
public static Dictionary<string, HistoryCache> caches = new Dictionary<string, HistoryCache>();
private static string getCacheName(Market market)
{
return Path.Combine(market.Connection.UniqueProviderName, market.PayingSymbol, market.TradedSymbol);
}
private static string getCacheDir(Market market)
{
return Path.Combine(BaseDataDirectory, getCacheName(market));
}
private static bool createIfNotExists(string directoryName)
{
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
return false;
}
return false;
}
public static HistoryCache getCache(Market market)
{
string cname = getCacheName(market);
if (!caches.ContainsKey(cname))
{
caches[cname] = new HistoryCache(market);
}
return caches[cname];
}
public string CacheName { get; private set; }
public string DataDirectory { get; private set; }
public string HistoricTradesFileName { get; private set; }
//Dictionary<Int64, HistoricTrade> history = new Dictionary<long, HistoricTrade>();
SortedSet<HistoricTrade> history = new SortedSet<HistoricTrade>();
public HistoryCache(Market market){
CacheName = getCacheName(market);
DataDirectory = Path.Combine(BaseDataDirectory, CacheName);
createIfNotExists(DataDirectory);
HistoricTradesFileName = Path.Combine(DataDirectory, "history.json");
load();
// Import Ticks if nothing available
if (this.history.Count == 0){
Tick[] ticks = market.getTicks();
int n = 0;
foreach (Tick tick in ticks){
HistoricTrade ht = new HistoricTrade();
ht.TimeStamp = tick.TimeStamp;
ht.Price = (tick.CloseAt + tick.OpenAt) / 2;
ht.Volume = tick.BaseVolume / ht.Price;
ht.TotalPrice = ht.Price * ht.Volume;
ht.UniqueID = n++;
history.Add(ht);
}
}
}
private void load(){
if (File.Exists(HistoricTradesFileName)){
JSON json = JSON.ReadFrom(HistoricTradesFileName);
foreach (HistoricTrade trade in json.To<HistoricTrade[]>()){
this.history.Add(trade);
}
};
}
private void save(){
lock(this)
{
HistoricTrade[] trades = this.history.ToArray();
JSON json = JSONConverter.From(trades);
json.WriteTo(HistoricTradesFileName, true);
}
}
public void Update(HistoricTrade[] trades){
lock (this)
{
foreach (HistoricTrade trade in trades)
{
if (!history.Contains(trade))
{
history.Add(trade);
}
}
}
save();
}
public WeightedAverage calulateWeightedAveragesPerVolume(double volume)
{
return calulateWeightedAveragesPerVolume(new double[] { volume })[0];
}
public WeightedAverage[] calulateWeightedAveragesPerVolume(double[] volumes)
{
WeightedAverage[] averages = new WeightedAverage[volumes.Length];
for (int n = 0; n<volumes.Length; n++){
averages[n] = new WeightedAverage("", "");
}
lock (this)
{
foreach (HistoricTrade trade in this.history)
{
int used = 0;
for (int n = 0; n < volumes.Length; n++)
{
if (volumes[n] > 0)
{
double v = volumes[n] > trade.Volume ? trade.Volume : volumes[n];
averages[n].TotalPrice += trade.Price * v;
averages[n].Volume += v;
volumes[n] -= v;
used++;
}
}
if (used == 0)
{
break;
}
}
}
return averages;
}
private static void checkDataDirectory(){
if (!Directory.Exists(BaseDataDirectory)){
Directory.CreateDirectory(BaseDataDirectory);
}
}
}
}

41
cache/HistoryService.cs vendored 100644
View File

@ -0,0 +1,41 @@
using System;
using sharp.trading.services;
using sharp.extensions;
using System.Net.Sockets;
namespace sharp.trading.cache
{
public class HistoryService : ITradingService
{
public HistoryService()
{
}
public void RunService()
{
Console.WriteLine("History Service runs");
Pair<string>[] marketPairs = TradingEnvironment.DefaultConnection.listMarkets();
foreach (Pair<string> pair in marketPairs){
try
{
Market market = TradingEnvironment.DefaultConnection.openMarket(pair);
Console.WriteLine("History Update: {0}-{1}",market.TradedSymbol,market.PayingSymbol);
HistoryCache.getCache(market).Update(market.getHistoricTrades());
Console.WriteLine("History Update: {0}-{1} finished.",market.TradedSymbol,market.PayingSymbol);
} catch (Exception e){
Console.WriteLine("History service could not update market data for {0}-{1}",pair.Item1,pair.Item2);
}
}
}
public int SchedulingIntervall()
{
return 120;
}
public void Shutdown()
{
}
}
}

34
cache/WeightedAverage.cs vendored 100644
View File

@ -0,0 +1,34 @@
using System;
namespace sharp.trading.cache
{
public class WeightedAverage
{
public string marketSymbol { get; private set; }
public string baseSymbol { get; private set; }
public double Volume { get; set; }
public double TotalPrice { get; set; }
public double AveragePrice { get { return TotalPrice / Volume; } }
public WeightedAverage(string msym,string bsym,double vol,double total)
{
marketSymbol = msym;
baseSymbol = bsym;
Volume = vol;
TotalPrice = total;
}
public WeightedAverage(string msym, string bsym)
{
marketSymbol = msym;
baseSymbol = bsym;
Volume = 0;
TotalPrice = 0;
}
public override string ToString()
{
return string.Format("[WeightedAverage: Volume={0}, TotalPrice={1}, AveragePrice={2}]", Volume, TotalPrice, AveragePrice);
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Runtime.CompilerServices;
namespace sharp.trading.services
{
public interface ITradingService
{
void RunService();
int SchedulingIntervall();
void Shutdown();
}
}

View File

@ -0,0 +1,182 @@
using System;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Permissions;
using sharp.extensions;
using sharp.json;
using System.IO;
namespace sharp.trading.services
{
public class ServiceManager
{
public static ServiceManager Default { get; private set; } = new ServiceManager();
public static void ReplaceDefault(ServiceManager newDefault){
if (Default != null){
Default.Shutdown();
}
Default = newDefault;
}
Thread serviceThread;
bool shutdown = false;
List<ScheduledService> scheduledServices = new List<ScheduledService>();
public ServiceManager()
{
serviceThread = new Thread(_scheduler);
serviceThread.Start();
Load();
}
public void Shutdown(){
lock (this){
shutdown = true;
}
while (serviceThread.IsAlive){
Thread.Sleep(250);
}
foreach (ScheduledService service in scheduledServices){
while (service.IsRunning){
Thread.Sleep(250);
}
service.TradingService.Shutdown();
}
Save();
}
private void Load(){
if (File.Exists(Path.Combine(TradingEnvironment.DataDirectory, "services.json"))){
JSON json = JSON.ReadFrom(Path.Combine(TradingEnvironment.DataDirectory, "services.json"));
ConstructableObject[] cos = json.To<ConstructableObject[]>();
foreach (ConstructableObject co in cos){
ITradingService its = (ITradingService)co.Construct();
Schedule(its,true);
}
}
}
public void Save(){
List<ConstructableObject> col = new List<ConstructableObject>();
lock (this){
foreach (ScheduledService sservice in scheduledServices){
ConstructableObject co = new ConstructableObject(sservice.TradingService);
col.Add(co);
}
}
JSON json = JSONConverter.From(col.ToArray());
json.WriteTo(Path.Combine(TradingEnvironment.DataDirectory,"services.json"),true);
}
public void Schedule(ITradingService service, bool RunImmediate = false){
lock (this){
ScheduledService sservice = new ScheduledService(service);
if (RunImmediate){
sservice.ScheduledRun = DateTime.UtcNow.Ticks;
}
scheduledServices.Add(sservice);
}
}
public void Unschedule(ITradingService service){
lock (this){
foreach (ScheduledService sserv in scheduledServices.ToArray()){
if (sserv.TradingService == service){
scheduledServices.Remove(sserv);
break;
}
}
}
}
void _scheduler(){
while (true){
try {
Thread.Sleep(1000);
} catch (Exception e){
}
if (shutdown){
break;
}
lock(this){
foreach (ScheduledService sservice in scheduledServices){
if (sservice.HasElapsed){
//ThreadPool.QueueUserWorkItem((state) => sservice.Run());
sservice.Run();
}
}
}
}
}
class ScheduledService {
public Int64 ScheduledRun { get; set; }
public Int64 ScheduledIntervall { get; private set; }
public ITradingService TradingService { get; private set; }
bool isRunning;
public bool IsRunning {
get { lock (this) { return isRunning; } }
private set { lock(this) { this.isRunning = value; } }
}
public ScheduledService(ITradingService tradingService){
this.TradingService = tradingService;
this.ScheduledRun = DateTime.UtcNow.Ticks + (tradingService.SchedulingIntervall() * 10000000);
}
public bool HasElapsed {
get
{
return ScheduledRun <= DateTime.UtcNow.Ticks;
}
}
public void Run(){
lock(this){
if (!IsRunning){
IsRunning = true;
} else {
Console.WriteLine("Service already running, re-scheduling...");
ScheduledRun += ScheduledIntervall;
return;
//throw new Exception("Tried to run ScheduledService while already running");
}
ScheduledIntervall = TradingService.SchedulingIntervall() * 10000000;
ScheduledRun += ScheduledIntervall;
}
try {
TradingService.RunService();
} catch (Exception e){
Console.WriteLine("ScheduledService threw exception: {0}",e.ToString());
}
IsRunning = false;
}
}
}
}

View File

@ -7,7 +7,7 @@
<OutputType>Library</OutputType>
<RootNamespace>sharp.trading</RootNamespace>
<AssemblyName>sharp.trading</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -35,6 +35,46 @@
<Compile Include="OrderBook.cs" />
<Compile Include="TradingConnection.cs" />
<Compile Include="VolumeRate.cs" />
<Compile Include="Market.cs" />
<Compile Include="bittrex\BittrexConnector.cs" />
<Compile Include="bittrex\JSONMarket.cs" />
<Compile Include="bittrex\BittrexMarket.cs" />
<Compile Include="bittrex\BittrexOrderbook.cs" />
<Compile Include="OrderType.cs" />
<Compile Include="OrderTarget.cs" />
<Compile Include="OrderState.cs" />
<Compile Include="bittrex\BittrexOrder.cs" />
<Compile Include="bittrex\BittrexException.cs" />
<Compile Include="Balance.cs" />
<Compile Include="bittrex\BittrexBalance.cs" />
<Compile Include="Currency.cs" />
<Compile Include="bittrex\BittrexCurrency.cs" />
<Compile Include="HistoricTrade.cs" />
<Compile Include="bittrex\BittrexHistoricTrade.cs" />
<Compile Include="TradingEnvironment.cs" />
<Compile Include="cache\HistoryCache.cs" />
<Compile Include="services\ServiceManager.cs" />
<Compile Include="services\ITradingService.cs" />
<Compile Include="cache\HistoryService.cs" />
<Compile Include="cache\WeightedAverage.cs" />
<Compile Include="Tick.cs" />
<Compile Include="bittrex\BittrexTick.cs" />
<Compile Include="OrderbookVolume.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\sharp-extensions\sharp.extensions.csproj">
<Project>{97CA3CA9-98B3-4492-B072-D7A5995B68E9}</Project>
<Name>sharp.extensions</Name>
</ProjectReference>
<ProjectReference Include="..\sharp-json\sharp.json.csproj">
<Project>{D9342117-3249-4D8B-87C9-51A50676B158}</Project>
<Name>sharp.json</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="bittrex\" />
<Folder Include="cache\" />
<Folder Include="services\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>