Fix backtest

This commit is contained in:
2025-04-29 01:17:58 +07:00
parent 204bd87e6a
commit f91b12fbcc
14 changed files with 113 additions and 92 deletions

View File

@@ -1,6 +1,7 @@
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
namespace Managing.Application.Abstractions;
@@ -8,7 +9,7 @@ namespace Managing.Application.Abstractions;
public interface IBotService
{
void SaveOrUpdateBotBackup(BotBackup botBackup);
void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data);
void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data);
void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots();

View File

@@ -1,5 +1,4 @@
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
@@ -28,6 +27,7 @@ namespace Managing.Application.Abstractions
Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
User User { get; set; }
string Identifier { get; set; }
Task Run();
Task ToggleIsForWatchOnly();

View File

@@ -45,7 +45,7 @@ namespace Managing.Application.Bots
public override void SaveBackup()
{
var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveOrUpdateBotBackup(Name, BotType.SimpleBot, data);
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, data);
}
public override void LoadBackup(BotBackup backup)

View File

@@ -146,7 +146,7 @@ public class TradingBot : Bot, ITradingBot
public async Task LoadAccount()
{
var account = await AccountService.GetAccount(AccountName, false, true);
var account = await AccountService.GetAccount(AccountName, false, false);
if (account == null)
{
Logger.LogWarning($"No account found for this {AccountName}");
@@ -192,7 +192,7 @@ public class TradingBot : Bot, ITradingBot
if (balance < Constants.GMX.Config.MinimumPositionAmount)
{
await LogWarning(
$"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot and saving backup.");
$"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot {Identifier} and saving backup.");
SaveBackup();
Stop();
return;
@@ -587,8 +587,8 @@ public class TradingBot : Bot, ITradingBot
lastPrice,
signalIdentifier: signal.Identifier);
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
.Handle(command);
var position = (new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
.Handle(command)).Result;
if (position != null)
{
@@ -665,11 +665,10 @@ public class TradingBot : Bot, ITradingBot
}
else
{
var command = new ClosePositionCommand(position, lastPrice);
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest);
try
{
var closedPosition =
(new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
.Handle(command)).Result;
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
@@ -909,7 +908,7 @@ public class TradingBot : Bot, ITradingBot
BotTradingBalance = BotTradingBalance,
StartupTime = StartupTime,
};
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data));
}
public override void LoadBackup(BotBackup backup)
@@ -925,6 +924,8 @@ public class TradingBot : Bot, ITradingBot
AccountName = data.AccountName;
IsForWatchingOnly = data.IsForWatchingOnly;
BotTradingBalance = data.BotTradingBalance;
Identifier = backup.Identifier;
User = backup.User;
// Restore the startup time if it was previously saved
if (data.StartupTime != DateTime.MinValue)

View File

