Update bot config on front and back

This commit is contained in:
2025-06-04 15:42:21 +07:00
parent f41af96406
commit 756cd5fb11
14 changed files with 422 additions and 369 deletions

View File

@@ -178,39 +178,21 @@ public class BacktestController : BaseController
break; break;
case BotType.ScalpingBot: case BotType.ScalpingBot:
backtestResult = await _backtester.RunScalpingBotBacktest( backtestResult = await _backtester.RunScalpingBotBacktest(
account, backtestConfig,
moneyManagement,
request.Config.Ticker,
scenario,
request.Config.Timeframe,
request.Balance,
request.StartDate, request.StartDate,
request.EndDate, request.EndDate,
user, user,
request.WatchOnly,
request.Save, request.Save,
cooldownPeriod: request.Config.CooldownPeriod, null);
maxLossStreak: request.Config.MaxLossStreak,
maxPositionTimeHours: request.Config.MaxPositionTimeHours,
flipOnlyWhenInProfit: request.Config.FlipOnlyWhenInProfit);
break; break;
case BotType.FlippingBot: case BotType.FlippingBot:
backtestResult = await _backtester.RunFlippingBotBacktest( backtestResult = await _backtester.RunFlippingBotBacktest(
account, backtestConfig,
moneyManagement,
request.Config.Ticker,
scenario,
request.Config.Timeframe,
request.Balance,
request.StartDate, request.StartDate,
request.EndDate, request.EndDate,
user, user,
request.WatchOnly,
request.Save, request.Save,
cooldownPeriod: request.Config.CooldownPeriod, null);
maxLossStreak: request.Config.MaxLossStreak,
maxPositionTimeHours: request.Config.MaxPositionTimeHours,
flipOnlyWhenInProfit: request.Config.FlipOnlyWhenInProfit);
break; break;
} }

View File

