Push merge conflict

This commit is contained in:
2025-07-04 11:02:53 +07:00
parent 88f195c0ca
commit 59c5de7df7
27 changed files with 1133 additions and 1118 deletions

View File

@@ -155,11 +155,10 @@ The `TradingBotConfig` class defines all configuration parameters for trading bo
| `Timeframe` | `Timeframe` | Candle timeframe for analysis | | `Timeframe` | `Timeframe` | Candle timeframe for analysis |
| `IsForWatchingOnly` | `bool` | If true, bot only sends signals without trading | | `IsForWatchingOnly` | `bool` | If true, bot only sends signals without trading |
| `BotTradingBalance` | `decimal` | Initial trading balance for the bot | | `BotTradingBalance` | `decimal` | Initial trading balance for the bot |
| `BotType` | `BotType` | Type of trading bot behavior |
| `IsForBacktest` | `bool` | Whether this config is for backtesting | | `IsForBacktest` | `bool` | Whether this config is for backtesting |
| `CooldownPeriod` | `int` | Number of candles to wait before opening new position in same direction | | `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) | | `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 | | `Name` | `string` | Unique identifier/name for the bot |
| `FlipOnlyWhenInProfit` | `bool` | Only flip positions when current position is profitable (default: true) | | `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) - 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 - `CloseEarlyWhenProfitable` allows immediate closure when profitable instead of waiting full duration
**Profit-Controlled Flipping:** **Bot Behavior Control:**
- `FlipOnlyWhenInProfit` ensures safer trading by only flipping profitable positions - `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 - Helps prevent cascading losses in volatile markets
**Synth API Integration:** **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 | | CloseEarlyWhenProfitable | Close positions early when profitable (requires MaxPositionTimeHours) | false |
| BotTradingBalance | Initial trading balance for the bot | Required | | 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 | #### Watch-Only Mode
|-------------|----------------------------------------------------------------------------------------| - **`IsForWatchingOnly`**: When `true`, the bot analyzes market conditions and generates signals but does not execute actual trades
| SimpleBot | Basic bot implementation for simple trading strategies | - Perfect for strategy testing, signal validation, or paper trading
| ScalpingBot | Opens positions and waits for cooldown period before opening new ones in same direction | - Useful for developing confidence in a strategy before committing real capital
| FlippingBot | Advanced bot that can flip positions when opposite signals are triggered |
#### Flipping Mode Configuration #### Position Flipping
- **`FlipPosition`**: When `true`, enables the bot to flip positions when opposite signals are triggered
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)
- **`FlipOnlyWhenInProfit`**: Safety feature that only allows flipping when current position is profitable (default: true) - **`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 1. Opens initial position based on scenario signals
2. Monitors for opposite direction signals from the same scenario 2. Monitors for opposite direction signals from the same scenario
3. When opposite signal occurs: 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 4. Closes current position and immediately opens new position in opposite direction
5. Continues this cycle for the duration of the bot's operation 5. Continues this cycle for the duration of the bot's operation
**ScalpingBot vs FlippingBot:** **When Flipping is Disabled (`FlipPosition = false`):**
- **ScalpingBot**: Opens position → Waits for exit signal → Closes → Cooldown → Opens new position - Opens position → Waits for exit signal → Closes → Cooldown → Opens new position in same direction
- **FlippingBot**: Opens position → Monitors for opposite signals → Flips immediately (no cooldown between flips) - 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`) ## Backtesting (`BacktestController`)

View File

@@ -10,7 +10,6 @@ using Managing.Domain.Strategies;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
@@ -192,15 +191,14 @@ public class BacktestController : BaseController
ScenarioName = request.Config.ScenarioName, ScenarioName = request.Config.ScenarioName,
Scenario = scenario, // Use the converted scenario object Scenario = scenario, // Use the converted scenario object
Timeframe = request.Config.Timeframe, Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.WatchOnly, IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Balance, BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.Config.BotType,
IsForBacktest = true, IsForBacktest = true,
CooldownPeriod = request.Config.CooldownPeriod, CooldownPeriod = request.Config.CooldownPeriod,
MaxLossStreak = request.Config.MaxLossStreak, MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit, 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 ?? Name = request.Config.Name ??
$"Backtest-{request.Config.ScenarioName ?? request.Config.Scenario?.Name ?? "Custom"}-{DateTime.UtcNow:yyyyMMdd-HHmmss}", $"Backtest-{request.Config.ScenarioName ?? request.Config.Scenario?.Name ?? "Custom"}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable, CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
@@ -210,21 +208,12 @@ public class BacktestController : BaseController
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss 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( backtestResult = await _backtester.RunTradingBotBacktest(
backtestConfig, backtestConfig,
request.StartDate, request.StartDate,
request.EndDate, request.EndDate,
user, user,
request.Save); request.Save);
break;
}
await NotifyBacktesingSubscriberAsync(backtestResult); await NotifyBacktesingSubscriberAsync(backtestResult);
@@ -281,16 +270,6 @@ public class RunBacktestRequest
/// </summary> /// </summary>
public DateTime EndDate { get; set; } public DateTime EndDate { get; set; }
/// <summary>
/// The starting balance for the backtest
/// </summary>
public decimal Balance { get; set; }
/// <summary>
/// Whether to only watch the backtest without executing trades
/// </summary>
public bool WatchOnly { get; set; } = false;
/// <summary> /// <summary>
/// Whether to save the backtest results /// Whether to save the backtest results
/// </summary> /// </summary>

