Backtest opti (#10)

* Opti backtest

* clean
This commit is contained in:
Oda
2025-02-06 15:47:24 +07:00
committed by GitHub
parent 0987fa76cf
commit 9819fe014e
9 changed files with 78 additions and 47 deletions

View File

@@ -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

View File

@@ -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(),

View File

@@ -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();
} }

View File

@@ -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; }
@@ -30,4 +32,4 @@ namespace Managing.Application.Abstractions
decimal GetTotalFees(); decimal GetTotalFees();
void LoadStrategies(IEnumerable<IStrategy> strategies); void LoadStrategies(IEnumerable<IStrategy> strategies);
} }
} }

View File

@@ -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);

View File

@@ -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($"Signals : {Signals.Count}"); {
Logger.LogInformation($"ExecutionCount : {ExecutionCount}"); Logger.LogInformation($"Candle date : {OptimizedCandles.Last().Date:u}");
Logger.LogInformation($"Positions : {Positions.Count}"); Logger.LogInformation($"Signals : {Signals.Count}");
Logger.LogInformation("__________________________________________________"); Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
Logger.LogInformation($"Positions : {Positions.Count}");
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))
{ {

View 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);
}
}

View File

@@ -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;

View File

@@ -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);
} }
} }
} }