WIP
parent
72af6108e5
commit
e675cb243a
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
27
Order.cs
27
Order.cs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
106
OrderBook.cs
106
OrderBook.cs
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
namespace sharp.trading
|
||||
{
|
||||
public enum OrderState
|
||||
{
|
||||
INVALID, UNTOUCHED, PARTLYFILLED, FILLED, CANCELED
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
namespace sharp.trading
|
||||
{
|
||||
public enum OrderTarget
|
||||
{
|
||||
UNKNOWN, MARKET, LIMIT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
namespace sharp.trading
|
||||
{
|
||||
public enum OrderType
|
||||
{
|
||||
BUY, SELL, UNKOWN
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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"];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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[]>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace sharp.trading.services
|
||||
{
|
||||
public interface ITradingService
|
||||
{
|
||||
void RunService();
|
||||
int SchedulingIntervall();
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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>
|
Loading…
Reference in New Issue