View File

@@ -224,7 +224,6 @@ public class BotController : BaseController
Timeframe = request.Config.Timeframe, Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.Config.IsForWatchingOnly, IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Config.BotTradingBalance, BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.Config.BotType,
CooldownPeriod = request.Config.CooldownPeriod, CooldownPeriod = request.Config.CooldownPeriod,
MaxLossStreak = request.Config.MaxLossStreak, MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
@@ -236,7 +235,7 @@ public class BotController : BaseController
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss, UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
// Set computed/default properties // Set computed/default properties
IsForBacktest = false, IsForBacktest = false,
FlipPosition = request.Config.BotType == BotType.FlippingBot, FlipPosition = request.Config.FlipPosition,
Name = request.Config.Name Name = request.Config.Name
}; };
@@ -256,12 +255,11 @@ public class BotController : BaseController
/// <summary> /// <summary>
/// Stops a bot specified by type and name. /// Stops a bot specified by type and name.
/// </summary> /// </summary>
/// <param name="botType">The type of the bot to stop.</param>
/// <param name="identifier">The identifier of the bot to stop.</param> /// <param name="identifier">The identifier of the bot to stop.</param>
/// <returns>A string indicating the result of the stop operation.</returns> /// <returns>A string indicating the result of the stop operation.</returns>
[HttpGet] [HttpGet]
[Route("Stop")] [Route("Stop")]
public async Task<ActionResult<string>> Stop(BotType botType, string identifier) public async Task<ActionResult<string>> Stop(string identifier)
{ {
try try
{ {
@@ -271,8 +269,8 @@ public class BotController : BaseController
return Forbid("You don't have permission to stop this bot"); return Forbid("You don't have permission to stop this bot");
} }
var result = await _mediator.Send(new StopBotCommand(botType, identifier)); var result = await _mediator.Send(new StopBotCommand(identifier));
_logger.LogInformation($"{botType} type with identifier {identifier} is now {result}"); _logger.LogInformation($"Bot identifier {identifier} is now {result}");
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
@@ -343,7 +341,7 @@ public class BotController : BaseController
foreach (var bot in userBots) 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", await _hubContext.Clients.All.SendAsync("SendNotification",
$"Bot {bot.Identifier} paused by {user.Name}.", "Info"); $"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 // 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 // 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 // Get the saved bot backup
var backup = _botService.GetBotBackup(bot.Identifier); var backup = _botService.GetBotBackup(bot.Identifier);
@@ -776,7 +775,6 @@ public class BotController : BaseController
Timeframe = request.Config.Timeframe, Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.Config.IsForWatchingOnly, IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Config.BotTradingBalance, BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.Config.BotType,
CooldownPeriod = request.Config.CooldownPeriod, CooldownPeriod = request.Config.CooldownPeriod,
MaxLossStreak = request.Config.MaxLossStreak, MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
@@ -788,7 +786,7 @@ public class BotController : BaseController
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss, UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
// Set computed/default properties // Set computed/default properties
IsForBacktest = false, IsForBacktest = false,
FlipPosition = request.Config.BotType == BotType.FlippingBot, FlipPosition = request.Config.FlipPosition,
Name = request.Config.Name Name = request.Config.Name
}; };

View File

@@ -38,18 +38,14 @@ public class TradingBotConfigRequest
[Required] [Required]
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
/// <summary>
/// The type of bot (SimpleBot, ScalpingBot, FlippingBot)
/// </summary>
[Required]
public BotType BotType { get; set; }
/// <summary> /// <summary>
/// The name/identifier for this bot /// The name/identifier for this bot
/// </summary> /// </summary>
[Required] [Required]
public string Name { get; set; } public string Name { get; set; }
[Required] public bool FlipPosition { get; set; }
/// <summary> /// <summary>
/// Cooldown period between trades (in candles) /// Cooldown period between trades (in candles)
/// </summary> /// </summary>

View File

@@ -272,7 +272,6 @@ public class StatisticService : IStatisticService
Timeframe = timeframe, Timeframe = timeframe,
IsForWatchingOnly = true, IsForWatchingOnly = true,
BotTradingBalance = 1000, BotTradingBalance = 1000,
BotType = BotType.ScalpingBot,
IsForBacktest = true, IsForBacktest = true,
CooldownPeriod = 1, CooldownPeriod = 1,
MaxLossStreak = 0, MaxLossStreak = 0,

View File

@@ -20,11 +20,5 @@ namespace Managing.Application.Abstractions
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance configured for backtesting</returns> /// <returns>ITradingBot instance configured for backtesting</returns>
ITradingBot CreateBacktestTradingBot(TradingBotConfig config); 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);
} }
} }

View File