@@ -105,52 +105,64 @@ public class BotController : BaseController
{ {
try try
{ {
// Check if user owns the account if (request.Config == null)
if (!await UserOwnsBotAccount(request.Identifier, request.AccountName))
{ {
return Forbid("You don't have permission to start this bot"); return BadRequest("Bot configuration is required");
}
// Check if user owns the account specified in the request
if (!await UserOwnsBotAccount(null, request.Config.AccountName))
{
return Forbid("You don't have permission to start a bot with this account");
} }
// Trigger error if money management is not provided // Trigger error if money management is not provided
if (string.IsNullOrEmpty(request.MoneyManagementName)) if (string.IsNullOrEmpty(request.MoneyManagementName) && request.Config.MoneyManagement == null)
{ {
return BadRequest("Money management name is required"); return BadRequest("Money management name or money management object is required");
} }
var user = await GetUser(); var user = await GetUser();
var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
// Get money management if name is provided
MoneyManagement moneyManagement = request.Config.MoneyManagement;
if (!string.IsNullOrEmpty(request.MoneyManagementName))
{
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (moneyManagement == null) if (moneyManagement == null)
{ {
return BadRequest("Money management not found"); return BadRequest("Money management not found");
} }
}
// Validate initialTradingBalance // Validate initialTradingBalance
if (request.InitialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
{ {
return BadRequest( return BadRequest(
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}"); $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
} }
// Update the config with final money management
var config = new TradingBotConfig var config = new TradingBotConfig
{ {
AccountName = request.AccountName, AccountName = request.Config.AccountName,
MoneyManagement = moneyManagement, MoneyManagement = moneyManagement,
Ticker = request.Ticker, Ticker = request.Config.Ticker,
ScenarioName = request.Scenario, ScenarioName = request.Config.ScenarioName,
Timeframe = request.Timeframe, Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.IsForWatchOnly, IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.InitialTradingBalance, BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.BotType, BotType = request.Config.BotType,
CooldownPeriod = request.CooldownPeriod, CooldownPeriod = request.Config.CooldownPeriod,
MaxLossStreak = request.MaxLossStreak, MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.MaxPositionTimeHours, MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = request.FlipOnlyWhenInProfit, FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
IsForBacktest = false, IsForBacktest = false,
FlipPosition = request.BotType == BotType.FlippingBot, FlipPosition = request.Config.BotType == BotType.FlippingBot,
Name = request.Name Name = request.Config.Name
}; };
var result = await _mediator.Send(new StartBotCommand(config, request.Name, user)); var result = await _mediator.Send(new StartBotCommand(config, request.Config.Name, user));
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
return Ok(result); return Ok(result);
@@ -684,77 +696,14 @@ public class ClosePositionRequest
public class StartBotRequest public class StartBotRequest
{ {
/// <summary> /// <summary>
/// The type of bot to start /// The trading bot configuration
/// </summary> /// </summary>
public BotType BotType { get; set; } public TradingBotConfig Config { get; set; }
/// <summary> /// <summary>
/// The identifier of the bot /// Optional money management name (if not included in Config.MoneyManagement)
/// </summary> /// </summary>
public string Identifier { get; set; } public string? MoneyManagementName { get; set; }
/// <summary>
/// The ticker to trade
/// </summary>
public Ticker Ticker { get; set; }
/// <summary>
/// The scenario to use
/// </summary>
public string Scenario { get; set; }
/// <summary>
/// The timeframe to use
/// </summary>
public Timeframe Timeframe { get; set; }
/// <summary>
/// The account name to use
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// The money management name to use
/// </summary>
public string MoneyManagementName { get; set; }
/// <summary>
/// Whether the bot is for watching only
/// </summary>
public bool IsForWatchOnly { get; set; }
/// <summary>
/// The initial trading balance
/// </summary>
public decimal InitialTradingBalance { get; set; }
/// <summary>
/// The cooldown period in candles between positions
/// </summary>
public int CooldownPeriod { get; set; }
/// <summary>
/// The maximum number of consecutive losses before stopping
/// </summary>
public int MaxLossStreak { get; set; }
/// <summary>
/// The name of the bot
/// </summary>
public string Name { get; set; }
/// <summary>
/// Maximum time in hours that a position can remain open before being automatically closed.
/// Supports fractional values (e.g., 2.5 for 2 hours and 30 minutes).
/// If null, time-based position closure is disabled.
/// </summary>
public decimal? MaxPositionTimeHours { get; set; } = null;
/// <summary>
/// If true, positions will only be flipped when the current position is in profit.
/// If false, positions will be flipped regardless of profit status.
/// </summary>
public bool FlipOnlyWhenInProfit { get; set; } = true;
} }
/// <summary> /// <summary>

View File

@@ -1,79 +1,40 @@
using Managing.Domain.Accounts; using Managing.Domain.Backtests;
using Managing.Domain.Backtests; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Users; using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services namespace Managing.Application.Abstractions.Services
{ {
public interface IBacktester public interface IBacktester
{ {
Task<Backtest> RunScalpingBotBacktest( Task<Backtest> RunScalpingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null);
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true);
Task<Backtest> RunFlippingBotBacktest( Task<Backtest> RunFlippingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null);
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true);
bool DeleteBacktest(string id); bool DeleteBacktest(string id);
bool DeleteBacktests(); bool DeleteBacktests();
Task<Backtest> RunScalpingBotBacktest( Task<Backtest> RunScalpingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles, List<Candle> candles,
decimal balance, User user = null);
User user = null,
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true);
Task<Backtest> RunFlippingBotBacktest( Task<Backtest> RunFlippingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles, List<Candle> candles,
decimal balance, User user = null);
User user = null,
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true);
// User-specific operations // User-specific operations
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user); Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);

View File

@@ -6,6 +6,7 @@ using Managing.Application.Abstractions.Services;
using Managing.Application.Backtesting; using Managing.Application.Backtesting;
using Managing.Application.Bots.Base; using Managing.Application.Bots.Base;
using Managing.Core; using Managing.Core;
using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
@@ -42,7 +43,7 @@ namespace Managing.Application.Tests
_tradingService.Object, _tradingService.Object,
botService); botService);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger, _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
scenarioService); scenarioService, _accountService.Object);
_elapsedTimes = new List<double>(); _elapsedTimes = new List<double>();
} }
@@ -58,10 +59,25 @@ namespace Managing.Application.Tests
var localCandles = var localCandles =
FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json"); FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json");
var config = new TradingBotConfig
{
AccountName = _account.Name,
MoneyManagement = MoneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = true,
Name = "Test"
};
// Act // Act
var backtestResult = await _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario, var backtestResult = await _backtester.RunFlippingBotBacktest(config, localCandles.TakeLast(500).ToList());
timeframe, 1000, new DateTime().AddDays(-3), DateTime.UtcNow,
initialCandles: localCandles.TakeLast(500).ToList());
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None); var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json); File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
@@ -90,9 +106,25 @@ namespace Managing.Application.Tests
var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, "RsiDiv", period: 5); var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, "RsiDiv", period: 5);
scenario.AddStrategy(strategy); scenario.AddStrategy(strategy);
var config = new TradingBotConfig
{
AccountName = _account.Name,
MoneyManagement = MoneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = false,
Name = "Test"
};
// Act // Act
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario, var backtestResult = await _backtester.RunScalpingBotBacktest(config, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null, false, null);
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
//WriteCsvReport(backtestResult.GetStringReport()); //WriteCsvReport(backtestResult.GetStringReport());
// Assert // Assert
@@ -120,9 +152,25 @@ namespace Managing.Application.Tests
TakeProfit = 0.02m TakeProfit = 0.02m
}; };
var config = new TradingBotConfig
{
AccountName = _account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = false,
Name = "Test"
};
// Act // Act
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario, var backtestResult = await _backtester.RunScalpingBotBacktest(config, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null, false, null);
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
WriteCsvReport(backtestResult.GetStringReport()); WriteCsvReport(backtestResult.GetStringReport());
// Assert // Assert
@@ -191,10 +239,38 @@ namespace Managing.Application.Tests
var backtestResult = botType switch var backtestResult = botType switch
{ {
BotType.SimpleBot => throw new NotImplementedException(), BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement, BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(new TradingBotConfig
scenario, timeframe, candles, 1000, null).Result, {
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement, AccountName = _account.Name,
scenario, timeframe, candles, 1000, null).Result, MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = false,
Name = "Test"
}, candles, null).Result,
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(new TradingBotConfig
{
AccountName = _account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = true,
Name = "Test"
}, candles, null).Result,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
timer.Stop(); timer.Stop();
@@ -299,10 +375,38 @@ namespace Managing.Application.Tests
var backtestResult = botType switch var backtestResult = botType switch
{ {
BotType.SimpleBot => throw new NotImplementedException(), BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement, BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(new TradingBotConfig
scenario, timeframe, candles, 1000, null).Result, {
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement, AccountName = _account.Name,
scenario, timeframe, candles, 1000, null).Result, MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = false,
Name = "Test"
}, candles, null).Result,
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(new TradingBotConfig
{
AccountName = _account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = 1000,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = true,
Name = "Test"
}, candles, null).Result,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
@@ -262,16 +263,30 @@ public class StatisticService : IStatisticService
TakeProfit = 0.02m TakeProfit = 0.02m
}; };
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = true,
BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = 1,
MaxLossStreak = 0,
FlipPosition = false,
Name = "StatisticsBacktest"
};
var backtest = await _backtester.RunScalpingBotBacktest( var backtest = await _backtester.RunScalpingBotBacktest(
account, config,
moneyManagement,
ticker,
scenario,
timeframe,
1000,
DateTime.Now.AddDays(-7), DateTime.Now.AddDays(-7),
DateTime.Now, DateTime.Now,
isForWatchingOnly: true); null,
false,
null);
return backtest.Signals; return backtest.Signals;
} }

