378 lines
10 KiB
C#
378 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using sharp.trading;
|
|
using System.IO;
|
|
using sharp.json;
|
|
using System.Linq;
|
|
using sharp.extensions;
|
|
using System.Diagnostics;
|
|
using sharp.trading.services;
|
|
using sharp.trading.logging;
|
|
using sharp.json.attributes;
|
|
|
|
namespace sharp.tradebot
|
|
{
|
|
public abstract class TradingBot<T> : TradingBot where T:BasicBotSetup
|
|
{
|
|
public T BasicSetup {
|
|
get { return this.setup.CurrentValue; }
|
|
set { this.setup.CurrentValue = value; }
|
|
}
|
|
|
|
protected override BasicBotSetup BasicBotSetup
|
|
{
|
|
get { return BasicSetup; }
|
|
set { this.BasicSetup = (T)value; }
|
|
}
|
|
|
|
protected FileBackedJSONValue<T> setup;
|
|
|
|
protected override void EnvironmentAssigned(TradingBotEnvironment env){
|
|
this.setup = new FileBackedJSONValue<T>(Path.Combine(BaseDataPath,"botsetup.json"));
|
|
if (this.setup.CurrentValue.IsNull()){
|
|
this.setup.CurrentValue = Activator.CreateInstance<T>();
|
|
}
|
|
}
|
|
|
|
public override void Save()
|
|
{
|
|
setup.Save();
|
|
|
|
base.Save();
|
|
}
|
|
}
|
|
|
|
[JSONClassPolicy( Policy = JSONPolicy.ATTRIBUTED)]
|
|
public abstract class TradingBot : MarshalByRefObject
|
|
{
|
|
public TradingBotEnvironment TradingBotEnvironment { get; private set; }
|
|
public TradeBotLoader TradeBotLoader { get; private set; }
|
|
|
|
public Guid UID { get; private set; }
|
|
public Logger Logger { get; private set; }
|
|
|
|
public string BaseDataPath { get; private set; }
|
|
|
|
List<Order> orders = new List<Order>();
|
|
List<TradeBotBalance> balances = new List<TradeBotBalance>();
|
|
Market botMarket;
|
|
|
|
TextWriter balanceWriter;
|
|
|
|
protected abstract BasicBotSetup BasicBotSetup { get; set; }
|
|
protected abstract void EnvironmentAssigned(TradingBotEnvironment env);
|
|
|
|
[JSONField]
|
|
TradeBotBalance[] __balances {
|
|
get {
|
|
return balances.ToArray();
|
|
}
|
|
set
|
|
{
|
|
this.balances.Clear();
|
|
if (value != null)
|
|
{
|
|
this.balances.AddRange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
[JSONField]
|
|
Order[] __orders
|
|
{
|
|
get {
|
|
return orders.ToArray();
|
|
}
|
|
set
|
|
{
|
|
this.orders.Clear();
|
|
if (value != null)
|
|
{
|
|
this.orders.AddRange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsPrepared { get; private set; }
|
|
|
|
public TradingBot()
|
|
{
|
|
}
|
|
|
|
public Market BotMarket {
|
|
get
|
|
{
|
|
if (this.botMarket.IsNull() && !this.BasicBotSetup.BaseSymbol.Equals("") && !this.BasicBotSetup.BaseSymbol.Equals(""))
|
|
{
|
|
botMarket = TradingBotEnvironment.TradingConnection.openMarket(BasicBotSetup.MarketSymbol, BasicBotSetup.BaseSymbol);
|
|
}
|
|
return this.botMarket;
|
|
}
|
|
}
|
|
|
|
public void AfterLoad(TradingBotEnvironment environment,Guid uid,Logger logger,TradeBotLoader loader){
|
|
this.TradingBotEnvironment = environment;
|
|
this.UID = uid;
|
|
this.Logger = logger;
|
|
this.BaseDataPath = Path.Combine(environment.BaseDataDirectory, "bots", uid.ToString());
|
|
|
|
if (!Directory.Exists(DataDirectory)){
|
|
Directory.CreateDirectory(DataDirectory);
|
|
}
|
|
|
|
EnvironmentAssigned(environment);
|
|
|
|
Log("TradingBot [{0}] loaded to AppDomain [{1}]",GetType().Name,AppDomain.CurrentDomain.FriendlyName);
|
|
}
|
|
|
|
public virtual void Prepare()
|
|
{
|
|
|
|
//if (File.Exists(Path.Combine(DataDirectory,"orders.json"))){
|
|
// JSON jsonorders = JSON.ReadFrom(Path.Combine(DataDirectory,"orders.json"));
|
|
// string[] orderids = jsonorders.To<string[]>();
|
|
// foreach (string oid in orderids){
|
|
// this.orders.Add(TradingEnvironment.DefaultConnection.getOrder(oid));
|
|
// }
|
|
//}
|
|
|
|
//if (File.Exists(Path.Combine(DataDirectory,"balances.json"))){
|
|
// JSON jsonbalances = JSON.ReadFrom(Path.Combine(DataDirectory,"balances.json"));
|
|
// this.balances.Clear();
|
|
// this.balances.AddRange( jsonbalances.To<TradeBotBalance[]>());
|
|
//}
|
|
|
|
if (File.Exists(Path.Combine(DataDirectory,"botsetup.json"))){
|
|
JSON jbasicsetup = JSON.ReadFrom(Path.Combine(DataDirectory, "botsetup.json"));
|
|
this.BasicBotSetup = (BasicBotSetup)JSONConverter.To(BasicBotSetup.GetType(),jbasicsetup);
|
|
}
|
|
|
|
JSON jstate = JSON.ReadFrom(Path.Combine(BaseDataPath, "state.json"));
|
|
if (jstate != null){
|
|
JSONConverter.ApplyObject(jstate, this);
|
|
}
|
|
balanceWriter = new StreamWriter(new FileStream(Path.Combine(BaseDataPath, "balance.log"),FileMode.Append));
|
|
|
|
this.TradingBotEnvironment.RegisterPeriodic(Balancing,10);
|
|
|
|
IsPrepared = true;
|
|
}
|
|
|
|
public virtual void Unprepare()
|
|
{
|
|
IsPrepared = false;
|
|
this.TradingBotEnvironment.UnregisterPeriodic(Balancing);
|
|
balanceWriter.Close();
|
|
|
|
Save();
|
|
}
|
|
|
|
public virtual void Save(){
|
|
JSON.From(this).WriteTo(Path.Combine(BaseDataPath, "state.json"),true);
|
|
}
|
|
|
|
public Order[] Orders {
|
|
get { return this.orders.ToArray(); }
|
|
}
|
|
|
|
public Order getOrder(string orderID){
|
|
foreach (Order o in this.orders){
|
|
if (o.OrderID.Equals(orderID)){
|
|
return o;
|
|
}
|
|
}
|
|
|
|
Order order = TradingEnvironment.DefaultConnection.getOrder(orderID);
|
|
return order;
|
|
}
|
|
|
|
public void cancelOrder(Order order){
|
|
TradingEnvironment.DefaultConnection.cancelOrder(order);
|
|
}
|
|
|
|
public void cancelOrder(string orderID)
|
|
{
|
|
cancelOrder(getOrder(orderID));
|
|
}
|
|
|
|
public Order createLimitOrder(OrderType orderType,string marketCurrency,string baseCurrency,double quantity,double limit){
|
|
|
|
TradeBotBalance bBase = getBalance(baseCurrency);
|
|
TradeBotBalance bMarket = getBalance(marketCurrency);
|
|
|
|
Market orderMarket = TradingEnvironment.DefaultConnection.openMarket(marketCurrency, baseCurrency);
|
|
|
|
if (orderMarket.MinimumTradeVolume > quantity){
|
|
Log("Refusing to create order below minimum trading volume for market! Volume: {0:#0.000000} {1} < {2:#0.000000} {1}", quantity, marketCurrency, orderMarket.MinimumTradeVolume);
|
|
return null;
|
|
}
|
|
|
|
if (orderType == OrderType.BUY){
|
|
if (bBase.CurrentBalance < (limit * quantity)){
|
|
Log("Refusing order to buy {0} @{1} with balance of {2}", quantity, limit, bBase.CurrentBalance);
|
|
return null;
|
|
}
|
|
} else {
|
|
if (bMarket.CurrentBalance < (quantity)){
|
|
Log("Refusing order to sell {0} @{1} with balance of {2}", quantity, limit, bMarket.CurrentBalance);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Log("createLimitOrder({0},{1},{2},{3},{4})", orderType, marketCurrency, baseCurrency, quantity, limit);
|
|
|
|
try
|
|
{
|
|
Order o = TradingEnvironment.DefaultConnection.createOrder(orderType, OrderTarget.LIMIT, marketCurrency, baseCurrency, quantity, limit);
|
|
this.orders.Add(o);
|
|
return o;
|
|
|
|
} catch (Exception e){
|
|
Log("createLimitOrder(): threw Exception: {0}", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void RemoveOrder(Order order){
|
|
this.orders.Remove(order);
|
|
}
|
|
|
|
public VolumeRate getVolumeOnOrders(){
|
|
VolumeRate vrate = new VolumeRate();
|
|
|
|
foreach (Order order in this.orders){
|
|
if (order.OrderType == OrderType.BUY){
|
|
vrate.Volume += order.OrderVolume;
|
|
vrate.Price += order.LimitPrice * order.OrderVolume;
|
|
} else {
|
|
vrate.Volume -= order.OrderVolume;
|
|
vrate.Price -= order.LimitPrice * order.OrderVolume;
|
|
}
|
|
}
|
|
|
|
vrate.Price /= vrate.Volume;
|
|
|
|
return vrate;
|
|
}
|
|
|
|
/* Balances */
|
|
|
|
public TradeBotBalance getBalance(string symbol){
|
|
if (symbol.IsNull() || symbol.Equals(""))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (TradeBotBalance b in balances){
|
|
if (b.Currency.Equals(symbol)){
|
|
return b;
|
|
}
|
|
}
|
|
|
|
TradeBotBalance balance = new TradeBotBalance(symbol);
|
|
balances.Add(balance);
|
|
|
|
return balance;
|
|
}
|
|
|
|
|
|
public T loadJSON<T>(string filename){
|
|
if (File.Exists(Path.Combine(DataDirectory, filename)))
|
|
{
|
|
return JSON.ReadFrom(Path.Combine(DataDirectory, filename)).To<T>();
|
|
}
|
|
|
|
if (typeof(T).IsArray){
|
|
return (T)(object)Array.CreateInstance(typeof(T).GetElementType(), 0);
|
|
}
|
|
|
|
return Activator.CreateInstance<T>();
|
|
}
|
|
|
|
public void saveJSON(object o, string filename){
|
|
JSONConverter.From(o).WriteTo(Path.Combine(DataDirectory, filename),true);
|
|
}
|
|
|
|
|
|
public string DataDirectory {
|
|
get { return BaseDataPath; }
|
|
}
|
|
|
|
public void Log(string format,params object[] args){
|
|
Logger.Log(format,args);
|
|
}
|
|
|
|
public void DumpBalances(){
|
|
foreach (TradeBotBalance balance in balances){
|
|
Log(balance.ToString());
|
|
}
|
|
}
|
|
|
|
|
|
public virtual int SchedulingIntervall()
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
public virtual void Balancing(){
|
|
|
|
if (BasicBotSetup.Enabled)
|
|
{
|
|
TradeBotBalance bMarket = getBalance(BasicBotSetup.MarketSymbol);
|
|
TradeBotBalance bBase = getBalance(BasicBotSetup.BaseSymbol);
|
|
bool charged = false;
|
|
|
|
if (BasicBotSetup.ChargeBaseBalance != 0){
|
|
bBase.CurrentBalance += BasicBotSetup.ChargeBaseBalance;
|
|
charged = true;
|
|
}
|
|
if (BasicBotSetup.ChargeMarketBalance != 0){
|
|
bMarket.CurrentBalance += BasicBotSetup.ChargeMarketBalance;
|
|
charged = true;
|
|
}
|
|
|
|
if (charged){
|
|
Log("Charged balances: {0,11:####0.00000000} {1} / {2,11:####0.00000000} {3}", BasicBotSetup.ChargeBaseBalance, BasicBotSetup.BaseSymbol, BasicBotSetup.ChargeMarketBalance, BasicBotSetup.MarketSymbol);
|
|
balanceWriter.WriteLine("{0}\t{1}\t{2}\t{3,12:0.00000000}\t{4,12:0.00000000}\t{5,12:0.00000000}\t{6,12:0.00000000}\t{7,12:0.00000000}", DateTime.Now, "-", "CHARGE", BasicBotSetup.ChargeMarketBalance,BasicBotSetup.ChargeBaseBalance,0,bBase.CurrentBalance,bMarket.CurrentBalance);
|
|
balanceWriter.Flush();
|
|
|
|
BasicBotSetup.ChargeMarketBalance = 0;
|
|
BasicBotSetup.ChargeBaseBalance = 0;
|
|
}
|
|
|
|
foreach (Order order in this.__orders)
|
|
{
|
|
TradingBotEnvironment.TradingConnection.refreshOrder(order);
|
|
if (!order.IsOpen)
|
|
{
|
|
bBase.CurrentBalance -= order.PayedFees;
|
|
if (order.OrderType == OrderType.BUY){
|
|
bBase.CurrentBalance -= order.PayedPrice;
|
|
bMarket.CurrentBalance += order.FilledVolume;
|
|
} else {
|
|
bBase.CurrentBalance += order.PayedPrice;
|
|
bMarket.CurrentBalance -= order.FilledVolume;
|
|
}
|
|
this.RemoveOrder(order);
|
|
|
|
Log("Order has been closed: {5} {0,11:####0.00000000} {1} / {2,11:####0.00000000} {3} [ FEE: {4,11:####0.00000000} {3} ]",
|
|
order.FilledVolume,
|
|
BasicBotSetup.MarketSymbol,
|
|
order.PayedPrice,
|
|
BasicBotSetup.BaseSymbol,
|
|
order.PayedFees,
|
|
order.OrderType
|
|
);
|
|
|
|
balanceWriter.WriteLine("{0}\t{1}\t{2}\t{3:#0.00000000}\t{4:#0.00000000}\t{5:#0.00000000}\t{6:#0.00000000}\t{7:#0.00000000}", DateTime.Now, order.OrderID, order.OrderType, order.FilledVolume,order.PayedPrice,order.PayedFees,bBase.CurrentBalance,bMarket.CurrentBalance);
|
|
balanceWriter.Flush();
|
|
}
|
|
}
|
|
|
|
Save();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|