@@ -6,6 +6,7 @@ using Managing.Application.Bots;
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -43,14 +44,14 @@ namespace Managing.Application.ManageBot
await _botRepository.InsertBotAsync(botBackup);
}
public BotBackup GetBotBackup(string name)
public BotBackup GetBotBackup(string identifier)
{
return _botRepository.GetBots().FirstOrDefault(b => b.Name == name);
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
}
public void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data)
public void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data)
{
var backup = GetBotBackup(name);
var backup = GetBotBackup(identifier);
if (backup != null)
{
@@ -61,7 +62,8 @@ namespace Managing.Application.ManageBot
{
var botBackup = new BotBackup
{
Name = name,
User = user,
Identifier = identifier,
BotType = botType,
Data = data
};
@@ -88,13 +90,13 @@ namespace Managing.Application.ManageBot
{
var botTask =
new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance
_botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask);
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
}
public void AddTradingBotToCache(ITradingBot bot)
{
var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot);
_botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask);
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
}
public List<ITradingBot> GetActiveBots()
@@ -120,11 +122,11 @@ namespace Managing.Application.ManageBot
switch (backupBot.BotType)
{
case Enums.BotType.SimpleBot:
bot = CreateSimpleBot(backupBot.Name,
null); // Assuming null is an acceptable parameter for workflow
botTask = Task.Run(() => ((IBot)bot).Start());
break;
// case Enums.BotType.SimpleBot:
// bot = CreateSimpleBot(backupBot.Name,
// null); // Assuming null is an acceptable parameter for workflow
// botTask = Task.Run(() => ((IBot)bot).Start());
// break;
case Enums.BotType.ScalpingBot:
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var scalpingMoneyManagement =
@@ -132,7 +134,7 @@ namespace Managing.Application.ManageBot
bot = CreateScalpingBot(
scalpingBotData.AccountName,
scalpingMoneyManagement,
backupBot.Name,
scalpingBotData.Name,
scalpingBotData.Ticker,
scalpingBotData.ScenarioName,
scalpingBotData.Timeframe,
@@ -147,7 +149,7 @@ namespace Managing.Application.ManageBot
bot = CreateFlippingBot(
flippingBotData.AccountName,
flippingMoneyManagement,
backupBot.Name,
flippingBotData.Name,
flippingBotData.Ticker,
flippingBotData.ScenarioName,
flippingBotData.Timeframe,
@@ -160,7 +162,7 @@ namespace Managing.Application.ManageBot
if (bot != null && botTask != null)
{
var botWrapper = new BotTaskWrapper(botTask, bot.GetType(), bot);
_botTasks.AddOrUpdate(backupBot.Name, botWrapper, (key, existingVal) => botWrapper);
_botTasks.AddOrUpdate(backupBot.Identifier, botWrapper, (key, existingVal) => botWrapper);
}
}

View File

@@ -1,8 +1,8 @@
using MediatR;
using static Managing.Common.Enums;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions;
using Managing.Core;
using MediatR;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot;
@@ -31,39 +31,42 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
{
try
{
var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name);
var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier);
if (activeBot == null)
{
_logger.LogInformation("No active instance found for bot {BotName}. Starting backup...", backupBot.Name);
_logger.LogInformation("No active instance found for bot {Identifier}. Starting backup...",
backupBot.Identifier);
_botService.StartBotFromBackup(backupBot);
activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name);
activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier);
if (activeBot != null)
{
result[activeBot.GetName()] = BotStatus.Backup;
result[activeBot.Identifier] = BotStatus.Backup;
anyBackupStarted = true;
_logger.LogInformation("Backup bot {BotName} started successfully.", backupBot.Name);
_logger.LogInformation("Backup bot {Identifier} started successfully.", backupBot.Identifier);
}
else
{
result[backupBot.Name] = BotStatus.Down;
_logger.LogWarning("Backup bot {BotName} failed to start.", backupBot.Name);
result[backupBot.Identifier] = BotStatus.Down;
_logger.LogWarning("Backup bot {Identifier} failed to start.", backupBot.Identifier);
}
}
else
{
var status = MiscExtensions.ParseEnum<BotStatus>(activeBot.GetStatus());
result[activeBot.GetName()] = status;
result[activeBot.Identifier] = status;
anyBotActive = true;
_logger.LogInformation("Bot {BotName} is already active with status {Status}.", activeBot.GetName(), status);
_logger.LogInformation("Bot {Identifier} is already active with status {Status}.",
activeBot.Identifier,
status);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading bot {BotName}. Deleting its backup.", backupBot.Name);
_botService.DeleteBotBackup(backupBot.Name);
result[backupBot.Name] = BotStatus.Down;
_logger.LogError(ex, "Error loading bot {Identifier}. Deleting its backup.", backupBot.Identifier);
_botService.DeleteBotBackup(backupBot.Identifier);
result[backupBot.Identifier] = BotStatus.Down;
}
}
@@ -73,13 +76,14 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
// Determine final status
BotStatus finalStatus = anyBackupStarted
? BotStatus.Backup
: anyBotActive ? BotStatus.Up : BotStatus.Down;
: anyBotActive
? BotStatus.Up
: BotStatus.Down;
_logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
return Task.FromResult(finalStatus.ToString());
}
}
public class LoadBackupBotCommand : IRequest<string>

View File

@@ -27,14 +27,14 @@ public class ClosePositionCommandHandler(
return request.Position;
}
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
var isForPaperTrading = request.IsForBacktest;
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Check if position still open
if (!isForPaperTrading)
if (!request.IsForBacktest)
{
var p = (await exchangeService.GetBrokerPositions(account))
.FirstOrDefault(x => x.Ticker == request.Position.Ticker);
@@ -65,6 +65,8 @@ public class ClosePositionCommandHandler(
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
request.Position.Open.Leverage);
if (!request.IsForBacktest)
tradingService.UpdatePosition(request.Position);
}
@@ -72,10 +74,10 @@ public class ClosePositionCommandHandler(
}
catch (Exception ex)
{
// Log the error - regardless of the error type
logger?.LogError(ex, "Error closing position: {Message}", ex.Message);
logger?.LogError(ex, "Error closing position: {Message} \n Stacktrace : {StackTrace}", ex.Message,
ex.StackTrace);
throw new Exception(ex.Message);
throw;
}
}
}

View File

@@ -5,13 +5,15 @@ namespace Managing.Application.Trading.Commands
{
public class ClosePositionCommand : IRequest<Position>
{
public ClosePositionCommand(Position position, decimal? executionPrice = null)
public ClosePositionCommand(Position position, decimal? executionPrice = null, bool isForBacktest = false)
{
Position = position;
ExecutionPrice = executionPrice;
IsForBacktest = isForBacktest;
}
public Position Position { get; }
public decimal? ExecutionPrice { get; set; }
public bool IsForBacktest { get; set; }
}
}

View File

@@ -1,10 +1,12 @@
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Bots;
public class BotBackup
{
public string Name { get; set; }
public BotType BotType { get; set; }
public string Identifier { get; set; }
public User User { get; set; }
public string Data { get; set; }
}

View File

@@ -8,11 +8,14 @@
void Restart();
string GetStatus();
string GetName();
/// <summary>
/// Gets the total runtime of the bot since it was started
/// </summary>
/// <returns>TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running</returns>
TimeSpan GetRuntime();
string Identifier { get; set; }
void SaveBackup();
void LoadBackup(BotBackup backup);
}

View File

@@ -28,7 +28,7 @@ public class BotRepository : IBotRepository
public async Task UpdateBackupBot(BotBackup bot)
{
var b = await _botRepository.FindOneAsync(b => b.Name == bot.Name);
var b = await _botRepository.FindOneAsync(b => b.Identifier == bot.Identifier);
var dto = MongoMappers.Map(bot);
dto.Id = b.Id;
_botRepository.Update(dto);

View File

@@ -10,4 +10,6 @@ public class BotDto : Document
public string Name { get; set; }
public string Data { get; set; }
public BotType BotType { get; set; }
public string Identifier { get; set; }
public UserDto User { get; set; }
}

View File

@@ -720,7 +720,8 @@ public static class MongoMappers
return new BotDto
{
Name = bot.Name,
User = Map(bot.User),
Identifier = bot.Identifier,
BotType = bot.BotType,
Data = bot.Data,
};
@@ -732,7 +733,8 @@ public static class MongoMappers
return new BotBackup
{
Name = b.Name,
User = Map(b.User),
Identifier = b.Identifier,
BotType = b.BotType,
Data = b.Data
};

View File

@@ -197,7 +197,7 @@ public class TradingRepository : ITradingRepository
public void UpdatePosition(Position position)
{
var p = _positionRepository.FindOne(p => p.Open.ExchangeOrderId == position.Open.ExchangeOrderId);
var p = _positionRepository.FindOne(p => p.Identifier.Equals(position.Identifier));
var dto = MongoMappers.Map(position);
dto.Id = p.Id;
_positionRepository.Update(dto);