@@ -114,7 +114,7 @@ namespace Managing.Application.Backtesting
User user = null) User user = null)
{ {
// Set FlipPosition based on BotType // Set FlipPosition based on BotType
config.FlipPosition = config.BotType == BotType.FlippingBot; config.FlipPosition = config.FlipPosition;
var tradingBot = _botFactory.CreateBacktestTradingBot(config); var tradingBot = _botFactory.CreateBacktestTradingBot(config);
@@ -164,11 +164,11 @@ namespace Managing.Application.Backtesting
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)
{ {
var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
startDate, timeframe, endDate).Result; startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0) 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; return candles;
} }

View File

@@ -3,7 +3,6 @@ using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Bots.Base namespace Managing.Application.Bots.Base
{ {
@@ -61,64 +60,5 @@ namespace Managing.Application.Bots.Base
_botService, _botService,
config); 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);
}
} }
} }

View File

@@ -187,7 +187,7 @@ public class TradingBot : Bot, ITradingBot
Logger.LogInformation($"____________________{Name}____________________"); Logger.LogInformation($"____________________{Name}____________________");
Logger.LogInformation( 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(); var previousLastCandle = OptimizedCandles.LastOrDefault();
@@ -1450,7 +1450,6 @@ public class TradingBot : Bot, ITradingBot
} }
// Protect critical properties that shouldn't change for running bots // Protect critical properties that shouldn't change for running bots
var protectedBotType = Config.BotType;
var protectedIsForBacktest = Config.IsForBacktest; var protectedIsForBacktest = Config.IsForBacktest;
var protectedName = allowNameChange ? newConfig.Name : Config.Name; var protectedName = allowNameChange ? newConfig.Name : Config.Name;
@@ -1470,7 +1469,6 @@ public class TradingBot : Bot, ITradingBot
Config = newConfig; Config = newConfig;
// Restore protected properties // Restore protected properties
Config.BotType = protectedBotType;
Config.IsForBacktest = protectedIsForBacktest; Config.IsForBacktest = protectedIsForBacktest;
Config.Name = protectedName; Config.Name = protectedName;
@@ -1532,7 +1530,6 @@ public class TradingBot : Bot, ITradingBot
Timeframe = Config.Timeframe, Timeframe = Config.Timeframe,
IsForWatchingOnly = Config.IsForWatchingOnly, IsForWatchingOnly = Config.IsForWatchingOnly,
BotTradingBalance = Config.BotTradingBalance, BotTradingBalance = Config.BotTradingBalance,
BotType = Config.BotType,
IsForBacktest = Config.IsForBacktest, IsForBacktest = Config.IsForBacktest,
CooldownPeriod = Config.CooldownPeriod, CooldownPeriod = Config.CooldownPeriod,
MaxLossStreak = Config.MaxLossStreak, MaxLossStreak = Config.MaxLossStreak,

View File

@@ -1,17 +1,14 @@
using MediatR; using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot.Commands namespace Managing.Application.ManageBot.Commands
{ {
public class StopBotCommand : IRequest<string> public class StopBotCommand : IRequest<string>
{ {
public string Name { get; } public string Identifier { get; }
public BotType BotType { get; }
public StopBotCommand(BotType botType, string name) public StopBotCommand(string identifier)
{ {
BotType = botType; Identifier = identifier;
Name = name;
} }
} }
} }

View File

@@ -70,37 +70,25 @@ namespace Managing.Application.ManageBot
Timeframe = request.Config.Timeframe, Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.Config.IsForWatchingOnly, IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Config.BotTradingBalance, BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.Config.BotType,
IsForBacktest = request.Config.IsForBacktest, IsForBacktest = request.Config.IsForBacktest,
CooldownPeriod = CooldownPeriod =
request.Config.CooldownPeriod > 0 ? request.Config.CooldownPeriod : 1, // Default to 1 if not set request.Config.CooldownPeriod > 0 ? request.Config.CooldownPeriod : 1, // Default to 1 if not set
MaxLossStreak = request.Config.MaxLossStreak, MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, // Properly handle nullable value MaxPositionTimeHours = request.Config.MaxPositionTimeHours, // Properly handle nullable value
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit, 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, Name = request.Config.Name ?? request.Name,
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable 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();
case BotType.ScalpingBot:
case BotType.FlippingBot:
var tradingBot = _botFactory.CreateTradingBot(configToUse); var tradingBot = _botFactory.CreateTradingBot(configToUse);
tradingBot.User = request.User; tradingBot.User = request.User;
// Log the configuration being used // Log the configuration being used
await LogBotConfigurationAsync(tradingBot, $"{configToUse.BotType} created"); await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
_botService.AddTradingBotToCache(tradingBot); _botService.AddTradingBotToCache(tradingBot);
return tradingBot.GetStatus(); return tradingBot.GetStatus();
}
return botStatus.ToString(); return botStatus.ToString();
} }
@@ -116,7 +104,6 @@ namespace Managing.Application.ManageBot
{ {
var config = bot.GetConfiguration(); var config = bot.GetConfiguration();
var logMessage = $"{context} - Bot: {config.Name}, " + var logMessage = $"{context} - Bot: {config.Name}, " +
$"Type: {config.BotType}, " +
$"Account: {config.AccountName}, " + $"Account: {config.AccountName}, " +
$"Ticker: {config.Ticker}, " + $"Ticker: {config.Ticker}, " +
$"Balance: {config.BotTradingBalance}, " + $"Balance: {config.BotTradingBalance}, " +

View File

@@ -15,7 +15,7 @@ namespace Managing.Application.ManageBot
public Task<string> Handle(StopBotCommand request, CancellationToken cancellationToken) public Task<string> Handle(StopBotCommand request, CancellationToken cancellationToken)
{ {
return _botService.StopBot(request.Name); return _botService.StopBot(request.Identifier);
} }
} }
} }

View File

@@ -82,7 +82,6 @@ public class Backtest
Timeframe = Config.Timeframe, Timeframe = Config.Timeframe,
IsForWatchingOnly = false, // Always start as active bot IsForWatchingOnly = false, // Always start as active bot
BotTradingBalance = initialTradingBalance, BotTradingBalance = initialTradingBalance,
BotType = Config.BotType,
IsForBacktest = false, // Always false for live bots IsForBacktest = false, // Always false for live bots
CooldownPeriod = Config.CooldownPeriod, CooldownPeriod = Config.CooldownPeriod,
MaxLossStreak = Config.MaxLossStreak, MaxLossStreak = Config.MaxLossStreak,
@@ -117,7 +116,6 @@ public class Backtest
Timeframe = Config.Timeframe, Timeframe = Config.Timeframe,
IsForWatchingOnly = Config.IsForWatchingOnly, IsForWatchingOnly = Config.IsForWatchingOnly,
BotTradingBalance = balance, BotTradingBalance = balance,
BotType = Config.BotType,
IsForBacktest = true, IsForBacktest = true,
CooldownPeriod = Config.CooldownPeriod, CooldownPeriod = Config.CooldownPeriod,
MaxLossStreak = Config.MaxLossStreak, MaxLossStreak = Config.MaxLossStreak,

View File

@@ -14,7 +14,6 @@ public class TradingBotConfig
[Required] public Timeframe Timeframe { get; set; } [Required] public Timeframe Timeframe { get; set; }
[Required] public bool IsForWatchingOnly { get; set; } [Required] public bool IsForWatchingOnly { get; set; }
[Required] public decimal BotTradingBalance { get; set; } [Required] public decimal BotTradingBalance { get; set; }
[Required] public BotType BotType { get; set; }
[Required] public bool IsForBacktest { get; set; } [Required] public bool IsForBacktest { get; set; }
[Required] public int CooldownPeriod { get; set; } [Required] public int CooldownPeriod { get; set; }
[Required] public int MaxLossStreak { get; set; } [Required] public int MaxLossStreak { get; set; }

View File

@@ -1,7 +1,6 @@
using Exilion.TradingAtomics; using Exilion.TradingAtomics;
using Managing.Infrastructure.Databases.MongoDb.Attributes; using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations; using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections namespace Managing.Infrastructure.Databases.MongoDb.Collections
{ {
@@ -12,6 +11,7 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public int WinRate { get; set; } public int WinRate { get; set; }
public decimal GrowthPercentage { get; set; } public decimal GrowthPercentage { get; set; }
public decimal HodlPercentage { get; set; } public decimal HodlPercentage { get; set; }
public TradingBotConfigDto Config { get; set; }
public List<PositionDto> Positions { get; set; } public List<PositionDto> Positions { get; set; }
public List<SignalDto> Signals { get; set; } public List<SignalDto> Signals { get; set; }
public List<CandleDto> Candles { get; set; } public List<CandleDto> Candles { get; set; }
@@ -22,17 +22,5 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public UserDto User { get; set; } public UserDto User { get; set; }
public PerformanceMetrics Statistics { get; set; } public PerformanceMetrics Statistics { get; set; }
public double Score { 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; }
} }
} }

View File

@@ -4,6 +4,7 @@ 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.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
@@ -132,20 +133,7 @@ public static class MongoMappers
if (b == null) if (b == null)
return null; return null;
var config = new TradingBotConfig var config = Map(b.Config);
{
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 bTest = new Backtest( var bTest = new Backtest(
config, config,
@@ -181,6 +169,7 @@ public static class MongoMappers
WinRate = result.WinRate, WinRate = result.WinRate,
GrowthPercentage = result.GrowthPercentage, GrowthPercentage = result.GrowthPercentage,
HodlPercentage = result.HodlPercentage, HodlPercentage = result.HodlPercentage,
Config = Map(result.Config),
Positions = Map(result.Positions), Positions = Map(result.Positions),
Signals = result.Signals.Select(s => Map(s)).ToList(), Signals = result.Signals.Select(s => Map(s)).ToList(),
Candles = result.Candles.Select(c => Map(c)).ToList(), Candles = result.Candles.Select(c => Map(c)).ToList(),
@@ -191,16 +180,6 @@ public static class MongoMappers
StartDate = result.StartDate, StartDate = result.StartDate,
EndDate = result.EndDate, EndDate = result.EndDate,
Score = result.Score, 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 #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
} }

View File

@@ -21,6 +21,7 @@ const Modal: React.FC<IModalProps> = ({
titleHeader={titleHeader} titleHeader={titleHeader}
onClose={onClose} onClose={onClose}
onSubmit={onSubmit} onSubmit={onSubmit}
showModal={showModal}
/> />
{children} {children}
</div> </div>

View File

@@ -10,9 +10,7 @@ import Logo from '../../../assets/img/logo.png'
import {Loader} from '../../atoms' import {Loader} from '../../atoms'
const navigation = [ const navigation = [
{ href: '/desk', name: 'Desk' },
{ href: '/bots', name: 'Bots' }, { href: '/bots', name: 'Bots' },
{ href: '/workflow', name: 'Workflows' },
{ href: '/scenarios', name: 'Scenarios' }, { href: '/scenarios', name: 'Scenarios' },
{ href: '/backtest', name: 'Backtest' }, { href: '/backtest', name: 'Backtest' },
{ href: '/tools', name: 'Tools' }, { href: '/tools', name: 'Tools' },

View File

@@ -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 * Avalible Props
@@ -19,7 +19,7 @@ const Tabs: FC<ITabsProps> = ({
addButton = false, addButton = false,
onAddButton, onAddButton,
}) => { }) => {
const Panel = tabs && tabs.find((tab) => tab.index === selectedTab) const Panel = tabs && tabs.find((tab: any) => tab.index === selectedTab)
return ( return (
<div <div
@@ -28,7 +28,7 @@ const Tabs: FC<ITabsProps> = ({
} }
> >
<div className="tabs" role="tablist" aria-orientation={orientation}> <div className="tabs" role="tablist" aria-orientation={orientation}>
{tabs.map((tab) => ( {tabs.map((tab: any) => (
<button <button
className={ className={
'mb-5 tab tab-bordered ' + 'mb-5 tab tab-bordered ' +

View File

@@ -96,7 +96,6 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
ticker: backtest.config.ticker, ticker: backtest.config.ticker,
scenarioName: backtest.config.scenarioName, scenarioName: backtest.config.scenarioName,
timeframe: backtest.config.timeframe, timeframe: backtest.config.timeframe,
botType: backtest.config.botType,
isForWatchingOnly: isForWatchOnly, isForWatchingOnly: isForWatchOnly,
isForBacktest: false, // This is for running a live bot isForBacktest: false, // This is for running a live bot
cooldownPeriod: backtest.config.cooldownPeriod, cooldownPeriod: backtest.config.cooldownPeriod,
@@ -167,8 +166,6 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
config: optimizedConfig as unknown as TradingBotConfigRequest, config: optimizedConfig as unknown as TradingBotConfigRequest,
startDate: startDate, startDate: startDate,
endDate: endDate, endDate: endDate,
balance: backtest.walletBalances[0].value,
watchOnly: false,
save: false, save: false,
} }
@@ -285,7 +282,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
></CardText> ></CardText>
<CardText <CardText
title="Scenario" title="Scenario"
content={backtest.config.scenarioName} content={backtest.config.scenarioName ?? backtest.config.scenario?.name}
></CardText> ></CardText>
<CardText <CardText
title="Timeframe" title="Timeframe"

View File

@@ -351,16 +351,15 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
content={getAverageTradesPerDay() + " trades/day"} content={getAverageTradesPerDay() + " trades/day"}
></CardText> ></CardText>
</div> </div>
<div> <div className="w-full">
<figure> <figure className="w-full">
<TradeChart <TradeChart
width={1400}
height={1100}
candles={candles} candles={candles}
positions={positions} positions={positions}
walletBalances={walletBalances} walletBalances={walletBalances}
indicatorsValues={indicatorsValues} indicatorsValues={indicatorsValues}
signals={signals} signals={signals}
height={1000}
></TradeChart> ></TradeChart>
</figure> </figure>
</div> </div>

View File

@@ -4,7 +4,7 @@ import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import type {Backtest} from '../../../generated/ManagingApi' import type {Backtest} from '../../../generated/ManagingApi'
import {BacktestClient} 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 {CardText, SelectColumnFilter, Table} from '../../mollecules'
import {UnifiedTradingModal} from '../index' import {UnifiedTradingModal} from '../index'
import Toast from '../../mollecules/Toast/Toast' import Toast from '../../mollecules/Toast/Toast'

View File

@@ -47,8 +47,8 @@ type ITradeChartProps = {
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null; indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
stream?: Candle | null stream?: Candle | null
width: number width?: number
height: number height?: number
} }
const TradeChart = ({ const TradeChart = ({
@@ -62,12 +62,88 @@ const TradeChart = ({
height, height,
}: ITradeChartProps) => { }: ITradeChartProps) => {
const chartRef = React.useRef<HTMLDivElement>(null) const chartRef = React.useRef<HTMLDivElement>(null)
const containerRef = React.useRef<HTMLDivElement>(null)
const chart = useRef<IChartApi>() const chart = useRef<IChartApi>()
const {themeProperty} = useTheme() const {themeProperty} = useTheme()
const theme = themeProperty() const theme = themeProperty()
const series1 = useRef<ISeriesApi<'Candlestick'>>() const series1 = useRef<ISeriesApi<'Candlestick'>>()
const [timeDiff, setTimeDiff] = useState<number>(0) const [timeDiff, setTimeDiff] = useState<number>(0)
const [candleCount, setCandleCount] = useState<number>(candles.length) const [candleCount, setCandleCount] = useState<number>(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( function buildLine(
color: string, color: string,
@@ -164,7 +240,10 @@ const TradeChart = ({
} }
useEffect(() => { useEffect(() => {
if (chartRef.current) { if (chartRef.current && containerRef.current) {
const initialDimensions = getResponsiveDimensions()
setChartDimensions(initialDimensions)
const lineColor = theme['base-100'] const lineColor = theme['base-100']
chart.current = createChart(chartRef.current, { chart.current = createChart(chartRef.current, {
crosshair: { crosshair: {
@@ -194,7 +273,7 @@ const TradeChart = ({
visible: false, visible: false,
}, },
}, },
height: height, height: initialDimensions.height,
layout: { layout: {
background: {color: theme['base-300']}, background: {color: theme['base-300']},
textColor: theme.accent, textColor: theme.accent,
@@ -213,7 +292,7 @@ const TradeChart = ({
secondsVisible: true, secondsVisible: true,
timeVisible: true, timeVisible: true,
}, },
width: width, width: initialDimensions.width,
}) })
prepareChart() prepareChart()
@@ -710,7 +789,15 @@ const TradeChart = ({
} }
} }
return <div ref={chartRef}/> return (
<div
ref={containerRef}
className="w-full h-full"
style={{ minHeight: height || 250 }}
>
<div ref={chartRef} className="w-full h-full" />
</div>
)
} }
export default TradeChart export default TradeChart

View File

@@ -9,7 +9,6 @@ import {
AccountClient, AccountClient,
BacktestClient, BacktestClient,
BotClient, BotClient,
BotType,
DataClient, DataClient,
MoneyManagement, MoneyManagement,
MoneyManagementClient, MoneyManagementClient,
@@ -202,7 +201,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
} }
setValue('timeframe', backtest.config.timeframe); setValue('timeframe', backtest.config.timeframe);
setValue('botType', backtest.config.botType);
setValue('cooldownPeriod', backtest.config.cooldownPeriod); setValue('cooldownPeriod', backtest.config.cooldownPeriod);
setValue('maxLossStreak', backtest.config.maxLossStreak); setValue('maxLossStreak', backtest.config.maxLossStreak);
setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours); setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours);
@@ -245,7 +243,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
} }
setValue('timeframe', backtest.config.timeframe); setValue('timeframe', backtest.config.timeframe);
setValue('botType', backtest.config.botType);
setValue('cooldownPeriod', backtest.config.cooldownPeriod); setValue('cooldownPeriod', backtest.config.cooldownPeriod);
setValue('maxLossStreak', backtest.config.maxLossStreak); setValue('maxLossStreak', backtest.config.maxLossStreak);
setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours); setValue('maxPositionTimeHours', backtest.config.maxPositionTimeHours);
@@ -305,7 +302,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
} }
setValue('scenarioName', config.scenarioName || ''); setValue('scenarioName', config.scenarioName || '');
setValue('timeframe', config.timeframe); setValue('timeframe', config.timeframe);
setValue('botType', config.botType);
setValue('cooldownPeriod', config.cooldownPeriod); setValue('cooldownPeriod', config.cooldownPeriod);
setValue('maxLossStreak', config.maxLossStreak); setValue('maxLossStreak', config.maxLossStreak);
setValue('maxPositionTimeHours', config.maxPositionTimeHours); setValue('maxPositionTimeHours', config.maxPositionTimeHours);
@@ -443,13 +439,14 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
const onMoneyManagementChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const onMoneyManagementChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (e.target.value === 'custom') { if (e.target.value === 'custom') {
setShowCustomMoneyManagement(true); setShowCustomMoneyManagement(true);
setSelectedMoneyManagement('custom'); // Set selected to 'custom'
setCustomMoneyManagement(undefined); setCustomMoneyManagement(undefined);
setGlobalCustomMoneyManagement(null); // Clear global store when creating new custom setGlobalCustomMoneyManagement(null); // Clear global store when creating new custom
} else { } else {
setShowCustomMoneyManagement(false); setShowCustomMoneyManagement(false);
setSelectedMoneyManagement(e.target.value); // Update selected money management
setCustomMoneyManagement(undefined); setCustomMoneyManagement(undefined);
setGlobalCustomMoneyManagement(null); // Clear global store when switching away from custom setGlobalCustomMoneyManagement(null); // Clear global store when switching away from custom
setSelectedMoneyManagement(e.target.value);
} }
}; };
@@ -544,7 +541,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
} }
console.log(form) console.log(form)
console.log(moneyManagement) console.log(customMoneyManagement)
if (!moneyManagement) { if (!moneyManagement) {
t.update('error', 'Money management is required'); t.update('error', 'Money management is required');
return; return;
@@ -557,8 +554,8 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined, scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined,
scenarioName: customScenario ? undefined : form.scenarioName, scenarioName: customScenario ? undefined : form.scenarioName,
timeframe: form.timeframe, timeframe: form.timeframe,
botType: form.botType,
isForWatchingOnly: form.isForWatchingOnly || false, isForWatchingOnly: form.isForWatchingOnly || false,
flipPosition: false, // Default to false since we're only using isForWatchingOnly checkbox
cooldownPeriod: form.cooldownPeriod, cooldownPeriod: form.cooldownPeriod,
maxLossStreak: form.maxLossStreak, maxLossStreak: form.maxLossStreak,
maxPositionTimeHours: form.maxPositionTimeHours, maxPositionTimeHours: form.maxPositionTimeHours,
@@ -592,6 +589,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
closeModal(); closeModal();
} catch (error: any) { } catch (error: any) {
console.error(error);
t.update('error', `Error: ${error.message || error}`); t.update('error', `Error: ${error.message || error}`);
} }
}; };
@@ -607,7 +605,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined, scenario: customScenario ? convertScenarioToRequest(customScenario) : undefined,
scenarioName: customScenario ? undefined : form.scenarioName, scenarioName: customScenario ? undefined : form.scenarioName,
timeframe: form.timeframe, timeframe: form.timeframe,
botType: form.botType,
isForWatchingOnly: false, isForWatchingOnly: false,
cooldownPeriod: form.cooldownPeriod, cooldownPeriod: form.cooldownPeriod,
maxLossStreak: form.maxLossStreak, maxLossStreak: form.maxLossStreak,
@@ -622,14 +619,13 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
useForDynamicStopLoss: form.useForDynamicStopLoss ?? true, useForDynamicStopLoss: form.useForDynamicStopLoss ?? true,
moneyManagementName: showCustomMoneyManagement ? undefined : selectedMoneyManagement, moneyManagementName: showCustomMoneyManagement ? undefined : selectedMoneyManagement,
moneyManagement: customMoneyManagement, moneyManagement: customMoneyManagement,
flipPosition: form.isForWatchingOnly ?? false,
}; };
const request: RunBacktestRequest = { const request: RunBacktestRequest = {
config: tradingBotConfigRequest, config: tradingBotConfigRequest,
startDate: new Date(form.startDate), startDate: new Date(form.startDate),
endDate: new Date(form.endDate), endDate: new Date(form.endDate),
balance: form.balance,
watchOnly: false,
save: form.save || false, save: form.save || false,
}; };
@@ -724,8 +720,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
</FormInput> </FormInput>
</div> </div>
{/* Second Row: Money Management & Bot Type */} {/* Money Management */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormInput label="Money Management" htmlFor="moneyManagement"> <FormInput label="Money Management" htmlFor="moneyManagement">
<select <select
className="select select-bordered w-full" className="select select-bordered w-full"
@@ -749,21 +744,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
</select> </select>
</FormInput> </FormInput>
<FormInput label="Bot Type" htmlFor="botType">
<select
className="select select-bordered w-full"
{...register('botType', { required: true })}
disabled={mode === 'updateBot'} // Can't change bot type for existing bots
>
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</FormInput>
</div>
{/* Custom Money Management */} {/* Custom Money Management */}
{showCustomMoneyManagement && ( {showCustomMoneyManagement && (
<div className="mt-6"> <div className="mt-6">

View File

@@ -504,12 +504,8 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
bot_Stop(botType: BotType | undefined, identifier: string | null | undefined): Promise<string> { bot_Stop(identifier: string | null | undefined): Promise<string> {
let url_ = this.baseUrl + "/Bot/Stop?"; 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) if (identifier !== undefined && identifier !== null)
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@@ -2868,7 +2864,6 @@ export interface TradingBotConfig {
timeframe: Timeframe; timeframe: Timeframe;
isForWatchingOnly: boolean; isForWatchingOnly: boolean;
botTradingBalance: number; botTradingBalance: number;
botType: BotType;
isForBacktest: boolean; isForBacktest: boolean;
cooldownPeriod: number; cooldownPeriod: number;
maxLossStreak: number; maxLossStreak: number;
@@ -3014,12 +3009,6 @@ export enum Ticker {
Unknown = "Unknown", Unknown = "Unknown",
} }
export enum BotType {
SimpleBot = "SimpleBot",
ScalpingBot = "ScalpingBot",
FlippingBot = "FlippingBot",
}
export interface RiskManagement { export interface RiskManagement {
adverseProbabilityThreshold: number; adverseProbabilityThreshold: number;
favorableProbabilityThreshold: number; favorableProbabilityThreshold: number;
@@ -3336,8 +3325,6 @@ export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null; config?: TradingBotConfigRequest | null;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
balance?: number;
watchOnly?: boolean;
save?: boolean; save?: boolean;
} }
@@ -3347,8 +3334,8 @@ export interface TradingBotConfigRequest {
timeframe: Timeframe; timeframe: Timeframe;
isForWatchingOnly: boolean; isForWatchingOnly: boolean;
botTradingBalance: number; botTradingBalance: number;
botType: BotType;
name: string; name: string;
flipPosition: boolean;
cooldownPeriod: number; cooldownPeriod: number;
maxLossStreak: number; maxLossStreak: number;
scenario?: ScenarioRequest | null; scenario?: ScenarioRequest | null;
@@ -3397,6 +3384,12 @@ export interface StartBotRequest {
config?: TradingBotConfigRequest | null; config?: TradingBotConfigRequest | null;
} }
export enum BotType {
SimpleBot = "SimpleBot",
ScalpingBot = "ScalpingBot",
FlippingBot = "FlippingBot",
}
export interface TradingBotResponse { export interface TradingBotResponse {
status: string; status: string;
signals: Signal[]; signals: Signal[];

View File

@@ -111,7 +111,6 @@ export interface TradingBotConfig {
timeframe: Timeframe; timeframe: Timeframe;
isForWatchingOnly: boolean; isForWatchingOnly: boolean;
botTradingBalance: number; botTradingBalance: number;
botType: BotType;
isForBacktest: boolean; isForBacktest: boolean;
cooldownPeriod: number; cooldownPeriod: number;
maxLossStreak: number; maxLossStreak: number;
@@ -257,12 +256,6 @@ export enum Ticker {
Unknown = "Unknown", Unknown = "Unknown",
} }
export enum BotType {
SimpleBot = "SimpleBot",
ScalpingBot = "ScalpingBot",
FlippingBot = "FlippingBot",
}
export interface RiskManagement { export interface RiskManagement {
adverseProbabilityThreshold: number; adverseProbabilityThreshold: number;
favorableProbabilityThreshold: number; favorableProbabilityThreshold: number;
@@ -579,8 +572,6 @@ export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null; config?: TradingBotConfigRequest | null;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
balance?: number;
watchOnly?: boolean;
save?: boolean; save?: boolean;
} }
@@ -590,8 +581,8 @@ export interface TradingBotConfigRequest {
timeframe: Timeframe; timeframe: Timeframe;
isForWatchingOnly: boolean; isForWatchingOnly: boolean;
botTradingBalance: number; botTradingBalance: number;
botType: BotType;
name: string; name: string;
flipPosition: boolean;
cooldownPeriod: number; cooldownPeriod: number;
maxLossStreak: number; maxLossStreak: number;
scenario?: ScenarioRequest | null; scenario?: ScenarioRequest | null;
@@ -640,6 +631,12 @@ export interface StartBotRequest {
config?: TradingBotConfigRequest | null; config?: TradingBotConfigRequest | null;
} }
export enum BotType {
SimpleBot = "SimpleBot",
ScalpingBot = "ScalpingBot",
FlippingBot = "FlippingBot",
}
export interface TradingBotResponse { export interface TradingBotResponse {
status: string; status: string;
signals: Signal[]; signals: Signal[];

View File

@@ -6,8 +6,7 @@ import {CardPosition, CardSignal, CardText, Toast,} from '../../components/molle
import ManualPositionModal from '../../components/mollecules/ManualPositionModal' import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
import TradesModal from '../../components/mollecules/TradesModal/TradesModal' import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
import {TradeChart, UnifiedTradingModal} from '../../components/organism' import {TradeChart, UnifiedTradingModal} from '../../components/organism'
import type {BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi' import {BotClient, BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi'
import {BotClient} from '../../generated/ManagingApi'
import type {IBotList} from '../../global/type.tsx' import type {IBotList} from '../../global/type.tsx'
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal' import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
@@ -162,7 +161,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
if (status == 'Up') { if (status == 'Up') {
client client
.bot_Stop(botType, identifier) .bot_Stop(identifier)
.then(() => { .then(() => {
t.update('success', 'Bot stopped') t.update('success', 'Bot stopped')
}) })
@@ -195,7 +194,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
} }
function getUpdateBotBadge(bot: TradingBotResponse) { function getUpdateBotBadge(bot: TradingBotResponse) {
const classes = baseBadgeClass() + ' bg-warning' const classes = baseBadgeClass() + ' bg-orange-500'
return ( return (
<button className={classes} onClick={() => openUpdateBotModal(bot)}> <button className={classes} onClick={() => openUpdateBotModal(bot)}>
<p className="text-primary-content flex"> <p className="text-primary-content flex">
@@ -246,14 +245,12 @@ const BotList: React.FC<IBotList> = ({ list }) => {
className="sm:w-1 md:w-1/2 xl:w-1/2 w-full p-2" className="sm:w-1 md:w-1/2 xl:w-1/2 w-full p-2"
> >
<div className={cardClasses(bot.status)}> <div className={cardClasses(bot.status)}>
<figure> <figure className="w-full">
{ {
<TradeChart <TradeChart
candles={bot.candles} candles={bot.candles}
positions={bot.positions} positions={bot.positions}
signals={bot.signals} signals={bot.signals}
width={510}
height={300}
></TradeChart> ></TradeChart>
} }
</figure> </figure>
@@ -274,7 +271,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
{/* Action Badges */} {/* Action Badges */}
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.botType)} {getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.flipPosition ? BotType.FlippingBot : BotType.SimpleBot)}
{getUpdateBotBadge(bot)} {getUpdateBotBadge(bot)}
{getManualPositionBadge(bot.identifier)} {getManualPositionBadge(bot.identifier)}
{getDeleteBadge(bot.identifier)} {getDeleteBadge(bot.identifier)}
@@ -294,7 +291,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
</div> </div>
<div className="columns-2"> <div className="columns-2">
<CardSignal signals={bot.signals}></CardSignal> <CardSignal signals={bot.signals}></CardSignal>
<CardText title="Type" content={bot.config.botType}></CardText> <CardText title="Type" content={bot.config.flipPosition ? 'Flipping' : 'Simple'}></CardText>
</div> </div>
<div className="columns-2"> <div className="columns-2">
<CardPosition <CardPosition