Initial Commit

master
Harald Wolff 2017-11-23 13:02:43 +01:00
commit 1a758df642
10 changed files with 838 additions and 0 deletions

40
.gitignore vendored 100644
View File

@ -0,0 +1,40 @@
# Autosave files
*~
# build
[Oo]bj/
[Bb]in/
packages/
TestResults/
# globs
Makefile.in
*.DS_Store
*.sln.cache
*.suo
*.cache
*.pidb
*.userprefs
*.usertasks
config.log
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.user
*.tar.gz
tarballs/
test-results/
Thumbs.db
# Mac bundle stuff
*.dmg
*.app
# resharper
*_Resharper.*
*.Resharper
# dotCover
*.dotCover

14
BasicBotSetup.cs 100644
View File

@ -0,0 +1,14 @@
using System;
namespace sharp.tradebot
{
public class BasicBotSetup
{
public bool Enabled;
public string BaseSymbol = "";
public string MarketSymbol = "";
public double ChargeBaseBalance;
public double ChargeMarketBalance;
}
}

View File

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("sharp.tradebot")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

25
TradeBotBalance.cs 100644
View File

@ -0,0 +1,25 @@
using System;
namespace sharp.tradebot
{
public class TradeBotBalance
{
public readonly string Currency;
public double CurrentBalance;
public TradeBotBalance()
{
}
public TradeBotBalance(string currency)
{
this.Currency = currency;
}
public override string ToString()
{
return string.Format("[TradeBotBalance: Currency={0,5}, CurrentBalance={1,12:#####0.00000000}]", Currency, CurrentBalance);
}
}
}

125
TradeBotLoader.cs 100644
View File

@ -0,0 +1,125 @@
using System;
using System.Reflection;
using sharp.trading.logging;
using System.IO;
using System.Collections.Generic;
namespace sharp.tradebot
{
public class TradeBotLoader : MarshalByRefObject
{
public String DllPath { get; set; }
public String ClassName { get; set; }
public Guid UID { get; set; }
private AppDomain _appDomain;
private TradingBot _instance;
TradingBotEnvironment environment;
Dictionary<Guid, pworker> pWorkers;
struct pworker {
public PeriodicWorker worker;
public pworker(PeriodicWorker worker){
this.worker = worker;
}
public void call(){
worker();
}
}
public TradeBotLoader()
{
pWorkers = new Dictionary<Guid, pworker>();
}
public void RegisterPeriodic(PeriodicWorker worker, int timeout)
{
environment.RegisterPeriodic(worker,timeout);
}
public void UnregisterPeriodic(PeriodicWorker worker)
{
environment.UnregisterPeriodic(worker);
}
/*
public void RegisterPeriodic(PeriodicWorker worker, int timeout) {
Guid guid = Guid.NewGuid();
pworker pw = new pworker(worker);
pWorkers.Add(guid, pw );
environment.RegisterPeriodic( pWorkers[guid].call, timeout );
}
public void UnregisterPeriodic(PeriodicWorker worker){
foreach (Guid guid in pWorkers.Keys){
if (pWorkers[guid].worker == worker){
environment.UnregisterPeriodic(pWorkers[guid].call);
pWorkers.Remove(guid);
break;
}
}
}
*/
public void Load(TradingBotEnvironment environment)
{
this.environment = environment;
AppDomainSetup ads = new AppDomainSetup();
// ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
this._appDomain = AppDomain.CreateDomain(String.Format("{0}:{1}:{2}:{3}", DllPath, ClassName, UID, Guid.NewGuid().ToString()), AppDomain.CurrentDomain.Evidence, ads);
/*
Assembly assembly = this._appDomain.Load(File.ReadAllBytes(DllPath));
Type type = assembly.GetType(ClassName);
this._instance = (TradingBot)Activator.CreateInstance(type);
*/
this._instance = (TradingBot)this._appDomain.CreateInstanceFromAndUnwrap(DllPath, ClassName);
this._instance.AfterLoad(environment, UID, new FileLogger(Path.Combine(environment.BaseDataDirectory, "logs", String.Format("{0}.log", UID.ToString()))),this);
}
public void Unload()
{
if (this._instance != null)
{
lock (this._instance)
{
if (this._instance.IsPrepared)
{
this._instance.Unprepare();
}
this._instance.Logger.Close();
}
this._instance = null;
AppDomain.Unload(this._appDomain);
this._appDomain = null;
}
}
public void Prepare()
{
if (this._instance != null)
{
lock (this._instance)
{
this._instance.Prepare();
}
}
}
public void Unprepare()
{
if (this._instance != null)
{
lock (this._instance)
{
this._instance.Unprepare();
}
}
}
}
}

377
TradingBot.cs 100644
View File

@ -0,0 +1,377 @@
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();
}
}
}
}

View File

@ -0,0 +1,115 @@
using System;
using sharp.trading;
using System.IO;
using System.Timers;
using System.Threading;
using System.Collections.Generic;
namespace sharp.tradebot
{
public delegate void PeriodicWorker();
public class TradingBotEnvironment : MarshalByRefObject
{
public string BaseDataDirectory { get; private set; }
public TradingConnection TradingConnection { get; private set; }
Thread periodicsThread;
bool contPeriodics = true;
List<PeriodicTask> periodicTasks = new List<PeriodicTask>();
public TradingBotEnvironment(TradingConnection connection,string baseDataDirectory){
BaseDataDirectory = baseDataDirectory;
TradingConnection = connection;
periodicsThread = new Thread(periodics);
periodicsThread.Start();
}
public void RegisterPeriodic(PeriodicWorker worker,int timeout){
lock (periodicTasks){
periodicTasks.Add(new PeriodicTask(timeout,worker));
}
}
public void UnregisterPeriodic(PeriodicWorker worker)
{
lock (periodicTasks)
{
foreach (PeriodicTask ptask in periodicTasks.ToArray()){
if (ptask.worker == worker){
periodicTasks.Remove(ptask);
}
}
}
}
public void Stop(){
lock (this){
this.contPeriodics = false;
}
this.periodicsThread.Join();
}
private void periodics(){
Thread.Sleep(1000);
while (true){
lock (this){
if (!contPeriodics)
{
break;
}
}
lock (periodicTasks){
foreach (PeriodicTask ptask in periodicTasks){
ptask.Check();
}
}
Thread.Sleep(250);
}
}
class PeriodicTask {
public DateTime lastRun;
public Int64 interval;
public PeriodicWorker worker;
public PeriodicTask(Int64 interval,PeriodicWorker worker){
this.worker = worker;
this.interval = interval;
this.lastRun = DateTime.Now;
}
public void Check()
{
if ((this.lastRun == null) || ((DateTime.Now - this.lastRun).TotalSeconds >= this.interval))
{
this.lastRun = DateTime.Now;
try
{
lock (this.worker.Target){
if (!typeof(TradingBot).IsInstanceOfType(this.worker.Target) || ((TradingBot)this.worker.Target).IsPrepared){
try {
this.worker();
} catch (Exception e){
Console.WriteLine("Exception: {0}",e);
}
}
}
} catch (Exception e){
Console.WriteLine("Exception: {0}",e.ToString());
}
}
}
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.IO;
namespace sharp.trading.logging
{
public class FileLogger : Logger
{
TextWriter writer;
public FileLogger(string filename)
{
this.writer = new StreamWriter(new FileStream(filename, FileMode.Append));
}
protected override void log(string line)
{
this.writer.WriteLine(line);
this.writer.Flush();
}
public override void Close()
{
this.writer.Close();
base.Close();
}
}
}

30
logging/Logger.cs 100644
View File

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
namespace sharp.trading.logging
{
public abstract class Logger : MarshalByRefObject
{
protected abstract void log(string line);
public void Log(string format, params object[] args)
{
string preformat = string.Format(format, args);
string postformat = string.Format("{0} {1} {2}", DateTime.Now.ToLocalTime(), Process.GetCurrentProcess().Id, preformat);
lock (this)
{
log(postformat);
}
}
public void Log(Exception e){
Log("An Exception occured: {0} in {1}", e.ToString(),e.StackTrace);
}
public virtual void Close(){
}
}
}

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{798D4516-84F8-436D-BD7F-17AD288C6776}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>sharp.tradebot</RootNamespace>
<AssemblyName>sharp.tradebot</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TradingBot.cs" />
<Compile Include="TradingBotEnvironment.cs" />
<Compile Include="TradeBotBalance.cs" />
<Compile Include="logging\FileLogger.cs" />
<Compile Include="logging\Logger.cs" />
<Compile Include="TradeBotLoader.cs" />
<Compile Include="BasicBotSetup.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\sharp-trading\sharp.trading.csproj">
<Project>{CAAC53CC-671C-4B1E-8403-1E53D1D40D66}</Project>
<Name>sharp.trading</Name>
</ProjectReference>
<ProjectReference Include="..\sharp-json\sharp.json.csproj">
<Project>{D9342117-3249-4D8B-87C9-51A50676B158}</Project>
<Name>sharp.json</Name>
</ProjectReference>
<ProjectReference Include="..\sharp-extensions\sharp.extensions.csproj">
<Project>{97CA3CA9-98B3-4492-B072-D7A5995B68E9}</Project>
<Name>sharp.extensions</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="logging\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>