View File

@@ -1,13 +1,11 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Core.FixedSizedQueue; using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
@@ -26,18 +24,22 @@ namespace Managing.Application.Backtesting
private readonly IExchangeService _exchangeService; private readonly IExchangeService _exchangeService;
private readonly IBotFactory _botFactory; private readonly IBotFactory _botFactory;
private readonly IScenarioService _scenarioService; private readonly IScenarioService _scenarioService;
private readonly IAccountService _accountService;
public Backtester( public Backtester(
IExchangeService exchangeService, IExchangeService exchangeService,
IBotFactory botFactory, IBotFactory botFactory,
IBacktestRepository backtestRepository, IBacktestRepository backtestRepository,
ILogger<Backtester> logger, IScenarioService scenarioService) ILogger<Backtester> logger,
IScenarioService scenarioService,
IAccountService accountService)
{ {
_exchangeService = exchangeService; _exchangeService = exchangeService;
_botFactory = botFactory; _botFactory = botFactory;
_backtestRepository = backtestRepository; _backtestRepository = backtestRepository;
_logger = logger; _logger = logger;
_scenarioService = scenarioService; _scenarioService = scenarioService;
_accountService = accountService;
} }
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false) public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
@@ -54,45 +56,19 @@ namespace Managing.Application.Backtesting
} }
public async Task<Backtest> RunScalpingBotBacktest( public async Task<Backtest> RunScalpingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null)
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true)
{ {
var config = new TradingBotConfig var account = await GetAccountFromConfig(config);
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = balance,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak,
MaxPositionTimeHours = maxPositionTimeHours,
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
};
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config); var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
scalpingBot.LoadScenario(scenario.Name); scalpingBot.LoadScenario(config.ScenarioName);
scalpingBot.User = user; scalpingBot.User = user;
await scalpingBot.LoadAccount(); await scalpingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate); var candles = initialCandles ?? GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
var result = GetBacktestingResult(config, scalpingBot, candles); var result = GetBacktestingResult(config, scalpingBot, candles);
if (user != null) if (user != null)
@@ -113,46 +89,20 @@ namespace Managing.Application.Backtesting
} }
public async Task<Backtest> RunFlippingBotBacktest( public async Task<Backtest> RunFlippingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null)
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true)
{ {
var config = new TradingBotConfig var account = await GetAccountFromConfig(config);
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = balance,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak,
MaxPositionTimeHours = maxPositionTimeHours,
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
};
var flippingBot = _botFactory.CreateBacktestFlippingBot(config); var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
flippingBot.LoadScenario(scenario.Name); flippingBot.LoadScenario(config.ScenarioName);
flippingBot.User = user; flippingBot.User = user;
await flippingBot.LoadAccount(); await flippingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate); var candles = initialCandles ?? GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
var result = GetBacktestingResult(config, flippingBot, candles); var result = GetBacktestingResult(config, flippingBot, candles);
if (user != null) if (user != null)
@@ -173,38 +123,13 @@ namespace Managing.Application.Backtesting
} }
public async Task<Backtest> RunScalpingBotBacktest( public async Task<Backtest> RunScalpingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles, List<Candle> candles,
decimal balance, User user = null)
User user = null,
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var account = await GetAccountFromConfig(config);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = balance,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak,
MaxPositionTimeHours = maxPositionTimeHours,
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
};
var bot = _botFactory.CreateBacktestScalpingBot(config); var bot = _botFactory.CreateBacktestScalpingBot(config);
bot.LoadScenario(scenario.Name); bot.LoadScenario(config.ScenarioName);
bot.User = user; bot.User = user;
await bot.LoadAccount(); await bot.LoadAccount();
@@ -219,38 +144,13 @@ namespace Managing.Application.Backtesting
} }
public async Task<Backtest> RunFlippingBotBacktest( public async Task<Backtest> RunFlippingBotBacktest(
Account account, TradingBotConfig config,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles, List<Candle> candles,
decimal balance, User user = null)
User user = null,
int cooldownPeriod = 1,
int maxLossStreak = 0,
decimal? maxPositionTimeHours = null,
bool flipOnlyWhenInProfit = true)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var account = await GetAccountFromConfig(config);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = balance,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak,
MaxPositionTimeHours = maxPositionTimeHours,
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
};
var bot = _botFactory.CreateBacktestFlippingBot(config); var bot = _botFactory.CreateBacktestFlippingBot(config);
bot.LoadScenario(scenario.Name); bot.LoadScenario(config.ScenarioName);
bot.User = user; bot.User = user;
await bot.LoadAccount(); await bot.LoadAccount();
@@ -264,6 +164,23 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
{
// Use the account service to get the actual account
var account = await _accountService.GetAccount(config.AccountName, false, false);
if (account != null)
{
return account;
}
// Fallback: create a basic account structure if not found
return new Account
{
Name = config.AccountName,
Exchange = TradingExchanges.GmxV2 // Default exchange, should be configurable
};
}
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe, private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate) DateTime startDate, DateTime endDate)
{ {

View File

@@ -468,19 +468,44 @@ public class TradingBot : Bot, ITradingBot
var currentTime = Config.IsForBacktest ? lastCandle.Date : DateTime.UtcNow; var currentTime = Config.IsForBacktest ? lastCandle.Date : DateTime.UtcNow;
// Check if position has exceeded maximum time limit (only if MaxPositionTimeHours is set) // Check time-based position management (only if MaxPositionTimeHours is set)
if (Config.MaxPositionTimeHours.HasValue && HasPositionExceededTimeLimit(positionForSignal, currentTime)) if (Config.MaxPositionTimeHours.HasValue)
{ {
// Check if position is in profit or at breakeven before closing
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close); var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
var isAtBreakeven = Math.Abs(lastCandle.Close - positionForSignal.Open.Price) < 0.01m; // Small tolerance for breakeven var isAtBreakeven = Math.Abs(lastCandle.Close - positionForSignal.Open.Price) < 0.01m; // Small tolerance for breakeven
var hasExceededTimeLimit = HasPositionExceededTimeLimit(positionForSignal, currentTime);
if (isPositionInProfit || isAtBreakeven) // Calculate current unrealized PNL for logging
var currentPnl = CalculateUnrealizedPnl(positionForSignal, lastCandle.Close);
var pnlPercentage = Math.Round((currentPnl / (positionForSignal.Open.Price * positionForSignal.Open.Quantity)) * 100, 2);
// Early closure logic when CloseEarlyWhenProfitable is enabled
if (Config.CloseEarlyWhenProfitable && (isPositionInProfit || isAtBreakeven))
{ {
await LogInformation(
$"Closing position early due to profitability - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}. Position is {(isPositionInProfit ? "in profit" : "at breakeven")} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is enabled.");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return;
}
// Time limit exceeded logic
if (hasExceededTimeLimit)
{
if (Config.CloseEarlyWhenProfitable || isPositionInProfit || isAtBreakeven)
{
// Close when time limit is reached if:
// 1. CloseEarlyWhenProfitable is enabled (safety net), OR
// 2. Position is in profit/breakeven (when CloseEarlyWhenProfitable is disabled)
await LogInformation( await LogInformation(
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " + $"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " + $"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
$"Position is {(isPositionInProfit ? "in profit" : "at breakeven")} (entry: {positionForSignal.Open.Price}, current: {lastCandle.Close})"); $"Position is {(isPositionInProfit ? "in profit" : isAtBreakeven ? "at breakeven" : "at a loss")} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%)");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true); await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return; return;
} }
@@ -489,7 +514,9 @@ public class TradingBot : Bot, ITradingBot
await LogInformation( await LogInformation(
$"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " + $"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " + $"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Waiting for profit or breakeven before closing."); $"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is disabled - waiting for profit or breakeven before closing.");
}
} }
} }
@@ -1269,6 +1296,28 @@ public class TradingBot : Bot, ITradingBot
return timeOpen >= maxTimeAllowed; return timeOpen >= maxTimeAllowed;
} }
/// <summary>
/// Calculates the current unrealized PNL for a position
/// </summary>
/// <param name="position">The position to calculate PNL for</param>
/// <param name="currentPrice">The current market price</param>
/// <returns>The current unrealized PNL</returns>
private decimal CalculateUnrealizedPnl(Position position, decimal currentPrice)
{
if (position.OriginDirection == TradeDirection.Long)
{
return currentPrice - position.Open.Price;
}
else if (position.OriginDirection == TradeDirection.Short)
{
return position.Open.Price - currentPrice;
}
else
{
throw new ArgumentException("Invalid position direction");
}
}
/// <summary> /// <summary>
/// Updates the trading bot configuration with new settings. /// Updates the trading bot configuration with new settings.
/// This method validates the new configuration and applies it to the running bot. /// This method validates the new configuration and applies it to the running bot.

