From 59c5de7df7eddc53b54058c1b96131027ddcdfc8 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Fri, 4 Jul 2025 11:02:53 +0700 Subject: [PATCH] Push merge conflict --- README-API.md | 43 +- .../Controllers/BacktestController.cs | 39 +- src/Managing.Api/Controllers/BotController.cs | 18 +- .../Requests/TradingBotConfigRequest.cs | 8 +- .../StatisticService.cs | 1 - .../Abstractions/IBotFactory.cs | 12 +- .../Backtesting/Backtester.cs | 6 +- .../Bots/Base/BotFactory.cs | 62 +- src/Managing.Application/Bots/TradingBot.cs | 5 +- .../ManageBot/Commands/StopBotCommand.cs | 11 +- .../ManageBot/StartBotCommandHandler.cs | 27 +- .../ManageBot/StopBotCommandHandler.cs | 2 +- src/Managing.Domain/Backtests/Backtest.cs | 2 - src/Managing.Domain/Bots/TradingBotConfig.cs | 7 +- .../MongoDb/Collections/BacktestDto.cs | 16 +- .../MongoDb/MongoMappers.cs | 145 ++- .../src/components/mollecules/Modal/Modal.tsx | 1 + .../components/mollecules/NavBar/NavBar.tsx | 2 - .../src/components/mollecules/Tabs/Tabs.tsx | 8 +- .../organism/Backtest/backtestCards.tsx | 679 +++++++------- .../organism/Backtest/backtestRowDetails.tsx | 7 +- .../organism/Backtest/backtestTable.tsx | 878 +++++++++--------- .../Trading/TradeChart/TradeChart.tsx | 99 +- .../UnifiedTradingModal.tsx | 118 +-- .../src/generated/ManagingApi.ts | 23 +- .../src/generated/ManagingApiTypes.ts | 17 +- .../src/pages/botsPage/botList.tsx | 15 +- 27 files changed, 1133 insertions(+), 1118 deletions(-) diff --git a/README-API.md b/README-API.md index 9a5ec55..a96878c 100644 --- a/README-API.md +++ b/README-API.md @@ -155,11 +155,10 @@ The `TradingBotConfig` class defines all configuration parameters for trading bo | `Timeframe` | `Timeframe` | Candle timeframe for analysis | | `IsForWatchingOnly` | `bool` | If true, bot only sends signals without trading | | `BotTradingBalance` | `decimal` | Initial trading balance for the bot | -| `BotType` | `BotType` | Type of trading bot behavior | | `IsForBacktest` | `bool` | Whether this config is for backtesting | | `CooldownPeriod` | `int` | Number of candles to wait before opening new position in same direction | | `MaxLossStreak` | `int` | Maximum consecutive losses before requiring opposite direction signal (0 = no limit) | -| `FlipPosition` | `bool` | Whether the bot can flip positions | +| `FlipPosition` | `bool` | Whether the bot can flip positions (default: false) | | `Name` | `string` | Unique identifier/name for the bot | | `FlipOnlyWhenInProfit` | `bool` | Only flip positions when current position is profitable (default: true) | @@ -183,8 +182,10 @@ The `TradingBotConfig` class defines all configuration parameters for trading bo - Only closes when position is in profit or at breakeven (never closes at a loss due to time) - `CloseEarlyWhenProfitable` allows immediate closure when profitable instead of waiting full duration -**Profit-Controlled Flipping:** -- `FlipOnlyWhenInProfit` ensures safer trading by only flipping profitable positions +**Bot Behavior Control:** +- `IsForWatchingOnly` controls whether the bot executes actual trades or only analyzes and generates signals +- `FlipPosition` controls whether the bot can flip positions when opposite signals occur (default: false) +- `FlipOnlyWhenInProfit` ensures safer trading by only flipping profitable positions when flipping is enabled - Helps prevent cascading losses in volatile markets **Synth API Integration:** @@ -213,27 +214,22 @@ The `TradingBotConfig` class defines all configuration parameters for trading bo | CloseEarlyWhenProfitable | Close positions early when profitable (requires MaxPositionTimeHours) | false | | BotTradingBalance | Initial trading balance for the bot | Required | -### Bot Types +### Bot Behavior Configuration -The `BotType` enum in `TradingBotConfig` defines the following trading bot behaviors: +Trading bots support flexible behavior configuration through boolean flags rather than predefined types: -| Type | Description | -|-------------|----------------------------------------------------------------------------------------| -| SimpleBot | Basic bot implementation for simple trading strategies | -| ScalpingBot | Opens positions and waits for cooldown period before opening new ones in same direction | -| FlippingBot | Advanced bot that can flip positions when opposite signals are triggered | +#### Watch-Only Mode +- **`IsForWatchingOnly`**: When `true`, the bot analyzes market conditions and generates signals but does not execute actual trades +- Perfect for strategy testing, signal validation, or paper trading +- Useful for developing confidence in a strategy before committing real capital -#### Flipping Mode Configuration - -The flipping behavior is controlled by several `TradingBotConfig` properties: - -- **`BotType`**: Set to `FlippingBot` to enable position flipping capabilities -- **`FlipPosition`**: Boolean flag that enables/disables position flipping (automatically set based on BotType) +#### Position Flipping +- **`FlipPosition`**: When `true`, enables the bot to flip positions when opposite signals are triggered - **`FlipOnlyWhenInProfit`**: Safety feature that only allows flipping when current position is profitable (default: true) -#### How Flipping Works +#### How Position Flipping Works -**FlippingBot Behavior:** +**When Flipping is Enabled (`FlipPosition = true`):** 1. Opens initial position based on scenario signals 2. Monitors for opposite direction signals from the same scenario 3. When opposite signal occurs: @@ -242,11 +238,12 @@ The flipping behavior is controlled by several `TradingBotConfig` properties: 4. Closes current position and immediately opens new position in opposite direction 5. Continues this cycle for the duration of the bot's operation -**ScalpingBot vs FlippingBot:** -- **ScalpingBot**: Opens position → Waits for exit signal → Closes → Cooldown → Opens new position -- **FlippingBot**: Opens position → Monitors for opposite signals → Flips immediately (no cooldown between flips) +**When Flipping is Disabled (`FlipPosition = false`):** +- Opens position → Waits for exit signal → Closes → Cooldown → Opens new position in same direction +- More conservative approach with cooldown periods between trades +- Reduces frequency of trades and potential whipsaw losses -This configuration allows for more aggressive trading strategies while maintaining risk management through the profit-controlled flipping mechanism. +This simplified configuration provides clear control over bot behavior while maintaining risk management through the profit-controlled flipping mechanism. ## Backtesting (`BacktestController`) diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 04cbc7f..88d7cb9 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -10,7 +10,6 @@ using Managing.Domain.Strategies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; -using static Managing.Common.Enums; namespace Managing.Api.Controllers; @@ -192,15 +191,14 @@ public class BacktestController : BaseController ScenarioName = request.Config.ScenarioName, Scenario = scenario, // Use the converted scenario object Timeframe = request.Config.Timeframe, - IsForWatchingOnly = request.WatchOnly, - BotTradingBalance = request.Balance, - BotType = request.Config.BotType, + IsForWatchingOnly = request.Config.IsForWatchingOnly, + BotTradingBalance = request.Config.BotTradingBalance, IsForBacktest = true, CooldownPeriod = request.Config.CooldownPeriod, MaxLossStreak = request.Config.MaxLossStreak, MaxPositionTimeHours = request.Config.MaxPositionTimeHours, FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit, - FlipPosition = request.Config.BotType == BotType.FlippingBot, // Computed based on BotType + FlipPosition = request.Config.FlipPosition, // Computed based on BotType Name = request.Config.Name ?? $"Backtest-{request.Config.ScenarioName ?? request.Config.Scenario?.Name ?? "Custom"}-{DateTime.UtcNow:yyyyMMdd-HHmmss}", CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable, @@ -210,21 +208,12 @@ public class BacktestController : BaseController UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss }; - switch (request.Config.BotType) - { - case BotType.SimpleBot: - // SimpleBot backtest not implemented yet - break; - case BotType.ScalpingBot: - case BotType.FlippingBot: - backtestResult = await _backtester.RunTradingBotBacktest( - backtestConfig, - request.StartDate, - request.EndDate, - user, - request.Save); - break; - } + backtestResult = await _backtester.RunTradingBotBacktest( + backtestConfig, + request.StartDate, + request.EndDate, + user, + request.Save); await NotifyBacktesingSubscriberAsync(backtestResult); @@ -281,16 +270,6 @@ public class RunBacktestRequest /// public DateTime EndDate { get; set; } - /// - /// The starting balance for the backtest - /// - public decimal Balance { get; set; } - - /// - /// Whether to only watch the backtest without executing trades - /// - public bool WatchOnly { get; set; } = false; - /// /// Whether to save the backtest results /// diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index 4d5b157..c81aa26 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -224,7 +224,6 @@ public class BotController : BaseController Timeframe = request.Config.Timeframe, IsForWatchingOnly = request.Config.IsForWatchingOnly, BotTradingBalance = request.Config.BotTradingBalance, - BotType = request.Config.BotType, CooldownPeriod = request.Config.CooldownPeriod, MaxLossStreak = request.Config.MaxLossStreak, MaxPositionTimeHours = request.Config.MaxPositionTimeHours, @@ -236,7 +235,7 @@ public class BotController : BaseController UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss, // Set computed/default properties IsForBacktest = false, - FlipPosition = request.Config.BotType == BotType.FlippingBot, + FlipPosition = request.Config.FlipPosition, Name = request.Config.Name }; @@ -256,12 +255,11 @@ public class BotController : BaseController /// /// Stops a bot specified by type and name. /// - /// The type of the bot to stop. /// The identifier of the bot to stop. /// A string indicating the result of the stop operation. [HttpGet] [Route("Stop")] - public async Task> Stop(BotType botType, string identifier) + public async Task> Stop(string identifier) { try { @@ -271,8 +269,8 @@ public class BotController : BaseController return Forbid("You don't have permission to stop this bot"); } - var result = await _mediator.Send(new StopBotCommand(botType, identifier)); - _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}"); + var result = await _mediator.Send(new StopBotCommand(identifier)); + _logger.LogInformation($"Bot identifier {identifier} is now {result}"); await NotifyBotSubscriberAsync(); @@ -343,7 +341,7 @@ public class BotController : BaseController foreach (var bot in userBots) { - await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier)); + await _mediator.Send(new StopBotCommand(bot.Identifier)); await _hubContext.Clients.All.SendAsync("SendNotification", $"Bot {bot.Identifier} paused by {user.Name}.", "Info"); } @@ -422,7 +420,8 @@ public class BotController : BaseController { // We can't directly restart a bot with just BotType and Name // Instead, stop the bot and then retrieve the backup to start it again - await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier)); + await _mediator.Send( + new StopBotCommand(bot.Identifier)); // Get the saved bot backup var backup = _botService.GetBotBackup(bot.Identifier); @@ -776,7 +775,6 @@ public class BotController : BaseController Timeframe = request.Config.Timeframe, IsForWatchingOnly = request.Config.IsForWatchingOnly, BotTradingBalance = request.Config.BotTradingBalance, - BotType = request.Config.BotType, CooldownPeriod = request.Config.CooldownPeriod, MaxLossStreak = request.Config.MaxLossStreak, MaxPositionTimeHours = request.Config.MaxPositionTimeHours, @@ -788,7 +786,7 @@ public class BotController : BaseController UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss, // Set computed/default properties IsForBacktest = false, - FlipPosition = request.Config.BotType == BotType.FlippingBot, + FlipPosition = request.Config.FlipPosition, Name = request.Config.Name }; diff --git a/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs b/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs index 1bf60c4..95e98f8 100644 --- a/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs +++ b/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs @@ -38,18 +38,14 @@ public class TradingBotConfigRequest [Required] public decimal BotTradingBalance { get; set; } - /// - /// The type of bot (SimpleBot, ScalpingBot, FlippingBot) - /// - [Required] - public BotType BotType { get; set; } - /// /// The name/identifier for this bot /// [Required] public string Name { get; set; } + [Required] public bool FlipPosition { get; set; } + /// /// Cooldown period between trades (in candles) /// diff --git a/src/Managing.Application.Workers/StatisticService.cs b/src/Managing.Application.Workers/StatisticService.cs index 4fad64b..03b551d 100644 --- a/src/Managing.Application.Workers/StatisticService.cs +++ b/src/Managing.Application.Workers/StatisticService.cs @@ -272,7 +272,6 @@ public class StatisticService : IStatisticService Timeframe = timeframe, IsForWatchingOnly = true, BotTradingBalance = 1000, - BotType = BotType.ScalpingBot, IsForBacktest = true, CooldownPeriod = 1, MaxLossStreak = 0, diff --git a/src/Managing.Application/Abstractions/IBotFactory.cs b/src/Managing.Application/Abstractions/IBotFactory.cs index 4431d55..952c0e6 100644 --- a/src/Managing.Application/Abstractions/IBotFactory.cs +++ b/src/Managing.Application/Abstractions/IBotFactory.cs @@ -6,25 +6,19 @@ namespace Managing.Application.Abstractions public interface IBotFactory { IBot CreateSimpleBot(string botName, Workflow workflow); - + /// /// Creates a trading bot using the unified TradingBot class /// /// The trading bot configuration /// ITradingBot instance ITradingBot CreateTradingBot(TradingBotConfig config); - + /// /// Creates a trading bot for backtesting using the unified TradingBot class /// /// The trading bot configuration /// ITradingBot instance configured for backtesting ITradingBot CreateBacktestTradingBot(TradingBotConfig config); - - // Legacy methods - these will use TradingBot internally but maintain backward compatibility - ITradingBot CreateScalpingBot(TradingBotConfig config); - ITradingBot CreateBacktestScalpingBot(TradingBotConfig config); - ITradingBot CreateFlippingBot(TradingBotConfig config); - ITradingBot CreateBacktestFlippingBot(TradingBotConfig config); } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index 15be376..f360c3c 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -114,7 +114,7 @@ namespace Managing.Application.Backtesting User user = null) { // Set FlipPosition based on BotType - config.FlipPosition = config.BotType == BotType.FlippingBot; + config.FlipPosition = config.FlipPosition; var tradingBot = _botFactory.CreateBacktestTradingBot(config); @@ -164,11 +164,11 @@ namespace Managing.Application.Backtesting private List GetCandles(Account account, Ticker ticker, Timeframe timeframe, DateTime startDate, DateTime endDate) { - var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, + var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker, startDate, timeframe, endDate).Result; if (candles == null || candles.Count == 0) - throw new Exception($"No candles for {ticker} on {account.Exchange}"); + throw new Exception($"No candles for {ticker} on {timeframe} timeframe"); return candles; } diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs index f0ecb99..3201740 100644 --- a/src/Managing.Application/Bots/Base/BotFactory.cs +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -3,7 +3,6 @@ using Managing.Application.Abstractions.Services; using Managing.Domain.Bots; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; -using static Managing.Common.Enums; namespace Managing.Application.Bots.Base { @@ -61,64 +60,5 @@ namespace Managing.Application.Bots.Base _botService, config); } - - // Legacy methods for backward compatibility - will be deprecated - ITradingBot IBotFactory.CreateScalpingBot(TradingBotConfig config) - { - config.BotType = BotType.ScalpingBot; - config.FlipPosition = false; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - _botService, - config); - } - - ITradingBot IBotFactory.CreateBacktestScalpingBot(TradingBotConfig config) - { - config.BotType = BotType.ScalpingBot; - config.IsForBacktest = true; - config.FlipPosition = false; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - _botService, - config); - } - - public ITradingBot CreateFlippingBot(TradingBotConfig config) - { - config.BotType = BotType.FlippingBot; - config.FlipPosition = true; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - _botService, - config); - } - - public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config) - { - config.BotType = BotType.FlippingBot; - config.IsForBacktest = true; - config.FlipPosition = true; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - _botService, - config); - } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 3e0034e..b540b0c 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -187,7 +187,7 @@ public class TradingBot : Bot, ITradingBot Logger.LogInformation($"____________________{Name}____________________"); Logger.LogInformation( - $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {Config.BotType} - Ticker : {Config.Ticker}"); + $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Ticker : {Config.Ticker}"); } var previousLastCandle = OptimizedCandles.LastOrDefault(); @@ -1450,7 +1450,6 @@ public class TradingBot : Bot, ITradingBot } // Protect critical properties that shouldn't change for running bots - var protectedBotType = Config.BotType; var protectedIsForBacktest = Config.IsForBacktest; var protectedName = allowNameChange ? newConfig.Name : Config.Name; @@ -1470,7 +1469,6 @@ public class TradingBot : Bot, ITradingBot Config = newConfig; // Restore protected properties - Config.BotType = protectedBotType; Config.IsForBacktest = protectedIsForBacktest; Config.Name = protectedName; @@ -1532,7 +1530,6 @@ public class TradingBot : Bot, ITradingBot Timeframe = Config.Timeframe, IsForWatchingOnly = Config.IsForWatchingOnly, BotTradingBalance = Config.BotTradingBalance, - BotType = Config.BotType, IsForBacktest = Config.IsForBacktest, CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, diff --git a/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs b/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs index d0132b1..5ed673c 100644 --- a/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs @@ -1,17 +1,14 @@ using MediatR; -using static Managing.Common.Enums; namespace Managing.Application.ManageBot.Commands { public class StopBotCommand : IRequest { - public string Name { get; } - public BotType BotType { get; } + public string Identifier { get; } - public StopBotCommand(BotType botType, string name) + public StopBotCommand(string identifier) { - BotType = botType; - Name = name; + Identifier = identifier; } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index f9b74d5..1466d4e 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -70,37 +70,25 @@ namespace Managing.Application.ManageBot Timeframe = request.Config.Timeframe, IsForWatchingOnly = request.Config.IsForWatchingOnly, BotTradingBalance = request.Config.BotTradingBalance, - BotType = request.Config.BotType, IsForBacktest = request.Config.IsForBacktest, CooldownPeriod = request.Config.CooldownPeriod > 0 ? request.Config.CooldownPeriod : 1, // Default to 1 if not set MaxLossStreak = request.Config.MaxLossStreak, MaxPositionTimeHours = request.Config.MaxPositionTimeHours, // Properly handle nullable value FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit, - FlipPosition = request.Config.BotType == BotType.FlippingBot, // Set FlipPosition based on BotType + FlipPosition = request.Config.FlipPosition, // Set FlipPosition Name = request.Config.Name ?? request.Name, CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable }; - switch (configToUse.BotType) - { - case BotType.SimpleBot: - var bot = _botFactory.CreateSimpleBot(request.Name, null); - bot.User = request.User; - _botService.AddSimpleBotToCache(bot); - return bot.GetStatus(); + var tradingBot = _botFactory.CreateTradingBot(configToUse); + tradingBot.User = request.User; - case BotType.ScalpingBot: - case BotType.FlippingBot: - var tradingBot = _botFactory.CreateTradingBot(configToUse); - tradingBot.User = request.User; + // Log the configuration being used + await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created"); - // Log the configuration being used - await LogBotConfigurationAsync(tradingBot, $"{configToUse.BotType} created"); - - _botService.AddTradingBotToCache(tradingBot); - return tradingBot.GetStatus(); - } + _botService.AddTradingBotToCache(tradingBot); + return tradingBot.GetStatus(); return botStatus.ToString(); } @@ -116,7 +104,6 @@ namespace Managing.Application.ManageBot { var config = bot.GetConfiguration(); var logMessage = $"{context} - Bot: {config.Name}, " + - $"Type: {config.BotType}, " + $"Account: {config.AccountName}, " + $"Ticker: {config.Ticker}, " + $"Balance: {config.BotTradingBalance}, " + diff --git a/src/Managing.Application/ManageBot/StopBotCommandHandler.cs b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs index e92bbb4..df0a3a6 100644 --- a/src/Managing.Application/ManageBot/StopBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs @@ -15,7 +15,7 @@ namespace Managing.Application.ManageBot public Task Handle(StopBotCommand request, CancellationToken cancellationToken) { - return _botService.StopBot(request.Name); + return _botService.StopBot(request.Identifier); } } } \ No newline at end of file diff --git a/src/Managing.Domain/Backtests/Backtest.cs b/src/Managing.Domain/Backtests/Backtest.cs index b5dbd3a..dcd7984 100644 --- a/src/Managing.Domain/Backtests/Backtest.cs +++ b/src/Managing.Domain/Backtests/Backtest.cs @@ -82,7 +82,6 @@ public class Backtest Timeframe = Config.Timeframe, IsForWatchingOnly = false, // Always start as active bot BotTradingBalance = initialTradingBalance, - BotType = Config.BotType, IsForBacktest = false, // Always false for live bots CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, @@ -117,7 +116,6 @@ public class Backtest Timeframe = Config.Timeframe, IsForWatchingOnly = Config.IsForWatchingOnly, BotTradingBalance = balance, - BotType = Config.BotType, IsForBacktest = true, CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, diff --git a/src/Managing.Domain/Bots/TradingBotConfig.cs b/src/Managing.Domain/Bots/TradingBotConfig.cs index 1eb163a..71c121e 100644 --- a/src/Managing.Domain/Bots/TradingBotConfig.cs +++ b/src/Managing.Domain/Bots/TradingBotConfig.cs @@ -14,7 +14,6 @@ public class TradingBotConfig [Required] public Timeframe Timeframe { get; set; } [Required] public bool IsForWatchingOnly { get; set; } [Required] public decimal BotTradingBalance { get; set; } - [Required] public BotType BotType { get; set; } [Required] public bool IsForBacktest { get; set; } [Required] public int CooldownPeriod { get; set; } [Required] public int MaxLossStreak { get; set; } @@ -68,17 +67,17 @@ public class TradingBotConfig /// The actual Synth configuration is managed centrally in SynthPredictionService. /// public bool UseSynthApi { get; set; } = false; - + /// /// Whether to use Synth predictions for position sizing adjustments and risk assessment /// public bool UseForPositionSizing { get; set; } = true; - + /// /// Whether to use Synth predictions for signal filtering /// public bool UseForSignalFiltering { get; set; } = true; - + /// /// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments /// diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs index 140be3e..22fc401 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs @@ -1,7 +1,6 @@ using Exilion.TradingAtomics; using Managing.Infrastructure.Databases.MongoDb.Attributes; using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; namespace Managing.Infrastructure.Databases.MongoDb.Collections { @@ -12,6 +11,7 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections public int WinRate { get; set; } public decimal GrowthPercentage { get; set; } public decimal HodlPercentage { get; set; } + public TradingBotConfigDto Config { get; set; } public List Positions { get; set; } public List Signals { get; set; } public List Candles { get; set; } @@ -22,17 +22,5 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections public UserDto User { get; set; } public PerformanceMetrics Statistics { get; set; } public double Score { get; set; } - - // TradingBotConfig properties - public string AccountName { get; set; } - public Ticker Ticker { get; set; } - public string ScenarioName { get; set; } - public Timeframe Timeframe { get; set; } - public bool IsForWatchingOnly { get; set; } - public decimal BotTradingBalance { get; set; } - public BotType BotType { get; set; } - public bool IsForBacktest { get; set; } - public int CooldownPeriod { get; set; } - public int MaxLossStreak { get; set; } } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs index 0782a3f..fe0d065 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -4,6 +4,7 @@ using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; +using Managing.Domain.Risk; using Managing.Domain.Scenarios; using Managing.Domain.Statistics; using Managing.Domain.Strategies; @@ -132,20 +133,7 @@ public static class MongoMappers if (b == null) return null; - var config = new TradingBotConfig - { - AccountName = b.AccountName, - Ticker = b.Ticker, - ScenarioName = b.ScenarioName, - Timeframe = b.Timeframe, - IsForWatchingOnly = b.IsForWatchingOnly, - BotTradingBalance = b.BotTradingBalance, - BotType = b.BotType, - IsForBacktest = b.IsForBacktest, - CooldownPeriod = b.CooldownPeriod, - MaxLossStreak = b.MaxLossStreak, - MoneyManagement = Map(b.MoneyManagement) - }; + var config = Map(b.Config); var bTest = new Backtest( config, @@ -181,6 +169,7 @@ public static class MongoMappers WinRate = result.WinRate, GrowthPercentage = result.GrowthPercentage, HodlPercentage = result.HodlPercentage, + Config = Map(result.Config), Positions = Map(result.Positions), Signals = result.Signals.Select(s => Map(s)).ToList(), Candles = result.Candles.Select(c => Map(c)).ToList(), @@ -191,16 +180,6 @@ public static class MongoMappers StartDate = result.StartDate, EndDate = result.EndDate, Score = result.Score, - AccountName = result.Config.AccountName, - Ticker = result.Config.Ticker, - ScenarioName = result.Config.ScenarioName, - Timeframe = result.Config.Timeframe, - IsForWatchingOnly = result.Config.IsForWatchingOnly, - BotTradingBalance = result.Config.BotTradingBalance, - BotType = result.Config.BotType, - IsForBacktest = result.Config.IsForBacktest, - CooldownPeriod = result.Config.CooldownPeriod, - MaxLossStreak = result.Config.MaxLossStreak }; } @@ -933,4 +912,122 @@ public static class MongoMappers } #endregion + + #region TradingBotConfig + + public static TradingBotConfigDto Map(TradingBotConfig config) + { + if (config == null) + return null; + + return new TradingBotConfigDto + { + AccountName = config.AccountName, + MoneyManagement = Map(config.MoneyManagement), + Ticker = config.Ticker, + Timeframe = config.Timeframe, + IsForWatchingOnly = config.IsForWatchingOnly, + BotTradingBalance = config.BotTradingBalance, + IsForBacktest = config.IsForBacktest, + CooldownPeriod = config.CooldownPeriod, + MaxLossStreak = config.MaxLossStreak, + FlipPosition = config.FlipPosition, + Name = config.Name, + RiskManagement = Map(config.RiskManagement), + Scenario = Map(config.Scenario), + ScenarioName = config.ScenarioName, + MaxPositionTimeHours = config.MaxPositionTimeHours, + CloseEarlyWhenProfitable = config.CloseEarlyWhenProfitable, + FlipOnlyWhenInProfit = config.FlipOnlyWhenInProfit, + UseSynthApi = config.UseSynthApi, + UseForPositionSizing = config.UseForPositionSizing, + UseForSignalFiltering = config.UseForSignalFiltering, + UseForDynamicStopLoss = config.UseForDynamicStopLoss + }; + } + + public static TradingBotConfig Map(TradingBotConfigDto dto) + { + if (dto == null) + return null; + + return new TradingBotConfig + { + AccountName = dto.AccountName, + MoneyManagement = Map(dto.MoneyManagement), + Ticker = dto.Ticker, + Timeframe = dto.Timeframe, + IsForWatchingOnly = dto.IsForWatchingOnly, + BotTradingBalance = dto.BotTradingBalance, + IsForBacktest = dto.IsForBacktest, + CooldownPeriod = dto.CooldownPeriod, + MaxLossStreak = dto.MaxLossStreak, + FlipPosition = dto.FlipPosition, + Name = dto.Name, + RiskManagement = Map(dto.RiskManagement), + Scenario = Map(dto.Scenario), + ScenarioName = dto.ScenarioName, + MaxPositionTimeHours = dto.MaxPositionTimeHours, + CloseEarlyWhenProfitable = dto.CloseEarlyWhenProfitable, + FlipOnlyWhenInProfit = dto.FlipOnlyWhenInProfit, + UseSynthApi = dto.UseSynthApi, + UseForPositionSizing = dto.UseForPositionSizing, + UseForSignalFiltering = dto.UseForSignalFiltering, + UseForDynamicStopLoss = dto.UseForDynamicStopLoss + }; + } + + #endregion + + #region RiskManagement + + public static RiskManagementDto Map(RiskManagement riskManagement) + { + if (riskManagement == null) + return null; + + return new RiskManagementDto + { + AdverseProbabilityThreshold = riskManagement.AdverseProbabilityThreshold, + FavorableProbabilityThreshold = riskManagement.FavorableProbabilityThreshold, + RiskAversion = riskManagement.RiskAversion, + KellyMinimumThreshold = riskManagement.KellyMinimumThreshold, + KellyMaximumCap = riskManagement.KellyMaximumCap, + MaxLiquidationProbability = riskManagement.MaxLiquidationProbability, + SignalValidationTimeHorizonHours = riskManagement.SignalValidationTimeHorizonHours, + PositionMonitoringTimeHorizonHours = riskManagement.PositionMonitoringTimeHorizonHours, + PositionWarningThreshold = riskManagement.PositionWarningThreshold, + PositionAutoCloseThreshold = riskManagement.PositionAutoCloseThreshold, + KellyFractionalMultiplier = riskManagement.KellyFractionalMultiplier, + RiskTolerance = riskManagement.RiskTolerance, + UseExpectedUtility = riskManagement.UseExpectedUtility, + UseKellyCriterion = riskManagement.UseKellyCriterion + }; + } + + public static RiskManagement Map(RiskManagementDto dto) + { + if (dto == null) + return null; + + return new RiskManagement + { + AdverseProbabilityThreshold = dto.AdverseProbabilityThreshold, + FavorableProbabilityThreshold = dto.FavorableProbabilityThreshold, + RiskAversion = dto.RiskAversion, + KellyMinimumThreshold = dto.KellyMinimumThreshold, + KellyMaximumCap = dto.KellyMaximumCap, + MaxLiquidationProbability = dto.MaxLiquidationProbability, + SignalValidationTimeHorizonHours = dto.SignalValidationTimeHorizonHours, + PositionMonitoringTimeHorizonHours = dto.PositionMonitoringTimeHorizonHours, + PositionWarningThreshold = dto.PositionWarningThreshold, + PositionAutoCloseThreshold = dto.PositionAutoCloseThreshold, + KellyFractionalMultiplier = dto.KellyFractionalMultiplier, + RiskTolerance = dto.RiskTolerance, + UseExpectedUtility = dto.UseExpectedUtility, + UseKellyCriterion = dto.UseKellyCriterion + }; + } + + #endregion } \ No newline at end of file diff --git a/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx b/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx index d29ddb8..e1e20b0 100644 --- a/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx +++ b/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx @@ -21,6 +21,7 @@ const Modal: React.FC = ({ titleHeader={titleHeader} onClose={onClose} onSubmit={onSubmit} + showModal={showModal} /> {children} diff --git a/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx index 4b53968..2c1b034 100644 --- a/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx +++ b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx @@ -10,9 +10,7 @@ import Logo from '../../../assets/img/logo.png' import {Loader} from '../../atoms' const navigation = [ - { href: '/desk', name: 'Desk' }, { href: '/bots', name: 'Bots' }, - { href: '/workflow', name: 'Workflows' }, { href: '/scenarios', name: 'Scenarios' }, { href: '/backtest', name: 'Backtest' }, { href: '/tools', name: 'Tools' }, diff --git a/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx b/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx index 1f06e88..47889b0 100644 --- a/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx +++ b/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx @@ -1,6 +1,6 @@ -import type { FC } from 'react' +import type {FC} from 'react' -import type { ITabsProps } from '../../../global/type' +import type {ITabsProps} from '../../../global/type.tsx' /** * Avalible Props @@ -19,7 +19,7 @@ const Tabs: FC = ({ addButton = false, onAddButton, }) => { - const Panel = tabs && tabs.find((tab) => tab.index === selectedTab) + const Panel = tabs && tabs.find((tab: any) => tab.index === selectedTab) return (
= ({ } >
- {tabs.map((tab) => ( + {tabs.map((tab: any) => ( -
+ // Calculate dates for the API call + const startDate = backtest.candles[0].date + const endDate = backtest.candles[backtest.candles.length - 1].date -
-
- { - - } -
+ // Create optimized backtest config + const optimizedConfig: TradingBotConfig = { + ...backtest.config, + name: `${backtest.config.ticker}-${backtest.config.scenarioName}-Optimized`, + moneyManagement: backtest.optimizedMoneyManagement || backtest.config.moneyManagement + } -
-

-
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
- {backtest.config.ticker} - {botStatusResult( - backtest.growthPercentage, - backtest.hodlPercentage - )} -

-
-
- - - - -
-
-
- - {/* */} - { - const realized = p.profitAndLoss?.realized ?? 0 - return realized > 0 ? p : null - })} - > - { - const realized = p.profitAndLoss?.realized ?? 0 - return realized <= 0 ? p : null - })} - > - + const request: RunBacktestRequest = { + config: optimizedConfig as unknown as TradingBotConfigRequest, + startDate: startDate, + endDate: endDate, + save: false, + } + + await client + .backtest_Run(request) + .then((backtest: Backtest) => { + t.update('success', `${backtest.config.ticker} Backtest Succeeded`) + setBacktests((arr: Backtest[]) => [...arr, backtest]) + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + function saveMoneyManagement(moneyManagement: MoneyManagement) { + setSelectedMoneyManagement(moneyManagement) + setShowMoneyManagementModal(true) + } + + return ( +
+ {list?.map((backtest: Backtest, index: number) => ( +
+
+
+ +
+ +
+
+ { + + } +
+ +
+

+
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ {backtest.config.ticker} + {botStatusResult( + backtest.growthPercentage, + backtest.hodlPercentage + )} +

+
+
+ + + + +
+
+
+ + {/* */} + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized > 0 ? p : null + })} + > + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized <= 0 ? p : null + })} + > + +
+ +
+
+ + + + +
+
+
+
+ WR {backtest.winRate?.toFixed(2).toString()} % +
+
+ PNL {backtest.growthPercentage?.toFixed(2).toString()} % +
+
+
+
+
+ ))} -
-
- - - - -
-
-
-
- WR {backtest.winRate?.toFixed(2).toString()} % -
-
- PNL {backtest.growthPercentage?.toFixed(2).toString()} % -
-
-
-
-
+ setShowMoneyManagementModal(false)} + /> + + {showBotNameModal && currentBacktest && moneyManagements && ( + + handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) + } + moneyManagements={moneyManagements} + selectedMoneyManagement={selectedMoneyManagementName} + setSelectedMoneyManagement={setSelectedMoneyManagementName} + /> + )}
- ))} - - setShowMoneyManagementModal(false)} - /> - - {showBotNameModal && currentBacktest && moneyManagements && ( - - handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) - } - moneyManagements={moneyManagements} - selectedMoneyManagement={selectedMoneyManagementName} - setSelectedMoneyManagement={setSelectedMoneyManagementName} - /> - )} -
- ) + ) } export default BacktestCards diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx index 0cef63d..f7dc341 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx @@ -351,16 +351,15 @@ const BacktestRowDetails: React.FC = ({ content={getAverageTradesPerDay() + " trades/day"} > -
-
+
+
diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx index 1044f79..9ed1a2c 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx @@ -4,491 +4,491 @@ import React, {useEffect, useState} from 'react' import useApiUrlStore from '../../../app/store/apiStore' import type {Backtest} from '../../../generated/ManagingApi' import {BacktestClient} from '../../../generated/ManagingApi' -import type {IBacktestCards} from '../../../global/type' +import type {IBacktestCards} from '../../../global/type.tsx' import {CardText, SelectColumnFilter, Table} from '../../mollecules' import {UnifiedTradingModal} from '../index' import Toast from '../../mollecules/Toast/Toast' import BacktestRowDetails from './backtestRowDetails' -const BacktestTable: React.FC = ({ list, isFetching, setBacktests }) => { - const [rows, setRows] = useState([]) - const { apiUrl } = useApiUrlStore() - const [optimizedMoneyManagement, setOptimizedMoneyManagement] = useState({ - stopLoss: 0, - takeProfit: 0, - }) - const [positionTimingStats, setPositionTimingStats] = useState({ - averageOpenTime: 0, - medianOpenTime: 0, - losingPositionsAverageOpenTime: 0, - }) - const [cooldownRecommendations, setCooldownRecommendations] = useState({ - averageCooldown: 0, - medianCooldown: 0, - }) - - // Bot configuration modal state - const [showBotConfigModal, setShowBotConfigModal] = useState(false) - const [selectedBacktest, setSelectedBacktest] = useState(null) +const BacktestTable: React.FC = ({list, isFetching, setBacktests}) => { + const [rows, setRows] = useState([]) + const {apiUrl} = useApiUrlStore() + const [optimizedMoneyManagement, setOptimizedMoneyManagement] = useState({ + stopLoss: 0, + takeProfit: 0, + }) + const [positionTimingStats, setPositionTimingStats] = useState({ + averageOpenTime: 0, + medianOpenTime: 0, + losingPositionsAverageOpenTime: 0, + }) + const [cooldownRecommendations, setCooldownRecommendations] = useState({ + averageCooldown: 0, + medianCooldown: 0, + }) - // Backtest configuration modal state - const [showBacktestConfigModal, setShowBacktestConfigModal] = useState(false) - const [selectedBacktestForRerun, setSelectedBacktestForRerun] = useState(null) + // Bot configuration modal state + const [showBotConfigModal, setShowBotConfigModal] = useState(false) + const [selectedBacktest, setSelectedBacktest] = useState(null) - const handleOpenBotConfigModal = (backtest: Backtest) => { - setSelectedBacktest(backtest) - setShowBotConfigModal(true) - } + // Backtest configuration modal state + const [showBacktestConfigModal, setShowBacktestConfigModal] = useState(false) + const [selectedBacktestForRerun, setSelectedBacktestForRerun] = useState(null) - const handleCloseBotConfigModal = () => { - setShowBotConfigModal(false) - setSelectedBacktest(null) - } + const handleOpenBotConfigModal = (backtest: Backtest) => { + setSelectedBacktest(backtest) + setShowBotConfigModal(true) + } - const handleOpenBacktestConfigModal = (backtest: Backtest) => { - setSelectedBacktestForRerun(backtest) - setShowBacktestConfigModal(true) - } + const handleCloseBotConfigModal = () => { + setShowBotConfigModal(false) + setSelectedBacktest(null) + } - const handleCloseBacktestConfigModal = () => { - setShowBacktestConfigModal(false) - setSelectedBacktestForRerun(null) - } + const handleOpenBacktestConfigModal = (backtest: Backtest) => { + setSelectedBacktestForRerun(backtest) + setShowBacktestConfigModal(true) + } - async function deleteBacktest(id: string) { - const t = new Toast('Deleting backtest') - const client = new BacktestClient({}, apiUrl) + const handleCloseBacktestConfigModal = () => { + setShowBacktestConfigModal(false) + setSelectedBacktestForRerun(null) + } - await client - .backtest_DeleteBacktest(id) - .then(() => { - t.update('success', 'Backtest deleted') - // Remove the deleted backtest from the list - if (list) { - const updatedList = list.filter(backtest => backtest.id !== id); - setBacktests(updatedList); - } - }) - .catch((err) => { - t.update('error', err) - }) - } + async function deleteBacktest(id: string) { + const t = new Toast('Deleting backtest') + const client = new BacktestClient({}, apiUrl) - const getScoreColor = (score: number) => { - if (score >= 75) return '#08C25F'; // success - if (score >= 50) return '#B0DB43'; // info - if (score >= 25) return '#EB6F22'; // warning - return '#FF5340'; // error - }; + await client + .backtest_DeleteBacktest(id) + .then(() => { + t.update('success', 'Backtest deleted') + // Remove the deleted backtest from the list + if (list) { + const updatedList = list.filter(backtest => backtest.id !== id); + setBacktests(updatedList); + } + }) + .catch((err) => { + t.update('error', err) + }) + } - const columns = React.useMemo( - () => [ - { - Header: 'Informations', - columns: [ - { - Cell: ({ row }: any) => ( - // Use Cell to render an expander for each row. - // We can use the getToggleRowExpandedProps prop-getter - // to build the expander. - + const getScoreColor = (score: number) => { + if (score >= 75) return '#08C25F'; // success + if (score >= 50) return '#B0DB43'; // info + if (score >= 25) return '#EB6F22'; // warning + return '#FF5340'; // error + }; + + const columns = React.useMemo( + () => [ + { + Header: 'Informations', + columns: [ + { + Cell: ({row}: any) => ( + // Use Cell to render an expander for each row. + // We can use the getToggleRowExpandedProps prop-getter + // to build the expander. + {row.isExpanded ? ( - + ) : ( - + )} - ), + ), - // Make sure it has an ID - Header: ({ - getToggleAllRowsExpandedProps, - isAllRowsExpanded, - }: any) => ( - + // Make sure it has an ID + Header: ({ + getToggleAllRowsExpandedProps, + isAllRowsExpanded, + }: any) => ( + {isAllRowsExpanded ? 'v' : '>'} - ), - // Build our expander column - id: 'expander', - }, - { - Header: 'Score', - accessor: 'score', - Cell: ({ cell }: any) => ( - + ), + // Build our expander column + id: 'expander', + }, + { + Header: 'Score', + accessor: 'score', + Cell: ({cell}: any) => ( + {cell.row.values.score.toFixed(2)} - ), - disableFilters: true, - }, - { - Filter: SelectColumnFilter, - Header: 'Ticker', - accessor: 'config.ticker', - disableSortBy: true, - }, - - { - Filter: SelectColumnFilter, - Header: 'Timeframe', - accessor: 'config.timeframe', - disableSortBy: true, - }, - { - Filter: SelectColumnFilter, - Header: 'Scenario', - accessor: 'config.scenarioName', - disableSortBy: true, - }, - { - Filter: SelectColumnFilter, - Header: 'BotType', - accessor: 'config.botType', - disableSortBy: true, - }, + ), + disableFilters: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Ticker', + accessor: 'config.ticker', + disableSortBy: true, + }, + + { + Filter: SelectColumnFilter, + Header: 'Timeframe', + accessor: 'config.timeframe', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Scenario', + accessor: 'config.scenarioName', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'BotType', + accessor: 'config.botType', + disableSortBy: true, + }, + ], + }, + { + Header: 'Results', + columns: [ + { + Cell: ({cell}: any) => ( + <>{cell.row.values.finalPnl.toFixed(2)} $ + ), + Header: 'Pnl $', + accessor: 'finalPnl', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({cell}: any) => ( + <>{cell.row.values.hodlPercentage.toFixed(2)} % + ), + Header: 'Hodl %', + accessor: 'hodlPercentage', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({cell}: any) => <>{cell.row.values.winRate} %, + Header: 'Winrate', + accessor: 'winRate', + disableFilters: true, + }, + { + Cell: ({cell}: any) => ( + <>{cell.row.values.growthPercentage.toFixed(2)} % + ), + Header: 'Pnl %', + accessor: 'growthPercentage', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({cell}: any) => ( + <> + {( + cell.row.values.growthPercentage - + cell.row.values.hodlPercentage + ).toFixed(2)} + + ), + Header: 'H/P', + accessor: 'diff', + disableFilters: true, + sortType: 'basic', + }, + ], + }, + { + Header: 'Action', + columns: [ + { + Cell: ({cell}: any) => ( + <> +
+ +
+ + ), + Header: '', + accessor: 'id', + disableFilters: true, + }, + { + Cell: ({cell}: any) => ( + <> +
+ +
+ + ), + Header: '', + accessor: 'rerun', + disableFilters: true, + }, + { + Cell: ({cell}: any) => ( + <> +
+ +
+ + ), + Header: '', + accessor: 'runner', + disableFilters: true, + } + ], + }, ], - }, - { - Header: 'Results', - columns: [ - { - Cell: ({ cell }: any) => ( - <>{cell.row.values.finalPnl.toFixed(2)} $ - ), - Header: 'Pnl $', - accessor: 'finalPnl', - disableFilters: true, - sortType: 'basic', - }, - { - Cell: ({ cell }: any) => ( - <>{cell.row.values.hodlPercentage.toFixed(2)} % - ), - Header: 'Hodl %', - accessor: 'hodlPercentage', - disableFilters: true, - sortType: 'basic', - }, - { - Cell: ({ cell }: any) => <>{cell.row.values.winRate} %, - Header: 'Winrate', - accessor: 'winRate', - disableFilters: true, - }, - { - Cell: ({ cell }: any) => ( - <>{cell.row.values.growthPercentage.toFixed(2)} % - ), - Header: 'Pnl %', - accessor: 'growthPercentage', - disableFilters: true, - sortType: 'basic', - }, - { - Cell: ({ cell }: any) => ( - <> - {( - cell.row.values.growthPercentage - - cell.row.values.hodlPercentage - ).toFixed(2)} - - ), - Header: 'H/P', - accessor: 'diff', - disableFilters: true, - sortType: 'basic', - }, - ], - }, - { - Header: 'Action', - columns: [ - { - Cell: ({ cell }: any) => ( - <> -
- -
- - ), - Header: '', - accessor: 'id', - disableFilters: true, - }, - { - Cell: ({ cell }: any) => ( - <> -
- -
- - ), - Header: '', - accessor: 'rerun', - disableFilters: true, - }, - { - Cell: ({ cell }: any) => ( - <> -
- -
- - ), - Header: '', - accessor: 'runner', - disableFilters: true, - } - ], - }, - ], - [] - ) + [] + ) - useEffect(() => { - if (list) { - setRows(list) - - // Calculate average optimized money management - if (list.length > 0) { - const optimized = list.map((b) => b.optimizedMoneyManagement); - const stopLoss = optimized.reduce((acc, curr) => acc + (curr?.stopLoss ?? 0), 0); - const takeProfit = optimized.reduce((acc, curr) => acc + (curr?.takeProfit ?? 0), 0); - - setOptimizedMoneyManagement({ - stopLoss: stopLoss / optimized.length, - takeProfit: takeProfit / optimized.length, - }); + useEffect(() => { + if (list) { + setRows(list) - // Calculate position timing statistics - const allPositions = list.flatMap(backtest => backtest.positions); - const finishedPositions = allPositions.filter(p => p.status === 'Finished'); - - if (finishedPositions.length > 0) { - // Calculate position open times in hours - const openTimes = finishedPositions.map(position => { - const openTime = new Date(position.open.date); - // Find the closing trade (either stopLoss or takeProfit that was filled) - let closeTime = new Date(); - - if (position.stopLoss.status === 'Filled') { - closeTime = new Date(position.stopLoss.date); - } else if (position.takeProfit1.status === 'Filled') { - closeTime = new Date(position.takeProfit1.date); - } else if (position.takeProfit2?.status === 'Filled') { - closeTime = new Date(position.takeProfit2.date); - } - - // Return time difference in hours - return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60); - }); + // Calculate average optimized money management + if (list.length > 0) { + const optimized = list.map((b) => b.optimizedMoneyManagement); + const stopLoss = optimized.reduce((acc, curr) => acc + (curr?.stopLoss ?? 0), 0); + const takeProfit = optimized.reduce((acc, curr) => acc + (curr?.takeProfit ?? 0), 0); - // Calculate average - const averageOpenTime = openTimes.reduce((sum, time) => sum + time, 0) / openTimes.length; + setOptimizedMoneyManagement({ + stopLoss: stopLoss / optimized.length, + takeProfit: takeProfit / optimized.length, + }); - // Calculate median - const sortedTimes = [...openTimes].sort((a, b) => a - b); - const medianOpenTime = sortedTimes.length % 2 === 0 - ? (sortedTimes[sortedTimes.length / 2 - 1] + sortedTimes[sortedTimes.length / 2]) / 2 - : sortedTimes[Math.floor(sortedTimes.length / 2)]; + // Calculate position timing statistics + const allPositions = list.flatMap(backtest => backtest.positions); + const finishedPositions = allPositions.filter(p => p.status === 'Finished'); - // Calculate average for losing positions - const losingPositions = finishedPositions.filter(p => (p.profitAndLoss?.realized ?? 0) < 0); - let losingPositionsAverageOpenTime = 0; - - if (losingPositions.length > 0) { - const losingOpenTimes = losingPositions.map(position => { - const openTime = new Date(position.open.date); - let closeTime = new Date(); - - if (position.stopLoss.status === 'Filled') { - closeTime = new Date(position.stopLoss.date); - } else if (position.takeProfit1.status === 'Filled') { - closeTime = new Date(position.takeProfit1.date); - } else if (position.takeProfit2?.status === 'Filled') { - closeTime = new Date(position.takeProfit2.date); - } - - return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60); - }); - - losingPositionsAverageOpenTime = losingOpenTimes.reduce((sum, time) => sum + time, 0) / losingOpenTimes.length; - } + if (finishedPositions.length > 0) { + // Calculate position open times in hours + const openTimes = finishedPositions.map(position => { + const openTime = new Date(position.open.date); + // Find the closing trade (either stopLoss or takeProfit that was filled) + let closeTime = new Date(); - setPositionTimingStats({ - averageOpenTime, - medianOpenTime, - losingPositionsAverageOpenTime, - }); - } + if (position.stopLoss.status === 'Filled') { + closeTime = new Date(position.stopLoss.date); + } else if (position.takeProfit1.status === 'Filled') { + closeTime = new Date(position.takeProfit1.date); + } else if (position.takeProfit2?.status === 'Filled') { + closeTime = new Date(position.takeProfit2.date); + } - // Calculate cooldown recommendations across all backtests - const allCooldownValues: number[] = []; - - list.forEach(backtest => { - if (backtest.positions.length < 2 || !backtest.candles || backtest.candles.length < 2) { - return; - } + // Return time difference in hours + return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60); + }); - // Determine candle timeframe in milliseconds - const candleTimeframeMs = new Date(backtest.candles[1].date).getTime() - new Date(backtest.candles[0].date).getTime(); + // Calculate average + const averageOpenTime = openTimes.reduce((sum, time) => sum + time, 0) / openTimes.length; - const sortedPositions = [...backtest.positions].sort((a, b) => { - const dateA = new Date(a.open.date).getTime(); - const dateB = new Date(b.open.date).getTime(); - return dateA - dateB; - }); + // Calculate median + const sortedTimes = [...openTimes].sort((a, b) => a - b); + const medianOpenTime = sortedTimes.length % 2 === 0 + ? (sortedTimes[sortedTimes.length / 2 - 1] + sortedTimes[sortedTimes.length / 2]) / 2 + : sortedTimes[Math.floor(sortedTimes.length / 2)]; - for (let i = 0; i < sortedPositions.length - 1; i++) { - const currentPosition = sortedPositions[i]; - const nextPosition = sortedPositions[i + 1]; + // Calculate average for losing positions + const losingPositions = finishedPositions.filter(p => (p.profitAndLoss?.realized ?? 0) < 0); + let losingPositionsAverageOpenTime = 0; - const currentRealized = currentPosition.profitAndLoss?.realized ?? 0; - const nextRealized = nextPosition.profitAndLoss?.realized ?? 0; + if (losingPositions.length > 0) { + const losingOpenTimes = losingPositions.map(position => { + const openTime = new Date(position.open.date); + let closeTime = new Date(); - // Check if current position is winning and next position is losing - if (currentRealized > 0 && nextRealized <= 0) { - // Calculate the close time of the current (winning) position - let currentCloseDate: Date | null = null; - if (currentPosition.profitAndLoss?.realized != null) { - if (currentPosition.profitAndLoss.realized > 0) { - currentCloseDate = new Date(currentPosition.takeProfit1.date); - } else { - currentCloseDate = new Date(currentPosition.stopLoss.date); + if (position.stopLoss.status === 'Filled') { + closeTime = new Date(position.stopLoss.date); + } else if (position.takeProfit1.status === 'Filled') { + closeTime = new Date(position.takeProfit1.date); + } else if (position.takeProfit2?.status === 'Filled') { + closeTime = new Date(position.takeProfit2.date); + } + + return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60); + }); + + losingPositionsAverageOpenTime = losingOpenTimes.reduce((sum, time) => sum + time, 0) / losingOpenTimes.length; + } + + setPositionTimingStats({ + averageOpenTime, + medianOpenTime, + losingPositionsAverageOpenTime, + }); } - } - if (currentCloseDate) { - const nextOpenDate = new Date(nextPosition.open.date); - const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime(); - - if (gapInMs >= 0) { // Only consider positive gaps - // Convert milliseconds to number of candles - const gapInCandles = Math.floor(gapInMs / candleTimeframeMs); - allCooldownValues.push(gapInCandles); + // Calculate cooldown recommendations across all backtests + const allCooldownValues: number[] = []; + + list.forEach(backtest => { + if (backtest.positions.length < 2 || !backtest.candles || backtest.candles.length < 2) { + return; + } + + // Determine candle timeframe in milliseconds + const candleTimeframeMs = new Date(backtest.candles[1].date).getTime() - new Date(backtest.candles[0].date).getTime(); + + const sortedPositions = [...backtest.positions].sort((a, b) => { + const dateA = new Date(a.open.date).getTime(); + const dateB = new Date(b.open.date).getTime(); + return dateA - dateB; + }); + + for (let i = 0; i < sortedPositions.length - 1; i++) { + const currentPosition = sortedPositions[i]; + const nextPosition = sortedPositions[i + 1]; + + const currentRealized = currentPosition.profitAndLoss?.realized ?? 0; + const nextRealized = nextPosition.profitAndLoss?.realized ?? 0; + + // Check if current position is winning and next position is losing + if (currentRealized > 0 && nextRealized <= 0) { + // Calculate the close time of the current (winning) position + let currentCloseDate: Date | null = null; + if (currentPosition.profitAndLoss?.realized != null) { + if (currentPosition.profitAndLoss.realized > 0) { + currentCloseDate = new Date(currentPosition.takeProfit1.date); + } else { + currentCloseDate = new Date(currentPosition.stopLoss.date); + } + } + + if (currentCloseDate) { + const nextOpenDate = new Date(nextPosition.open.date); + const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime(); + + if (gapInMs >= 0) { // Only consider positive gaps + // Convert milliseconds to number of candles + const gapInCandles = Math.floor(gapInMs / candleTimeframeMs); + allCooldownValues.push(gapInCandles); + } + } + } + } + }); + + if (allCooldownValues.length > 0) { + // Calculate average cooldown + const averageCooldown = allCooldownValues.reduce((sum, value) => sum + value, 0) / allCooldownValues.length; + + // Calculate median cooldown + const sortedCooldowns = [...allCooldownValues].sort((a, b) => a - b); + const medianCooldown = sortedCooldowns.length % 2 === 0 + ? (sortedCooldowns[sortedCooldowns.length / 2 - 1] + sortedCooldowns[sortedCooldowns.length / 2]) / 2 + : sortedCooldowns[Math.floor(sortedCooldowns.length / 2)]; + + setCooldownRecommendations({ + averageCooldown: Math.ceil(averageCooldown), + medianCooldown: Math.ceil(medianCooldown), + }); } - } } - } - }); - - if (allCooldownValues.length > 0) { - // Calculate average cooldown - const averageCooldown = allCooldownValues.reduce((sum, value) => sum + value, 0) / allCooldownValues.length; - - // Calculate median cooldown - const sortedCooldowns = [...allCooldownValues].sort((a, b) => a - b); - const medianCooldown = sortedCooldowns.length % 2 === 0 - ? (sortedCooldowns[sortedCooldowns.length / 2 - 1] + sortedCooldowns[sortedCooldowns.length / 2]) / 2 - : sortedCooldowns[Math.floor(sortedCooldowns.length / 2)]; - - setCooldownRecommendations({ - averageCooldown: Math.ceil(averageCooldown), - medianCooldown: Math.ceil(medianCooldown), - }); } - } - } - }, [list]) + }, [list]) - return ( - <> - {isFetching ? ( -
- -
- ) : ( + return ( <> - {list && list.length > 0 && ( - <> -
- -
-
- -
-
- -
- - )} - ( - - )} - /> - - {/* Bot Configuration Modal */} - {selectedBacktest && ( - - )} + {isFetching ? ( +
+ +
+ ) : ( + <> + {list && list.length > 0 && ( + <> +
+ +
+
+ +
+
+ +
+ + )} +
( + + )} + /> - {/* Backtest Configuration Modal */} - {selectedBacktestForRerun && ( - - )} + {/* Bot Configuration Modal */} + {selectedBacktest && ( + + )} + + {/* Backtest Configuration Modal */} + {selectedBacktestForRerun && ( + + )} + + )} - )} - - ) + ) } export default BacktestTable diff --git a/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx index 92e1553..1e308f6 100644 --- a/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx +++ b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx @@ -47,8 +47,8 @@ type ITradeChartProps = { walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null; stream?: Candle | null - width: number - height: number + width?: number + height?: number } const TradeChart = ({ @@ -62,12 +62,88 @@ const TradeChart = ({ height, }: ITradeChartProps) => { const chartRef = React.useRef(null) + const containerRef = React.useRef(null) const chart = useRef() const {themeProperty} = useTheme() const theme = themeProperty() const series1 = useRef>() const [timeDiff, setTimeDiff] = useState(0) const [candleCount, setCandleCount] = useState(candles.length) + const [chartDimensions, setChartDimensions] = useState({ width: 0, height: 0 }) + + // Get responsive dimensions + const getResponsiveDimensions = () => { + if (!containerRef.current) return { width: width || 510, height: height || 300 } + + const containerWidth = containerRef.current.offsetWidth + const containerHeight = containerRef.current.offsetHeight + + // Use provided dimensions if available, otherwise calculate responsive ones + if (width && height) { + return { width, height } + } + + // For responsive mode, calculate based on container + const calculatedWidth = containerWidth > 0 ? containerWidth : 510 + + // Use different aspect ratios for different screen sizes + let aspectRatio = 0.6 // Default ratio (height/width) + if (containerWidth < 768) { // Mobile + aspectRatio = 0.8 // Taller on mobile for better visibility + } else if (containerWidth < 1024) { // Tablet + aspectRatio = 0.65 + } + + const calculatedHeight = height || Math.max(250, calculatedWidth * aspectRatio) + + return { + width: calculatedWidth, + height: calculatedHeight + } + } + + // Resize observer to handle container size changes + useEffect(() => { + if (!containerRef.current) return + + const resizeObserver = new ResizeObserver(() => { + const newDimensions = getResponsiveDimensions() + setChartDimensions(newDimensions) + + if (chart.current) { + chart.current.applyOptions({ + width: newDimensions.width, + height: newDimensions.height + }) + } + }) + + resizeObserver.observe(containerRef.current) + + return () => { + resizeObserver.disconnect() + } + }, [width, height]) + + // Handle window resize for additional responsiveness + useEffect(() => { + const handleResize = () => { + setTimeout(() => { + const newDimensions = getResponsiveDimensions() + setChartDimensions(newDimensions) + + if (chart.current) { + chart.current.applyOptions({ + width: newDimensions.width, + height: newDimensions.height + }) + } + }, 100) // Small delay to ensure container has updated + } + + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, [width, height]) function buildLine( color: string, @@ -164,7 +240,10 @@ const TradeChart = ({ } useEffect(() => { - if (chartRef.current) { + if (chartRef.current && containerRef.current) { + const initialDimensions = getResponsiveDimensions() + setChartDimensions(initialDimensions) + const lineColor = theme['base-100'] chart.current = createChart(chartRef.current, { crosshair: { @@ -194,7 +273,7 @@ const TradeChart = ({ visible: false, }, }, - height: height, + height: initialDimensions.height, layout: { background: {color: theme['base-300']}, textColor: theme.accent, @@ -213,7 +292,7 @@ const TradeChart = ({ secondsVisible: true, timeVisible: true, }, - width: width, + width: initialDimensions.width, }) prepareChart() @@ -710,7 +789,15 @@ const TradeChart = ({ } } - return
+ return ( +
+
+
+ ) } export default TradeChart diff --git a/src/Managing.WebApp/src/components/organism/UnifiedTradingModal/UnifiedTradingModal.tsx b/src/Managing.WebApp/src/components/organism/UnifiedTradingModal/UnifiedTradingModal.tsx index 5586931..c38211a 100644 --- a/src/Managing.WebApp/src/components/organism/UnifiedTradingModal/UnifiedTradingModal.tsx +++ b/src/Managing.WebApp/src/components/organism/UnifiedTradingModal/UnifiedTradingModal.tsx @@ -6,25 +6,24 @@ import useApiUrlStore from '../../../app/store/apiStore' import {useCustomMoneyManagement} from '../../../app/store/customMoneyManagement' import {useCustomScenario} from '../../../app/store/customScenario' import { - AccountClient, - BacktestClient, - BotClient, - BotType, - DataClient, - MoneyManagement, - MoneyManagementClient, - RiskManagement, - RiskToleranceLevel, - RunBacktestRequest, - Scenario, - ScenarioClient, - ScenarioRequest, - SignalType, - StartBotRequest, - Ticker, - Timeframe, - TradingBotConfigRequest, - UpdateBotConfigRequest, + AccountClient, + BacktestClient, + BotClient, + DataClient, + MoneyManagement, + MoneyManagementClient, + RiskManagement, + RiskToleranceLevel, + RunBacktestRequest, + Scenario, + ScenarioClient, + ScenarioRequest, + SignalType, + StartBotRequest, + Ticker, + Timeframe, + TradingBotConfigRequest, + UpdateBotConfigRequest, } from '../../../generated/ManagingApi' import type {IUnifiedTradingConfigInput, UnifiedTradingModalProps} from '../../../global/type' import {Loader, Slider} from '../../atoms' @@ -197,12 +196,11 @@ const UnifiedTradingModal: React.FC = ({ setGlobalCustomScenario((backtest.config as any).scenario); // Also update global store for prefilling setSelectedScenario('custom'); } else if (backtest.config.scenarioName) { - setSelectedScenario(backtest.config.scenarioName); - setShowCustomScenario(false); + setSelectedScenario(backtest.config.scenarioName); + setShowCustomScenario(false); } setValue('timeframe', backtest.config.timeframe); - setValue('botType', backtest.config.botType); setValue('cooldownPeriod', backtest.config.cooldownPeriod); setValue('maxLossStreak', backtest.config.maxLossStreak); setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours); @@ -245,7 +243,6 @@ const UnifiedTradingModal: React.FC = ({ } setValue('timeframe', backtest.config.timeframe); - setValue('botType', backtest.config.botType); setValue('cooldownPeriod', backtest.config.cooldownPeriod); setValue('maxLossStreak', backtest.config.maxLossStreak); setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours); @@ -305,7 +302,6 @@ const UnifiedTradingModal: React.FC = ({ } setValue('scenarioName', config.scenarioName || ''); setValue('timeframe', config.timeframe); - setValue('botType', config.botType); setValue('cooldownPeriod', config.cooldownPeriod); setValue('maxLossStreak', config.maxLossStreak); setValue('maxPositionTimeHours', config.maxPositionTimeHours); @@ -443,13 +439,14 @@ const UnifiedTradingModal: React.FC = ({ const onMoneyManagementChange = (e: React.ChangeEvent) => { if (e.target.value === 'custom') { setShowCustomMoneyManagement(true); + setSelectedMoneyManagement('custom'); // Set selected to 'custom' setCustomMoneyManagement(undefined); setGlobalCustomMoneyManagement(null); // Clear global store when creating new custom } else { setShowCustomMoneyManagement(false); + setSelectedMoneyManagement(e.target.value); // Update selected money management setCustomMoneyManagement(undefined); setGlobalCustomMoneyManagement(null); // Clear global store when switching away from custom - setSelectedMoneyManagement(e.target.value); } }; @@ -544,7 +541,7 @@ const UnifiedTradingModal: React.FC = ({ } console.log(form) - console.log(moneyManagement) + console.log(customMoneyManagement) if (!moneyManagement) { t.update('error', 'Money management is required'); return; @@ -557,8 +554,8 @@ const UnifiedTradingModal: React.FC = ({ scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined, scenarioName: customScenario ? undefined : form.scenarioName, timeframe: form.timeframe, - botType: form.botType, isForWatchingOnly: form.isForWatchingOnly || false, + flipPosition: false, // Default to false since we're only using isForWatchingOnly checkbox cooldownPeriod: form.cooldownPeriod, maxLossStreak: form.maxLossStreak, maxPositionTimeHours: form.maxPositionTimeHours, @@ -592,6 +589,7 @@ const UnifiedTradingModal: React.FC = ({ closeModal(); } catch (error: any) { + console.error(error); t.update('error', `Error: ${error.message || error}`); } }; @@ -607,7 +605,6 @@ const UnifiedTradingModal: React.FC = ({ scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined, scenarioName: customScenario ? undefined : form.scenarioName, timeframe: form.timeframe, - botType: form.botType, isForWatchingOnly: false, cooldownPeriod: form.cooldownPeriod, maxLossStreak: form.maxLossStreak, @@ -622,14 +619,13 @@ const UnifiedTradingModal: React.FC = ({ useForDynamicStopLoss: form.useForDynamicStopLoss ?? true, moneyManagementName: showCustomMoneyManagement ? undefined : selectedMoneyManagement, moneyManagement: customMoneyManagement, + flipPosition: form.isForWatchingOnly ?? false, }; const request: RunBacktestRequest = { config: tradingBotConfigRequest, startDate: new Date(form.startDate), endDate: new Date(form.endDate), - balance: form.balance, - watchOnly: false, save: form.save || false, }; @@ -724,45 +720,29 @@ const UnifiedTradingModal: React.FC = ({
- {/* Second Row: Money Management & Bot Type */} -
- - - - - - - -
+ {/* Money Management */} + + + {/* Custom Money Management */} {showCustomMoneyManagement && ( diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index c1547d0..24dfbee 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -504,12 +504,8 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_Stop(botType: BotType | undefined, identifier: string | null | undefined): Promise { + bot_Stop(identifier: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/Stop?"; - if (botType === null) - throw new Error("The parameter 'botType' cannot be null."); - else if (botType !== undefined) - url_ += "botType=" + encodeURIComponent("" + botType) + "&"; if (identifier !== undefined && identifier !== null) url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ = url_.replace(/[?&]$/, ""); @@ -2868,7 +2864,6 @@ export interface TradingBotConfig { timeframe: Timeframe; isForWatchingOnly: boolean; botTradingBalance: number; - botType: BotType; isForBacktest: boolean; cooldownPeriod: number; maxLossStreak: number; @@ -3014,12 +3009,6 @@ export enum Ticker { Unknown = "Unknown", } -export enum BotType { - SimpleBot = "SimpleBot", - ScalpingBot = "ScalpingBot", - FlippingBot = "FlippingBot", -} - export interface RiskManagement { adverseProbabilityThreshold: number; favorableProbabilityThreshold: number; @@ -3336,8 +3325,6 @@ export interface RunBacktestRequest { config?: TradingBotConfigRequest | null; startDate?: Date; endDate?: Date; - balance?: number; - watchOnly?: boolean; save?: boolean; } @@ -3347,8 +3334,8 @@ export interface TradingBotConfigRequest { timeframe: Timeframe; isForWatchingOnly: boolean; botTradingBalance: number; - botType: BotType; name: string; + flipPosition: boolean; cooldownPeriod: number; maxLossStreak: number; scenario?: ScenarioRequest | null; @@ -3397,6 +3384,12 @@ export interface StartBotRequest { config?: TradingBotConfigRequest | null; } +export enum BotType { + SimpleBot = "SimpleBot", + ScalpingBot = "ScalpingBot", + FlippingBot = "FlippingBot", +} + export interface TradingBotResponse { status: string; signals: Signal[]; diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 513a166..7ecccf7 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -111,7 +111,6 @@ export interface TradingBotConfig { timeframe: Timeframe; isForWatchingOnly: boolean; botTradingBalance: number; - botType: BotType; isForBacktest: boolean; cooldownPeriod: number; maxLossStreak: number; @@ -257,12 +256,6 @@ export enum Ticker { Unknown = "Unknown", } -export enum BotType { - SimpleBot = "SimpleBot", - ScalpingBot = "ScalpingBot", - FlippingBot = "FlippingBot", -} - export interface RiskManagement { adverseProbabilityThreshold: number; favorableProbabilityThreshold: number; @@ -579,8 +572,6 @@ export interface RunBacktestRequest { config?: TradingBotConfigRequest | null; startDate?: Date; endDate?: Date; - balance?: number; - watchOnly?: boolean; save?: boolean; } @@ -590,8 +581,8 @@ export interface TradingBotConfigRequest { timeframe: Timeframe; isForWatchingOnly: boolean; botTradingBalance: number; - botType: BotType; name: string; + flipPosition: boolean; cooldownPeriod: number; maxLossStreak: number; scenario?: ScenarioRequest | null; @@ -640,6 +631,12 @@ export interface StartBotRequest { config?: TradingBotConfigRequest | null; } +export enum BotType { + SimpleBot = "SimpleBot", + ScalpingBot = "ScalpingBot", + FlippingBot = "FlippingBot", +} + export interface TradingBotResponse { status: string; signals: Signal[]; diff --git a/src/Managing.WebApp/src/pages/botsPage/botList.tsx b/src/Managing.WebApp/src/pages/botsPage/botList.tsx index 971cb5a..971b90b 100644 --- a/src/Managing.WebApp/src/pages/botsPage/botList.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/botList.tsx @@ -6,8 +6,7 @@ import {CardPosition, CardSignal, CardText, Toast,} from '../../components/molle import ManualPositionModal from '../../components/mollecules/ManualPositionModal' import TradesModal from '../../components/mollecules/TradesModal/TradesModal' import {TradeChart, UnifiedTradingModal} from '../../components/organism' -import type {BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi' -import {BotClient} from '../../generated/ManagingApi' +import {BotClient, BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi' import type {IBotList} from '../../global/type.tsx' import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal' @@ -162,7 +161,7 @@ const BotList: React.FC = ({ list }) => { if (status == 'Up') { client - .bot_Stop(botType, identifier) + .bot_Stop(identifier) .then(() => { t.update('success', 'Bot stopped') }) @@ -195,7 +194,7 @@ const BotList: React.FC = ({ list }) => { } function getUpdateBotBadge(bot: TradingBotResponse) { - const classes = baseBadgeClass() + ' bg-warning' + const classes = baseBadgeClass() + ' bg-orange-500' return (