Update bot config on front and back
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Core;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Strategies;
|
||||
@@ -26,18 +24,22 @@ namespace Managing.Application.Backtesting
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IBotFactory _botFactory;
|
||||
private readonly IScenarioService _scenarioService;
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public Backtester(
|
||||
IExchangeService exchangeService,
|
||||
IBotFactory botFactory,
|
||||
IBacktestRepository backtestRepository,
|
||||
ILogger<Backtester> logger, IScenarioService scenarioService)
|
||||
ILogger<Backtester> logger,
|
||||
IScenarioService scenarioService,
|
||||
IAccountService accountService)
|
||||
{
|
||||
_exchangeService = exchangeService;
|
||||
_botFactory = botFactory;
|
||||
_backtestRepository = backtestRepository;
|
||||
_logger = logger;
|
||||
_scenarioService = scenarioService;
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
|
||||
@@ -54,45 +56,19 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunScalpingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
Ticker ticker,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe,
|
||||
decimal balance,
|
||||
TradingBotConfig config,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle>? initialCandles = null,
|
||||
int cooldownPeriod = 1,
|
||||
int maxLossStreak = 0,
|
||||
decimal? maxPositionTimeHours = null,
|
||||
bool flipOnlyWhenInProfit = true)
|
||||
List<Candle>? initialCandles = null)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
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 account = await GetAccountFromConfig(config);
|
||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
|
||||
scalpingBot.LoadScenario(scenario.Name);
|
||||
scalpingBot.LoadScenario(config.ScenarioName);
|
||||
scalpingBot.User = user;
|
||||
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);
|
||||
|
||||
if (user != null)
|
||||
@@ -113,46 +89,20 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
Ticker ticker,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe,
|
||||
decimal balance,
|
||||
TradingBotConfig config,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle>? initialCandles = null,
|
||||
int cooldownPeriod = 1,
|
||||
int maxLossStreak = 0,
|
||||
decimal? maxPositionTimeHours = null,
|
||||
bool flipOnlyWhenInProfit = true)
|
||||
List<Candle>? initialCandles = null)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
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 account = await GetAccountFromConfig(config);
|
||||
var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
|
||||
flippingBot.LoadScenario(scenario.Name);
|
||||
flippingBot.LoadScenario(config.ScenarioName);
|
||||
flippingBot.User = user;
|
||||
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);
|
||||
|
||||
if (user != null)
|
||||
@@ -173,38 +123,13 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunScalpingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe,
|
||||
TradingBotConfig config,
|
||||
List<Candle> candles,
|
||||
decimal balance,
|
||||
User user = null,
|
||||
int cooldownPeriod = 1,
|
||||
int maxLossStreak = 0,
|
||||
decimal? maxPositionTimeHours = null,
|
||||
bool flipOnlyWhenInProfit = true)
|
||||
User user = null)
|
||||
{
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||
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 account = await GetAccountFromConfig(config);
|
||||
var bot = _botFactory.CreateBacktestScalpingBot(config);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
bot.LoadScenario(config.ScenarioName);
|
||||
bot.User = user;
|
||||
await bot.LoadAccount();
|
||||
|
||||
@@ -219,38 +144,13 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe,
|
||||
TradingBotConfig config,
|
||||
List<Candle> candles,
|
||||
decimal balance,
|
||||
User user = null,
|
||||
int cooldownPeriod = 1,
|
||||
int maxLossStreak = 0,
|
||||
decimal? maxPositionTimeHours = null,
|
||||
bool flipOnlyWhenInProfit = true)
|
||||
User user = null)
|
||||
{
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||
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 account = await GetAccountFromConfig(config);
|
||||
var bot = _botFactory.CreateBacktestFlippingBot(config);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
bot.LoadScenario(config.ScenarioName);
|
||||
bot.User = user;
|
||||
await bot.LoadAccount();
|
||||
|
||||
@@ -264,6 +164,23 @@ namespace Managing.Application.Backtesting
|
||||
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,
|
||||
DateTime startDate, DateTime endDate)
|
||||
{
|
||||
|
||||
@@ -468,28 +468,55 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
var currentTime = Config.IsForBacktest ? lastCandle.Date : DateTime.UtcNow;
|
||||
|
||||
// Check if position has exceeded maximum time limit (only if MaxPositionTimeHours is set)
|
||||
if (Config.MaxPositionTimeHours.HasValue && HasPositionExceededTimeLimit(positionForSignal, currentTime))
|
||||
// Check time-based position management (only if MaxPositionTimeHours is set)
|
||||
if (Config.MaxPositionTimeHours.HasValue)
|
||||
{
|
||||
// Check if position is in profit or at breakeven before closing
|
||||
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
|
||||
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 due to time limit - Position opened at {positionForSignal.Open.Date}, " +
|
||||
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
|
||||
$"Position is {(isPositionInProfit ? "in profit" : "at breakeven")} (entry: {positionForSignal.Open.Price}, current: {lastCandle.Close})");
|
||||
$"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;
|
||||
}
|
||||
else
|
||||
|
||||
// Time limit exceeded logic
|
||||
if (hasExceededTimeLimit)
|
||||
{
|
||||
await LogInformation(
|
||||
$"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " +
|
||||
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
|
||||
$"Waiting for profit or breakeven before closing.");
|
||||
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(
|
||||
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
|
||||
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
|
||||
$"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);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogInformation(
|
||||
$"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " +
|
||||
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
|
||||
$"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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Updates the trading bot configuration with new settings.
|
||||
/// This method validates the new configuration and applies it to the running bot.
|
||||
|
||||
Reference in New Issue
Block a user