View File

@@ -26,6 +26,14 @@ public class TradingBotConfig
/// </summary> /// </summary>
public decimal? MaxPositionTimeHours { get; set; } public decimal? MaxPositionTimeHours { get; set; }
/// <summary>
/// When MaxPositionTimeHours is set and this is true, the position will be closed as soon as
/// it reaches breakeven or profit, rather than waiting for the full time duration.
/// If false, the position will only be closed when MaxPositionTimeHours is reached.
/// Default is false to maintain existing behavior.
/// </summary>
public bool CloseEarlyWhenProfitable { get; set; } = false;
/// <summary> /// <summary>
/// If true, positions will only be flipped when the current position is in profit. /// If true, positions will only be flipped when the current position is in profit.
/// If false, positions will be flipped regardless of profit status. /// If false, positions will be flipped regardless of profit status.

View File

@@ -9,7 +9,6 @@ import type {
MoneyManagement, MoneyManagement,
RunBacktestRequest, RunBacktestRequest,
StartBotRequest, StartBotRequest,
Ticker,
TradingBotConfig TradingBotConfig
} from '../../../generated/ManagingApi' } from '../../../generated/ManagingApi'
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi' import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
@@ -90,23 +89,39 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
moneyManagementName.toLowerCase() === 'custom' || moneyManagementName.toLowerCase() === 'custom' ||
moneyManagementName.toLowerCase().includes('custom'); moneyManagementName.toLowerCase().includes('custom');
const request: StartBotRequest = { // Create TradingBotConfig from the backtest configuration
const tradingBotConfig: TradingBotConfig = {
accountName: backtest.config.accountName, accountName: backtest.config.accountName,
name: botName, ticker: backtest.config.ticker,
botType: backtest.config.botType, scenarioName: backtest.config.scenarioName,
isForWatchOnly: isForWatchOnly,
// Only use the money management name if it's not a custom money management, otherwise use optimized
moneyManagementName: isCustomMoneyManagement ?
(backtest.optimizedMoneyManagement?.name || backtest.config.moneyManagement?.name) :
moneyManagementName,
scenario: backtest.config.scenarioName,
ticker: backtest.config.ticker as Ticker,
timeframe: backtest.config.timeframe, timeframe: backtest.config.timeframe,
initialTradingBalance: initialTradingBalance, botType: backtest.config.botType,
isForWatchingOnly: isForWatchOnly,
isForBacktest: false, // This is for running a live bot
cooldownPeriod: backtest.config.cooldownPeriod, cooldownPeriod: backtest.config.cooldownPeriod,
maxLossStreak: backtest.config.maxLossStreak, maxLossStreak: backtest.config.maxLossStreak,
maxPositionTimeHours: backtest.config.maxPositionTimeHours, maxPositionTimeHours: backtest.config.maxPositionTimeHours,
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit,
flipPosition: backtest.config.flipPosition,
name: botName,
botTradingBalance: initialTradingBalance,
// Use the optimized or original money management from backtest if it's custom
moneyManagement: isCustomMoneyManagement ?
(backtest.optimizedMoneyManagement || backtest.config.moneyManagement || {
name: 'default',
leverage: 1,
stopLoss: 0.01,
takeProfit: 0.02,
timeframe: backtest.config.timeframe
}) :
backtest.config.moneyManagement, // Always provide a valid MoneyManagement object
closeEarlyWhenProfitable: backtest.config.closeEarlyWhenProfitable || false
};
const request: StartBotRequest = {
config: tradingBotConfig,
// Only use the money management name if it's not a custom money management
moneyManagementName: isCustomMoneyManagement ? undefined : moneyManagementName
} }
await client await client

