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 System;
|
||||||
|
using sharp.json.attributes;
|
||||||
namespace sharp.trading
|
namespace sharp.trading
|
||||||
{
|
{
|
||||||
public class Order
|
[JSONClassPolicy( Policy = JSONPolicy.PUBLIC)]
|
||||||
|
public class Order : MarshalByRefObject
|
||||||
{
|
{
|
||||||
public Double OrderVolume { get; set; }
|
public OrderType OrderType { get; set; }
|
||||||
public Double FilledVolume { get; set; }
|
public OrderTarget OrderTarget { get; set; }
|
||||||
public Double LimitPrice { 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;
|
||||||
|
using System.ComponentModel;
|
||||||
namespace sharp.trading
|
namespace sharp.trading
|
||||||
{
|
{
|
||||||
public class OrderBook
|
public abstract class OrderBook : MarshalByRefObject
|
||||||
{
|
{
|
||||||
public VolumeRate[] Bids { get; set; }
|
public VolumeRate[] Bids { get; protected set; }
|
||||||
public VolumeRate[] Asks { get; 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 System;
|
||||||
|
using sharp.extensions;
|
||||||
|
using System.CodeDom;
|
||||||
|
using System.Collections.Generic;
|
||||||
namespace sharp.trading
|
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 System;
|
||||||
|
using sharp.json;
|
||||||
|
using sharp.json.attributes;
|
||||||
namespace sharp.trading
|
namespace sharp.trading
|
||||||
{
|
{
|
||||||
public struct VolumeRate
|
[JSONClassPolicy(Policy = JSONPolicy.ATTRIBUTED)]
|
||||||
|
public struct VolumeRate : IComparable
|
||||||
{
|
{
|
||||||
|
[JSONField(Alias = "Quantity")]
|
||||||
public Double Volume { get; set; }
|
public Double Volume { get; set; }
|
||||||
|
[JSONField(Alias = "Rate")]
|
||||||
public Double Price { get; set; }
|
public Double Price { get; set; }
|
||||||
|
|
||||||
public VolumeRate(Double Volume,Double Price)
|
public VolumeRate(Double Volume,Double Price)
|
||||||
|
@ -12,5 +17,13 @@ namespace sharp.trading
|
||||||
this.Price = Price;
|
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>
|
<OutputType>Library</OutputType>
|
||||||
<RootNamespace>sharp.trading</RootNamespace>
|
<RootNamespace>sharp.trading</RootNamespace>
|
||||||
<AssemblyName>sharp.trading</AssemblyName>
|
<AssemblyName>sharp.trading</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
@ -35,6 +35,46 @@
|
||||||
<Compile Include="OrderBook.cs" />
|
<Compile Include="OrderBook.cs" />
|
||||||
<Compile Include="TradingConnection.cs" />
|
<Compile Include="TradingConnection.cs" />
|
||||||
<Compile Include="VolumeRate.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>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue