@@ -10,4 +10,4 @@ docker build -t managing.api -f Managing.Api/Dockerfile . --no-cache
|
|||||||
docker build -t managing.api.workers -f Managing.Api.Workers/Dockerfile . --no-cache
|
docker build -t managing.api.workers -f Managing.Api.Workers/Dockerfile . --no-cache
|
||||||
|
|
||||||
# Start up the project using docker-compose
|
# Start up the project using docker-compose
|
||||||
docker-compose -f Managing.Docker/docker-compose.yml -f Managing.Docker/docker-compose.sandbox.yml up -d
|
docker compose -f Managing.Docker/docker-compose.yml -f Managing.Docker/docker-compose.local.yml up -d
|
||||||
@@ -198,7 +198,7 @@ public class BotController : ControllerBase
|
|||||||
{
|
{
|
||||||
Status = item.GetStatus(),
|
Status = item.GetStatus(),
|
||||||
Name = item.GetName(),
|
Name = item.GetName(),
|
||||||
Candles = item.Candles.ToList(),
|
Candles = item.OptimizedCandles.ToList(),
|
||||||
Positions = item.Positions,
|
Positions = item.Positions,
|
||||||
Signals = item.Signals.ToList(),
|
Signals = item.Signals.ToList(),
|
||||||
WinRate = item.GetWinRate(),
|
WinRate = item.GetWinRate(),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
rsiStrategy.Candles.Add(candle);
|
rsiStrategy.Candles.Enqueue(candle);
|
||||||
var signals = rsiStrategy.Run();
|
var signals = rsiStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
rsiStrategy.Candles.Add(candle);
|
rsiStrategy.Candles.Enqueue(candle);
|
||||||
var signals = rsiStrategy.Run();
|
var signals = rsiStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
rsiStrategy.Candles.Add(candle);
|
rsiStrategy.Candles.Enqueue(candle);
|
||||||
var signals = rsiStrategy.Run();
|
var signals = rsiStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
superTrendStrategy.Candles.Add(candle);
|
superTrendStrategy.Candles.Enqueue(candle);
|
||||||
var signals = superTrendStrategy.Run();
|
var signals = superTrendStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
chandelierExitStrategy.Candles.Add(candle);
|
chandelierExitStrategy.Candles.Enqueue(candle);
|
||||||
var signals = chandelierExitStrategy.Run();
|
var signals = chandelierExitStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
emaTrendSrategy.Candles.Add(candle);
|
emaTrendSrategy.Candles.Enqueue(candle);
|
||||||
var signals = emaTrendSrategy.Run();
|
var signals = emaTrendSrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
stochRsiStrategy.Candles.Add(candle);
|
stochRsiStrategy.Candles.Enqueue(candle);
|
||||||
var signals = stochRsiStrategy.Run();
|
var signals = stochRsiStrategy.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Core.FixedSizedQueue;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
@@ -11,6 +12,7 @@ namespace Managing.Application.Abstractions
|
|||||||
{
|
{
|
||||||
HashSet<Signal> Signals { get; set; }
|
HashSet<Signal> Signals { get; set; }
|
||||||
List<Position> Positions { get; set; }
|
List<Position> Positions { get; set; }
|
||||||
|
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||||
HashSet<Candle> Candles { get; set; }
|
HashSet<Candle> Candles { get; set; }
|
||||||
Timeframe Timeframe { get; set; }
|
Timeframe Timeframe { get; set; }
|
||||||
HashSet<IStrategy> Strategies { get; set; }
|
HashSet<IStrategy> Strategies { get; set; }
|
||||||
|
|||||||
@@ -142,10 +142,12 @@ namespace Managing.Application.Backtesting
|
|||||||
bot.WalletBalances.Add(candles.FirstOrDefault().Date, balance);
|
bot.WalletBalances.Add(candles.FirstOrDefault().Date, balance);
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
bot.Candles.Add(candle);
|
bot.OptimizedCandles.Enqueue(candle);
|
||||||
bot.Run();
|
bot.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bot.Candles = new HashSet<Candle>(candles);
|
||||||
|
|
||||||
var finalPnl = bot.GetProfitAndLoss();
|
var finalPnl = bot.GetProfitAndLoss();
|
||||||
var winRate = bot.GetWinRate();
|
var winRate = bot.GetWinRate();
|
||||||
var optimizedMoneyManagement = TradingBox.GetBestMoneyManagement(candles, bot.Positions, moneyManagement);
|
var optimizedMoneyManagement = TradingBox.GetBestMoneyManagement(candles, bot.Positions, moneyManagement);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Trading;
|
using Managing.Application.Trading;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
|
using Managing.Core.FixedSizedQueue;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
@@ -27,6 +28,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
public Account Account { get; set; }
|
public Account Account { get; set; }
|
||||||
public HashSet<IStrategy> Strategies { get; set; }
|
public HashSet<IStrategy> Strategies { get; set; }
|
||||||
|
public FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||||
public HashSet<Candle> Candles { get; set; }
|
public HashSet<Candle> Candles { get; set; }
|
||||||
public HashSet<Signal> Signals { get; set; }
|
public HashSet<Signal> Signals { get; set; }
|
||||||
public List<Position> Positions { get; set; }
|
public List<Position> Positions { get; set; }
|
||||||
@@ -80,6 +82,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
Strategies = new HashSet<IStrategy>();
|
Strategies = new HashSet<IStrategy>();
|
||||||
Signals = new HashSet<Signal>();
|
Signals = new HashSet<Signal>();
|
||||||
|
OptimizedCandles = new FixedSizeQueue<Candle>(600);
|
||||||
Candles = new HashSet<Candle>();
|
Candles = new HashSet<Candle>();
|
||||||
Positions = new List<Position>();
|
Positions = new List<Position>();
|
||||||
WalletBalances = new Dictionary<DateTime, decimal>();
|
WalletBalances = new Dictionary<DateTime, decimal>();
|
||||||
@@ -160,13 +163,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
Logger.LogInformation(
|
Logger.LogInformation(
|
||||||
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
||||||
|
|
||||||
var previousCandleCount = Candles.Count;
|
var previousCandleCount = OptimizedCandles.Count;
|
||||||
|
|
||||||
if (!IsForBacktest)
|
if (!IsForBacktest)
|
||||||
await UpdateCandles();
|
await UpdateCandles();
|
||||||
|
|
||||||
if (Candles.Count > previousCandleCount || IsForBacktest)
|
if (OptimizedCandles.Count > previousCandleCount || IsForBacktest)
|
||||||
await UpdateSignals(Candles);
|
await UpdateSignals(OptimizedCandles);
|
||||||
else
|
else
|
||||||
Logger.LogInformation($"No need to update signals for {Ticker}");
|
Logger.LogInformation($"No need to update signals for {Ticker}");
|
||||||
|
|
||||||
@@ -177,35 +180,38 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
SaveBackup();
|
SaveBackup();
|
||||||
|
|
||||||
await UpdateWalletBalances();
|
await UpdateWalletBalances();
|
||||||
Logger.LogInformation($"Candles : {Candles.Count}");
|
if (OptimizedCandles.Count % 100 == 0) // Log every 10th execution
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"Candle date : {OptimizedCandles.Last().Date:u}");
|
||||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
Logger.LogInformation($"Signals : {Signals.Count}");
|
||||||
Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
|
Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
|
||||||
Logger.LogInformation($"Positions : {Positions.Count}");
|
Logger.LogInformation($"Positions : {Positions.Count}");
|
||||||
Logger.LogInformation("__________________________________________________");
|
Logger.LogInformation("__________________________________________________");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task PreloadCandles()
|
private async Task PreloadCandles()
|
||||||
{
|
{
|
||||||
if (Candles.Any())
|
if (OptimizedCandles.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
||||||
|
|
||||||
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||||
{
|
{
|
||||||
if (!Candles.Any(c => c.Date == candle.Date))
|
if (!OptimizedCandles.Any(c => c.Date == candle.Date))
|
||||||
{
|
{
|
||||||
Candles.Add(candle);
|
OptimizedCandles.Enqueue(candle);
|
||||||
await UpdateSignals(Candles);
|
await UpdateSignals(OptimizedCandles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PreloadedCandlesCount = Candles.Count();
|
PreloadedCandlesCount = OptimizedCandles.Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSignals(HashSet<Candle> candles)
|
private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
|
||||||
{
|
{
|
||||||
var signal = TradingBox.GetSignal(candles, Strategies, Signals);
|
var signal = TradingBox.GetSignal(candles.ToHashSet(), Strategies, Signals);
|
||||||
|
|
||||||
if (signal == null) return;
|
if (signal == null) return;
|
||||||
|
|
||||||
@@ -236,15 +242,15 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
protected async Task UpdateCandles()
|
protected async Task UpdateCandles()
|
||||||
{
|
{
|
||||||
if (Candles.Count == 0 || ExecutionCount == 0)
|
if (OptimizedCandles.Count == 0 || ExecutionCount == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var lastCandle = Candles.Last();
|
var lastCandle = OptimizedCandles.Last();
|
||||||
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
|
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
|
||||||
|
|
||||||
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||||
{
|
{
|
||||||
Candles.Add(candle);
|
OptimizedCandles.Enqueue(candle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,17 +275,25 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private async Task UpdateWalletBalances()
|
private async Task UpdateWalletBalances()
|
||||||
{
|
{
|
||||||
|
var lastCandle = OptimizedCandles.LastOrDefault();
|
||||||
|
if (lastCandle == null) return;
|
||||||
|
|
||||||
|
var date = lastCandle.Date;
|
||||||
|
|
||||||
if (WalletBalances.Count == 0)
|
if (WalletBalances.Count == 0)
|
||||||
{
|
{
|
||||||
WalletBalances.Add(Candles.LastOrDefault().Date, await ExchangeService.GetBalance(Account, IsForBacktest));
|
WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (!WalletBalances.Any(w => w.Key == Candles.LastOrDefault().Date))
|
|
||||||
|
if (!WalletBalances.ContainsKey(date))
|
||||||
{
|
{
|
||||||
var walletBalance = WalletBalances.FirstOrDefault().Value + GetProfitAndLoss();
|
var previousBalance = WalletBalances.First().Value;
|
||||||
WalletBalances.Add(Candles.LastOrDefault().Date, walletBalance);
|
WalletBalances[date] = previousBalance + GetProfitAndLoss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task UpdatePosition(Signal signal, Position positionForSignal)
|
private async Task UpdatePosition(Signal signal, Position positionForSignal)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -293,7 +307,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
||||||
{
|
{
|
||||||
await HandleClosedPosition(positionForSignal);
|
await HandleClosedPosition(positionForSignal);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (position.Status == PositionStatus.Filled)
|
else if (position.Status == PositionStatus.Filled)
|
||||||
{
|
{
|
||||||
@@ -301,7 +314,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// check if position is still open
|
// check if position is still open
|
||||||
// Check status, if still open update the status of the position
|
// Check status, if still open update the status of the position
|
||||||
var lastCandle = IsForBacktest
|
var lastCandle = IsForBacktest
|
||||||
? Candles.Last()
|
? OptimizedCandles.Last()
|
||||||
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
|
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
||||||
@@ -401,7 +414,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
&& p.SignalIdentifier != signal.Identifier);
|
&& p.SignalIdentifier != signal.Identifier);
|
||||||
|
|
||||||
var lastPrice = IsForBacktest
|
var lastPrice = IsForBacktest
|
||||||
? Candles.Last().Close
|
? OptimizedCandles.Last().Close
|
||||||
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
// If position open
|
// If position open
|
||||||
@@ -511,7 +524,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
if (lastPosition == null)
|
if (lastPosition == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var tenCandleAgo = Candles.TakeLast(10).First();
|
var tenCandleAgo = OptimizedCandles.TakeLast(10).First();
|
||||||
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
|
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
|
||||||
|
|
||||||
return positionSignal.Date < tenCandleAgo.Date;
|
return positionSignal.Date < tenCandleAgo.Date;
|
||||||
@@ -543,8 +556,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var closedPosition =
|
var closedPosition =
|
||||||
await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
|
(new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||||
.Handle(command);
|
.Handle(command)).Result;
|
||||||
|
|
||||||
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
||||||
{
|
{
|
||||||
|
|||||||
13
src/Managing.Core/FixedSizedQueue/FixedSizeQueue.cs
Normal file
13
src/Managing.Core/FixedSizedQueue/FixedSizeQueue.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Managing.Core.FixedSizedQueue;
|
||||||
|
|
||||||
|
public class FixedSizeQueue<T> : Queue<T>
|
||||||
|
{
|
||||||
|
private readonly int _maxSize;
|
||||||
|
public FixedSizeQueue(int maxSize) => _maxSize = maxSize;
|
||||||
|
|
||||||
|
public new void Enqueue(T item)
|
||||||
|
{
|
||||||
|
while (Count >= _maxSize) Dequeue();
|
||||||
|
base.Enqueue(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,10 @@ public static class TradingBox
|
|||||||
HashSet<Signal> previousSignal)
|
HashSet<Signal> previousSignal)
|
||||||
{
|
{
|
||||||
var signalOnCandles = new HashSet<Signal>();
|
var signalOnCandles = new HashSet<Signal>();
|
||||||
|
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
|
||||||
foreach (var strategy in strategies)
|
foreach (var strategy in strategies)
|
||||||
{
|
{
|
||||||
strategy.UpdateCandles(newCandles);
|
strategy.UpdateCandles(limitedCandles.ToHashSet());
|
||||||
var signals = strategy.Run();
|
var signals = strategy.Run();
|
||||||
|
|
||||||
if (signals == null || signals.Count == 0) continue;
|
if (signals == null || signals.Count == 0) continue;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Managing.Domain.Candles;
|
using Managing.Core.FixedSizedQueue;
|
||||||
using Managing.Core;
|
using Managing.Domain.Candles;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Strategies
|
namespace Managing.Domain.Strategies
|
||||||
{
|
{
|
||||||
@@ -11,14 +11,14 @@ namespace Managing.Domain.Strategies
|
|||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Timeframe = timeframe;
|
Timeframe = timeframe;
|
||||||
Candles = new List<Candle>();
|
Candles = new FixedSizeQueue<Candle>(600);
|
||||||
Type = type;
|
Type = type;
|
||||||
SignalType = ScenarioHelpers.GetSignalType(type);
|
SignalType = ScenarioHelpers.GetSignalType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Timeframe Timeframe { get; set; }
|
public Timeframe Timeframe { get; set; }
|
||||||
public List<Candle> Candles { get; set; }
|
public FixedSizeQueue<Candle> Candles { get; set; }
|
||||||
public StrategyType Type { get; set; }
|
public StrategyType Type { get; set; }
|
||||||
public SignalType SignalType { get; set; }
|
public SignalType SignalType { get; set; }
|
||||||
public int MinimumHistory { get; set; }
|
public int MinimumHistory { get; set; }
|
||||||
@@ -49,7 +49,7 @@ namespace Managing.Domain.Strategies
|
|||||||
{
|
{
|
||||||
if (Candles.All(c => c.Date != item.Date))
|
if (Candles.All(c => c.Date != item.Date))
|
||||||
{
|
{
|
||||||
Candles.AddItem(item);
|
Candles.Enqueue(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user