View File

@@ -47,7 +47,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
cooldownPeriod: 10, // Default cooldown period of 10 minutes cooldownPeriod: 10, // Default cooldown period of 10 minutes
maxLossStreak: 0, // Default max loss streak of 0 (no limit) maxLossStreak: 0, // Default max loss streak of 0 (no limit)
maxPositionTimeHours: null, // Default to null (disabled) maxPositionTimeHours: null, // Default to null (disabled)
flipOnlyWhenInProfit: true // Default to true flipOnlyWhenInProfit: true, // Default to true
balance: 10000, // Default balance
closeEarlyWhenProfitable: false // Default to false
} }
}); });
const [selectedAccount, setSelectedAccount] = useState<string>('') const [selectedAccount, setSelectedAccount] = useState<string>('')
@@ -123,14 +125,15 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true, flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true,
flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type
name: `Backtest-${scenarioName}-${ticker}-${new Date().toISOString()}`, name: `Backtest-${scenarioName}-${ticker}-${new Date().toISOString()}`,
botTradingBalance: 0, // Will be set in the request botTradingBalance: form.balance,
moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || { moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || {
name: 'placeholder', name: 'placeholder',
leverage: 1, leverage: 1,
stopLoss: 0.01, stopLoss: 0.01,
takeProfit: 0.02, takeProfit: 0.02,
timeframe: form.timeframe timeframe: form.timeframe
} },
closeEarlyWhenProfitable: form.closeEarlyWhenProfitable ?? false
}; };
// Create the RunBacktestRequest // Create the RunBacktestRequest
@@ -138,7 +141,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
config: tradingBotConfig, config: tradingBotConfig,
startDate: new Date(form.startDate), startDate: new Date(form.startDate),
endDate: new Date(form.endDate), endDate: new Date(form.endDate),
balance: balance, balance: form.balance,
watchOnly: false, watchOnly: false,
save: form.save || false, save: form.save || false,
moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement, moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement,
@@ -335,8 +338,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
} else if (selectedMoneyManagement) { } else if (selectedMoneyManagement) {
mm = moneyManagements.find((m) => m.name === selectedMoneyManagement); mm = moneyManagements.find((m) => m.name === selectedMoneyManagement);
} }
// Use actual initial balance and a minimum threshold // Use form balance if available, otherwise fall back to state balance
const initialBalance = balance; const initialBalance = balance; // This state is kept in sync with form
const minBalance = 10; // You can make this configurable if needed const minBalance = 10; // You can make this configurable if needed
if (mm && mm.leverage && mm.stopLoss && initialBalance > minBalance) { if (mm && mm.leverage && mm.stopLoss && initialBalance > minBalance) {
const perLoss = mm.leverage * mm.stopLoss; const perLoss = mm.leverage * mm.stopLoss;
@@ -400,8 +403,11 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
<input <input
type="number" type="number"
className="input input-bordered w-full" className="input input-bordered w-full"
value={balance} {...register('balance', { valueAsNumber: true })}
onChange={(e) => setBalance(Number(e.target.value))} onChange={(e) => {
setValue('balance', Number(e.target.value));
setBalance(Number(e.target.value)); // Keep state in sync for UI calculations
}}
/> />
</FormInput> </FormInput>
@@ -443,7 +449,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
</FormInput> </FormInput>
</div> </div>
{/* Sixth Row: Flip Only When In Profit & Save */} {/* Sixth Row: Flip Only When In Profit & Close Early When Profitable */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit"> <FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit">
<input <input
@@ -456,6 +462,20 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
</div> </div>
</FormInput> </FormInput>
<FormInput label="Close Early When Profitable" htmlFor="closeEarlyWhenProfitable">
<input
type="checkbox"
className="toggle toggle-primary"
{...register('closeEarlyWhenProfitable')}
/>
<div className="text-xs text-gray-500 mt-1">
If enabled, positions will close early when they become profitable
</div>
</FormInput>
</div>
{/* Seventh Row: Save */}
<div className="grid grid-cols-1 gap-4">
<FormInput label="Save" htmlFor="save"> <FormInput label="Save" htmlFor="save">
<input <input
type="checkbox" type="checkbox"
@@ -465,7 +485,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
</FormInput> </FormInput>
</div> </div>
{/* Seventh Row: Start Date & End Date */} {/* Eighth Row: Start Date & End Date */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<FormInput label="Start Date" htmlFor="startDate"> <FormInput label="Start Date" htmlFor="startDate">
<input <input

View File

@@ -226,6 +226,25 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
const cooldownRecommendations = getCooldownRecommendations(); const cooldownRecommendations = getCooldownRecommendations();
// Calculate average trades per day
const getAverageTradesPerDay = () => {
if (positions.length === 0) return "0.00";
// Get all trade dates and sort them
const tradeDates = positions.map(position => new Date(position.open.date)).sort((a, b) => a.getTime() - b.getTime());
if (tradeDates.length < 2) return positions.length.toString();
// Calculate the date range in days
const firstTradeDate = tradeDates[0];
const lastTradeDate = tradeDates[tradeDates.length - 1];
const diffInMs = lastTradeDate.getTime() - firstTradeDate.getTime();
const diffInDays = Math.max(1, diffInMs / (1000 * 60 * 60 * 24)); // Ensure at least 1 day
const averageTradesPerDay = positions.length / diffInDays;
return averageTradesPerDay.toFixed(2);
};
return ( return (
<> <>
<div className="grid grid-flow-row"> <div className="grid grid-flow-row">
@@ -325,6 +344,10 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
title="Median Cooldown" title="Median Cooldown"
content={cooldownRecommendations.median + " candles"} content={cooldownRecommendations.median + " candles"}
></CardText> ></CardText>
<CardText
title="Avg Trades Per Day"
content={getAverageTradesPerDay() + " trades/day"}
></CardText>
</div> </div>
<div> <div>
<figure> <figure>

View File

@@ -3,7 +3,7 @@ import React, {useEffect, useState} from 'react'
import {useQuery} from '@tanstack/react-query' import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import type {Backtest, StartBotRequest, Ticker,} from '../../../generated/ManagingApi' import type {Backtest, StartBotRequest, Ticker, TradingBotConfig} from '../../../generated/ManagingApi'
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi' import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
import type {IBacktestCards} from '../../../global/type' import type {IBacktestCards} from '../../../global/type'
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules' import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
@@ -58,19 +58,39 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
moneyManagementName.toLowerCase() === 'custom' || moneyManagementName.toLowerCase() === 'custom' ||
moneyManagementName.toLowerCase().includes('custom'); moneyManagementName.toLowerCase().includes('custom');
const request: StartBotRequest = { // Create TradingBotConfig from the backtest configuration
const tradingBotConfig: TradingBotConfig = {
accountName: backtest.config.accountName, accountName: backtest.config.accountName,
botType: backtest.config.botType, ticker: backtest.config.ticker,
isForWatchOnly: isForWatchOnly, scenarioName: backtest.config.scenarioName,
// Only use the money management name if it's not a custom money management
moneyManagementName: isCustomMoneyManagement ? '' : moneyManagementName,
scenario: backtest.config.scenarioName,
ticker: backtest.config.ticker as Ticker,
timeframe: backtest.config.timeframe, timeframe: backtest.config.timeframe,
initialTradingBalance: initialTradingBalance, botType: backtest.config.botType,
isForWatchingOnly: isForWatchOnly,
isForBacktest: false, // This is for running a live bot
cooldownPeriod: backtest.config.cooldownPeriod, cooldownPeriod: backtest.config.cooldownPeriod,
maxLossStreak: backtest.config.maxLossStreak, maxLossStreak: backtest.config.maxLossStreak,
maxPositionTimeHours: backtest.config.maxPositionTimeHours,
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit,
flipPosition: backtest.config.flipPosition,
name: botName, name: botName,
botTradingBalance: initialTradingBalance,
// Use the money management from backtest if it's custom, otherwise leave null and use moneyManagementName
moneyManagement: isCustomMoneyManagement ?
(backtest.config.moneyManagement || {
name: 'default',
leverage: 1,
stopLoss: 0.01,
takeProfit: 0.02,
timeframe: backtest.config.timeframe
}) :
backtest.config.moneyManagement, // Always provide a valid MoneyManagement object
closeEarlyWhenProfitable: backtest.config.closeEarlyWhenProfitable || false
};
const request: StartBotRequest = {
config: tradingBotConfig,
// Only use the money management name if it's not a custom money management
moneyManagementName: isCustomMoneyManagement ? undefined : moneyManagementName
} }
await client await client

View File

@@ -2699,6 +2699,7 @@ export interface TradingBotConfig {
flipPosition: boolean; flipPosition: boolean;
name: string; name: string;
maxPositionTimeHours?: number | null; maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit: boolean; flipOnlyWhenInProfit: boolean;
} }
@@ -3113,20 +3114,8 @@ export interface RunBacktestRequest {
} }
export interface StartBotRequest { export interface StartBotRequest {
botType?: BotType; config?: TradingBotConfig | null;
identifier?: string | null;
ticker?: Ticker;
scenario?: string | null;
timeframe?: Timeframe;
accountName?: string | null;
moneyManagementName?: string | null; moneyManagementName?: string | null;
isForWatchOnly?: boolean;
initialTradingBalance?: number;
cooldownPeriod?: number;
maxLossStreak?: number;
name?: string | null;
maxPositionTimeHours?: number | null;
flipOnlyWhenInProfit?: boolean;
} }
export interface TradingBot { export interface TradingBot {

View File

@@ -116,6 +116,7 @@ export type IBacktestsFormInput = {
maxLossStreak: number maxLossStreak: number
maxPositionTimeHours?: number | null maxPositionTimeHours?: number | null
flipOnlyWhenInProfit?: boolean flipOnlyWhenInProfit?: boolean
closeEarlyWhenProfitable?: boolean
} }
export type IBacktestCards = { export type IBacktestCards = {