Add new parameters
This commit is contained in:
16
README.md
16
README.md
@@ -146,6 +146,9 @@ The backtest system works with multiple required parameters :
|
|||||||
- Timeframe (OneDay, FifteenMinutes, etc..)
|
- Timeframe (OneDay, FifteenMinutes, etc..)
|
||||||
- BotType (ScalpingBot or FlippingBot)
|
- BotType (ScalpingBot or FlippingBot)
|
||||||
- Initial balance
|
- Initial balance
|
||||||
|
- **Advanced parameters**: All bot configuration parameters (time limits, profit-controlled flipping, etc.)
|
||||||
|
- **Smart bot deployment**: Deploy successful backtests as live bots with optimized settings
|
||||||
|
- **Enhanced UI**: Wider modals with organized 2-column parameter layouts
|
||||||
|
|
||||||
## Bots
|
## Bots
|
||||||
|
|
||||||
@@ -154,6 +157,19 @@ The backtest system works with multiple required parameters :
|
|||||||
- Delete a bot
|
- Delete a bot
|
||||||
- Stop all bots
|
- Stop all bots
|
||||||
- Set bot to watch only (send signal to discord instead of opening a new position)
|
- Set bot to watch only (send signal to discord instead of opening a new position)
|
||||||
|
- **Time-based position management**: Automatically close positions after maximum time limit (only when in profit/breakeven)
|
||||||
|
- **Advanced position flipping**: Control whether positions flip only when current position is profitable
|
||||||
|
- **Real-time configuration updates**: Update bot settings without restarting
|
||||||
|
- **Enhanced money management**: Smart money management selection with optimized settings from backtests
|
||||||
|
|
||||||
|
### Bot Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|--------------------------|-------------------------------------------------------------------------------------------------------|---------|
|
||||||
|
| MaxPositionTimeHours | Maximum time (in hours) a position can stay open. Closes only when in profit/breakeven. Null = disabled | null |
|
||||||
|
| FlipOnlyWhenInProfit | Only flip positions when current position is profitable | true |
|
||||||
|
| CooldownPeriod | Number of candles to wait before opening new position in same direction | 10 |
|
||||||
|
| MaxLossStreak | Maximum consecutive losses before requiring opposite direction signal. 0 = no limit | 0 |
|
||||||
|
|
||||||
Bot types availables :
|
Bot types availables :
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -13,6 +14,7 @@ namespace Managing.Api.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controller for managing backtest operations.
|
/// Controller for managing backtest operations.
|
||||||
/// Provides endpoints for creating, retrieving, and deleting backtests.
|
/// Provides endpoints for creating, retrieving, and deleting backtests.
|
||||||
|
/// Returns complete backtest configurations for easy bot deployment.
|
||||||
/// Requires authorization for access.
|
/// Requires authorization for access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
@@ -52,8 +54,9 @@ public class BacktestController : BaseController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all backtests for the authenticated user.
|
/// Retrieves all backtests for the authenticated user.
|
||||||
|
/// Each backtest includes the complete TradingBotConfig for easy bot deployment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A list of backtests.</returns>
|
/// <returns>A list of backtests with complete configurations.</returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
|
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
|
||||||
{
|
{
|
||||||
@@ -63,10 +66,11 @@ public class BacktestController : BaseController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a specific backtest by ID for the authenticated user.
|
/// Retrieves a specific backtest by ID for the authenticated user.
|
||||||
/// This endpoint will also populate the candles for visualization.
|
/// This endpoint will also populate the candles for visualization and includes
|
||||||
|
/// the complete TradingBotConfig that can be used to start a new bot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ID of the backtest to retrieve.</param>
|
/// <param name="id">The ID of the backtest to retrieve.</param>
|
||||||
/// <returns>The requested backtest with populated candle data.</returns>
|
/// <returns>The requested backtest with populated candle data and complete configuration.</returns>
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<ActionResult<Backtest>> Backtest(string id)
|
public async Task<ActionResult<Backtest>> Backtest(string id)
|
||||||
{
|
{
|
||||||
@@ -94,75 +98,81 @@ public class BacktestController : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a backtest with the specified parameters.
|
/// Runs a backtest with the specified configuration.
|
||||||
|
/// The returned backtest includes a complete TradingBotConfig that preserves all
|
||||||
|
/// settings including nullable MaxPositionTimeHours for easy bot deployment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="accountName">The name of the account to use for the backtest.</param>
|
/// <param name="request">The backtest request containing configuration and parameters.</param>
|
||||||
/// <param name="botType">The type of bot to use for the backtest.</param>
|
/// <returns>The result of the backtest with complete configuration.</returns>
|
||||||
/// <param name="ticker">The ticker symbol to backtest.</param>
|
|
||||||
/// <param name="scenarioName">The name of the scenario to use for the backtest.</param>
|
|
||||||
/// <param name="timeframe">The timeframe for the backtest.</param>
|
|
||||||
/// <param name="watchOnly">Whether to only watch the backtest without executing trades.</param>
|
|
||||||
/// <param name="startDate">The start date for the backtest.</param>
|
|
||||||
/// <param name="endDate">The end date for the backtest.</param>
|
|
||||||
/// <param name="balance">The starting balance for the backtest.</param>
|
|
||||||
/// <param name="moneyManagementName">The name of the money management strategy to use.</param>
|
|
||||||
/// <param name="moneyManagement">The money management strategy details, if not using a named strategy.</param>
|
|
||||||
/// <param name="save">Whether to save the backtest results.</param>
|
|
||||||
/// <param name="cooldownPeriod">The cooldown period for the backtest.</param>
|
|
||||||
/// <param name="maxLossStreak">The maximum loss streak for the backtest.</param>
|
|
||||||
/// <returns>The result of the backtest.</returns>
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("Run")]
|
[Route("Run")]
|
||||||
public async Task<ActionResult<Backtest>> Run(string accountName,
|
public async Task<ActionResult<Backtest>> Run([FromBody] RunBacktestRequest request)
|
||||||
BotType botType,
|
|
||||||
Ticker ticker,
|
|
||||||
string scenarioName,
|
|
||||||
Timeframe timeframe,
|
|
||||||
bool watchOnly,
|
|
||||||
decimal balance,
|
|
||||||
string moneyManagementName,
|
|
||||||
DateTime startDate,
|
|
||||||
DateTime endDate,
|
|
||||||
MoneyManagement? moneyManagement = null,
|
|
||||||
bool save = false,
|
|
||||||
int cooldownPeriod = 1,
|
|
||||||
int maxLossStreak = 0)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(accountName))
|
if (request?.Config == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName));
|
return BadRequest("Backtest configuration is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(scenarioName))
|
if (string.IsNullOrEmpty(request.Config.AccountName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"'{nameof(scenarioName)}' cannot be null or empty.", nameof(scenarioName));
|
return BadRequest("Account name is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null)
|
if (string.IsNullOrEmpty(request.Config.ScenarioName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
return BadRequest("Scenario name is required");
|
||||||
$"'{nameof(moneyManagementName)}' and '{nameof(moneyManagement)}' cannot be null or empty.",
|
|
||||||
nameof(moneyManagementName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(request.MoneyManagementName) && request.MoneyManagement == null)
|
||||||
|
{
|
||||||
|
return BadRequest("Either money management name or money management object is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
Backtest backtestResult = null;
|
Backtest backtestResult = null;
|
||||||
var scenario = _scenarioService.GetScenario(scenarioName);
|
var scenario = _scenarioService.GetScenario(request.Config.ScenarioName);
|
||||||
var account = await _accountService.GetAccount(accountName, true, false);
|
var account = await _accountService.GetAccount(request.Config.AccountName, true, false);
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null)
|
|
||||||
{
|
|
||||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
moneyManagement.FormatPercentage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scenario == null)
|
if (scenario == null)
|
||||||
return BadRequest("No scenario found");
|
return BadRequest("No scenario found");
|
||||||
|
|
||||||
switch (botType)
|
// Get money management
|
||||||
|
MoneyManagement moneyManagement;
|
||||||
|
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||||
|
{
|
||||||
|
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||||
|
if (moneyManagement == null)
|
||||||
|
return BadRequest("Money management not found");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moneyManagement = request.MoneyManagement;
|
||||||
|
moneyManagement?.FormatPercentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config with money management
|
||||||
|
var backtestConfig = new TradingBotConfig
|
||||||
|
{
|
||||||
|
AccountName = request.Config.AccountName,
|
||||||
|
MoneyManagement = moneyManagement,
|
||||||
|
Ticker = request.Config.Ticker,
|
||||||
|
ScenarioName = request.Config.ScenarioName,
|
||||||
|
Timeframe = request.Config.Timeframe,
|
||||||
|
IsForWatchingOnly = request.WatchOnly,
|
||||||
|
BotTradingBalance = request.Balance,
|
||||||
|
BotType = request.Config.BotType,
|
||||||
|
IsForBacktest = true,
|
||||||
|
CooldownPeriod = request.Config.CooldownPeriod,
|
||||||
|
MaxLossStreak = request.Config.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||||
|
FlipPosition = request.Config.FlipPosition,
|
||||||
|
Name = request.Config.Name ?? $"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (request.Config.BotType)
|
||||||
{
|
{
|
||||||
case BotType.SimpleBot:
|
case BotType.SimpleBot:
|
||||||
break;
|
break;
|
||||||
@@ -170,33 +180,37 @@ public class BacktestController : BaseController
|
|||||||
backtestResult = await _backtester.RunScalpingBotBacktest(
|
backtestResult = await _backtester.RunScalpingBotBacktest(
|
||||||
account,
|
account,
|
||||||
moneyManagement,
|
moneyManagement,
|
||||||
ticker,
|
request.Config.Ticker,
|
||||||
scenario,
|
scenario,
|
||||||
timeframe,
|
request.Config.Timeframe,
|
||||||
balance,
|
request.Balance,
|
||||||
startDate,
|
request.StartDate,
|
||||||
endDate,
|
request.EndDate,
|
||||||
user,
|
user,
|
||||||
watchOnly,
|
request.WatchOnly,
|
||||||
save,
|
request.Save,
|
||||||
cooldownPeriod: cooldownPeriod,
|
cooldownPeriod: request.Config.CooldownPeriod,
|
||||||
maxLossStreak: maxLossStreak);
|
maxLossStreak: request.Config.MaxLossStreak,
|
||||||
|
maxPositionTimeHours: request.Config.MaxPositionTimeHours,
|
||||||
|
flipOnlyWhenInProfit: request.Config.FlipOnlyWhenInProfit);
|
||||||
break;
|
break;
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
backtestResult = await _backtester.RunFlippingBotBacktest(
|
backtestResult = await _backtester.RunFlippingBotBacktest(
|
||||||
account,
|
account,
|
||||||
moneyManagement,
|
moneyManagement,
|
||||||
ticker,
|
request.Config.Ticker,
|
||||||
scenario,
|
scenario,
|
||||||
timeframe,
|
request.Config.Timeframe,
|
||||||
balance,
|
request.Balance,
|
||||||
startDate,
|
request.StartDate,
|
||||||
endDate,
|
request.EndDate,
|
||||||
user,
|
user,
|
||||||
watchOnly,
|
request.WatchOnly,
|
||||||
save,
|
request.Save,
|
||||||
cooldownPeriod: cooldownPeriod,
|
cooldownPeriod: request.Config.CooldownPeriod,
|
||||||
maxLossStreak: maxLossStreak);
|
maxLossStreak: request.Config.MaxLossStreak,
|
||||||
|
maxPositionTimeHours: request.Config.MaxPositionTimeHours,
|
||||||
|
flipOnlyWhenInProfit: request.Config.FlipOnlyWhenInProfit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +218,11 @@ public class BacktestController : BaseController
|
|||||||
|
|
||||||
return Ok(backtestResult);
|
return Ok(backtestResult);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error running backtest: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies subscribers about the backtesting results via SignalR.
|
/// Notifies subscribers about the backtesting results via SignalR.
|
||||||
@@ -217,3 +236,49 @@ public class BacktestController : BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request model for running a backtest
|
||||||
|
/// </summary>
|
||||||
|
public class RunBacktestRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The trading bot configuration to use for the backtest
|
||||||
|
/// </summary>
|
||||||
|
public TradingBotConfig Config { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start date for the backtest
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The end date for the backtest
|
||||||
|
/// </summary>
|
||||||
|
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>
|
||||||
|
/// Whether to save the backtest results
|
||||||
|
/// </summary>
|
||||||
|
public bool Save { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the money management strategy to use (optional if MoneyManagement is provided)
|
||||||
|
/// </summary>
|
||||||
|
public string? MoneyManagementName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The money management strategy details (optional if MoneyManagementName is provided)
|
||||||
|
/// </summary>
|
||||||
|
public MoneyManagement? MoneyManagement { get; set; }
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using Managing.Application.Hubs;
|
|||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -142,6 +143,8 @@ public class BotController : BaseController
|
|||||||
BotType = request.BotType,
|
BotType = request.BotType,
|
||||||
CooldownPeriod = request.CooldownPeriod,
|
CooldownPeriod = request.CooldownPeriod,
|
||||||
MaxLossStreak = request.MaxLossStreak,
|
MaxLossStreak = request.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = request.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = request.FlipOnlyWhenInProfit,
|
||||||
IsForBacktest = false,
|
IsForBacktest = false,
|
||||||
FlipPosition = request.BotType == BotType.FlippingBot,
|
FlipPosition = request.BotType == BotType.FlippingBot,
|
||||||
Name = request.Name
|
Name = request.Name
|
||||||
@@ -419,7 +422,9 @@ public class BotController : BaseController
|
|||||||
AccountName = item.Config.AccountName,
|
AccountName = item.Config.AccountName,
|
||||||
MoneyManagement = item.Config.MoneyManagement,
|
MoneyManagement = item.Config.MoneyManagement,
|
||||||
Identifier = item.Identifier,
|
Identifier = item.Identifier,
|
||||||
AgentName = item.User.AgentName
|
AgentName = item.User.AgentName,
|
||||||
|
MaxPositionTimeHours = item.Config.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = item.Config.FlipOnlyWhenInProfit
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,6 +548,102 @@ public class BotController : BaseController
|
|||||||
return StatusCode(500, $"Error closing position: {ex.Message}");
|
return StatusCode(500, $"Error closing position: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the configuration of a running bot
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request containing the new bot configuration</param>
|
||||||
|
/// <returns>A response indicating the result of the operation</returns>
|
||||||
|
[HttpPut]
|
||||||
|
[Route("UpdateConfig")]
|
||||||
|
public async Task<ActionResult<string>> UpdateBotConfig([FromBody] UpdateBotConfigRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if user owns the account
|
||||||
|
if (!await UserOwnsBotAccount(request.Identifier))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to update this bot's configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeBots = _botService.GetActiveBots();
|
||||||
|
var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier);
|
||||||
|
|
||||||
|
if (bot == null)
|
||||||
|
{
|
||||||
|
return NotFound($"Bot with identifier {request.Identifier} not found or is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user for validation
|
||||||
|
var user = await GetUser();
|
||||||
|
|
||||||
|
// Validate money management if provided
|
||||||
|
MoneyManagement moneyManagement = null;
|
||||||
|
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||||
|
{
|
||||||
|
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||||
|
if (moneyManagement == null)
|
||||||
|
{
|
||||||
|
return BadRequest("Money management not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Keep existing money management if not provided
|
||||||
|
moneyManagement = bot.Config.MoneyManagement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate account if provided
|
||||||
|
if (!string.IsNullOrEmpty(request.AccountName))
|
||||||
|
{
|
||||||
|
var account = await _accountService.GetAccount(request.AccountName, true, false);
|
||||||
|
if (account == null || account.User?.Name != user.Name)
|
||||||
|
{
|
||||||
|
return BadRequest("Account not found or you don't have permission to use this account");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create updated configuration
|
||||||
|
var updatedConfig = new TradingBotConfig
|
||||||
|
{
|
||||||
|
AccountName = !string.IsNullOrEmpty(request.AccountName) ? request.AccountName : bot.Config.AccountName,
|
||||||
|
MoneyManagement = moneyManagement,
|
||||||
|
Ticker = request.Ticker ?? bot.Config.Ticker,
|
||||||
|
ScenarioName = !string.IsNullOrEmpty(request.ScenarioName) ? request.ScenarioName : bot.Config.ScenarioName,
|
||||||
|
Timeframe = request.Timeframe ?? bot.Config.Timeframe,
|
||||||
|
IsForWatchingOnly = request.IsForWatchingOnly ?? bot.Config.IsForWatchingOnly,
|
||||||
|
BotTradingBalance = request.BotTradingBalance ?? bot.Config.BotTradingBalance,
|
||||||
|
BotType = bot.Config.BotType, // Bot type cannot be changed
|
||||||
|
CooldownPeriod = request.CooldownPeriod ?? bot.Config.CooldownPeriod,
|
||||||
|
MaxLossStreak = request.MaxLossStreak ?? bot.Config.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = request.MaxPositionTimeHours ?? bot.Config.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = request.FlipOnlyWhenInProfit ?? bot.Config.FlipOnlyWhenInProfit,
|
||||||
|
IsForBacktest = bot.Config.IsForBacktest, // Cannot be changed for running bots
|
||||||
|
FlipPosition = request.FlipPosition ?? bot.Config.FlipPosition,
|
||||||
|
Name = !string.IsNullOrEmpty(request.Name) ? request.Name : bot.Config.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate the updated configuration
|
||||||
|
if (updatedConfig.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||||
|
{
|
||||||
|
return BadRequest($"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bot's configuration
|
||||||
|
var updateCommand = new UpdateBotConfigCommand(request.Identifier, updatedConfig);
|
||||||
|
var result = await _mediator.Send(updateCommand);
|
||||||
|
|
||||||
|
_logger.LogInformation($"Bot configuration update result for {request.Identifier} by user {user.Name}: {result}");
|
||||||
|
|
||||||
|
await NotifyBotSubscriberAsync();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error updating bot configuration");
|
||||||
|
return StatusCode(500, $"Error updating bot configuration: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -627,7 +728,107 @@ public class StartBotRequest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal InitialTradingBalance { get; set; }
|
public decimal InitialTradingBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cooldown period in candles between positions
|
||||||
|
/// </summary>
|
||||||
public int CooldownPeriod { get; set; }
|
public int CooldownPeriod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of consecutive losses before stopping
|
||||||
|
/// </summary>
|
||||||
public int MaxLossStreak { get; set; }
|
public int MaxLossStreak { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the bot
|
||||||
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum time in hours that a position can remain open before being automatically closed.
|
||||||
|
/// Supports fractional values (e.g., 2.5 for 2 hours and 30 minutes).
|
||||||
|
/// If null, time-based position closure is disabled.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? MaxPositionTimeHours { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, positions will only be flipped when the current position is in profit.
|
||||||
|
/// If false, positions will be flipped regardless of profit status.
|
||||||
|
/// </summary>
|
||||||
|
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request model for updating bot configuration
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateBotConfigRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The identifier of the bot to update
|
||||||
|
/// </summary>
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The account name to use (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public string? AccountName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The money management name to use (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public string? MoneyManagementName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ticker to trade (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public Ticker? Ticker { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scenario to use (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public string? ScenarioName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The timeframe to use (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public Timeframe? Timeframe { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the bot is for watching only (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsForWatchingOnly { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bot trading balance (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public decimal? BotTradingBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cooldown period in candles between positions (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public int? CooldownPeriod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of consecutive losses before stopping (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxLossStreak { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum time in hours that a position can remain open before being automatically closed (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public decimal? MaxPositionTimeHours { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, positions will only be flipped when the current position is in profit (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public bool? FlipOnlyWhenInProfit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether position flipping is enabled (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public bool? FlipPosition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the bot (optional - will keep existing if not provided)
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
@@ -25,5 +25,16 @@ namespace Managing.Api.Models.Responses
|
|||||||
[Required] public MoneyManagement MoneyManagement { get; internal set; }
|
[Required] public MoneyManagement MoneyManagement { get; internal set; }
|
||||||
[Required] public string Identifier { get; set; }
|
[Required] public string Identifier { get; set; }
|
||||||
[Required] public string AgentName { get; set; }
|
[Required] public string AgentName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum time in hours that a position can remain open before being automatically closed.
|
||||||
|
/// If null, time-based position closure is disabled.
|
||||||
|
/// </summary>
|
||||||
|
[Required] public decimal? MaxPositionTimeHours { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, positions will only be flipped when the current position is in profit.
|
||||||
|
/// </summary>
|
||||||
|
[Required] public bool FlipOnlyWhenInProfit { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,9 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
bool save = false,
|
bool save = false,
|
||||||
List<Candle>? initialCandles = null,
|
List<Candle>? initialCandles = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0);
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true);
|
||||||
|
|
||||||
Task<Backtest> RunFlippingBotBacktest(
|
Task<Backtest> RunFlippingBotBacktest(
|
||||||
Account account,
|
Account account,
|
||||||
@@ -40,7 +42,9 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
bool save = false,
|
bool save = false,
|
||||||
List<Candle>? initialCandles = null,
|
List<Candle>? initialCandles = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0);
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true);
|
||||||
|
|
||||||
bool DeleteBacktest(string id);
|
bool DeleteBacktest(string id);
|
||||||
bool DeleteBacktests();
|
bool DeleteBacktests();
|
||||||
@@ -54,7 +58,9 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
decimal balance,
|
decimal balance,
|
||||||
User user = null,
|
User user = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0);
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true);
|
||||||
|
|
||||||
Task<Backtest> RunFlippingBotBacktest(
|
Task<Backtest> RunFlippingBotBacktest(
|
||||||
Account account,
|
Account account,
|
||||||
@@ -65,7 +71,9 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
decimal balance,
|
decimal balance,
|
||||||
User user = null,
|
User user = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0);
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true);
|
||||||
|
|
||||||
// User-specific operations
|
// User-specific operations
|
||||||
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);
|
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);
|
||||||
|
|||||||
@@ -40,5 +40,18 @@ namespace Managing.Application.Abstractions
|
|||||||
|
|
||||||
Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||||
bool tradeClosingPosition = false);
|
bool tradeClosingPosition = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current trading bot configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A copy of the current configuration</returns>
|
||||||
|
TradingBotConfig GetConfiguration();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the trading bot configuration with new settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfig">The new configuration to apply</param>
|
||||||
|
/// <returns>True if the configuration was successfully updated, false otherwise</returns>
|
||||||
|
Task<bool> UpdateConfiguration(TradingBotConfig newConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,9 @@ namespace Managing.Application.Backtesting
|
|||||||
bool save = false,
|
bool save = false,
|
||||||
List<Candle>? initialCandles = null,
|
List<Candle>? initialCandles = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0)
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true)
|
||||||
{
|
{
|
||||||
var config = new TradingBotConfig
|
var config = new TradingBotConfig
|
||||||
{
|
{
|
||||||
@@ -81,7 +83,9 @@ namespace Managing.Application.Backtesting
|
|||||||
BotType = BotType.ScalpingBot,
|
BotType = BotType.ScalpingBot,
|
||||||
IsForBacktest = true,
|
IsForBacktest = true,
|
||||||
CooldownPeriod = cooldownPeriod,
|
CooldownPeriod = cooldownPeriod,
|
||||||
MaxLossStreak = maxLossStreak
|
MaxLossStreak = maxLossStreak,
|
||||||
|
MaxPositionTimeHours = maxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
|
||||||
};
|
};
|
||||||
|
|
||||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
|
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
|
||||||
@@ -122,7 +126,9 @@ namespace Managing.Application.Backtesting
|
|||||||
bool save = false,
|
bool save = false,
|
||||||
List<Candle>? initialCandles = null,
|
List<Candle>? initialCandles = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0)
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true)
|
||||||
{
|
{
|
||||||
var config = new TradingBotConfig
|
var config = new TradingBotConfig
|
||||||
{
|
{
|
||||||
@@ -136,7 +142,9 @@ namespace Managing.Application.Backtesting
|
|||||||
BotType = BotType.FlippingBot,
|
BotType = BotType.FlippingBot,
|
||||||
IsForBacktest = true,
|
IsForBacktest = true,
|
||||||
CooldownPeriod = cooldownPeriod,
|
CooldownPeriod = cooldownPeriod,
|
||||||
MaxLossStreak = maxLossStreak
|
MaxLossStreak = maxLossStreak,
|
||||||
|
MaxPositionTimeHours = maxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
|
||||||
};
|
};
|
||||||
|
|
||||||
var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
|
var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
|
||||||
@@ -173,7 +181,9 @@ namespace Managing.Application.Backtesting
|
|||||||
decimal balance,
|
decimal balance,
|
||||||
User user = null,
|
User user = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0)
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true)
|
||||||
{
|
{
|
||||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||||
var config = new TradingBotConfig
|
var config = new TradingBotConfig
|
||||||
@@ -188,7 +198,9 @@ namespace Managing.Application.Backtesting
|
|||||||
BotType = BotType.ScalpingBot,
|
BotType = BotType.ScalpingBot,
|
||||||
IsForBacktest = true,
|
IsForBacktest = true,
|
||||||
CooldownPeriod = cooldownPeriod,
|
CooldownPeriod = cooldownPeriod,
|
||||||
MaxLossStreak = maxLossStreak
|
MaxLossStreak = maxLossStreak,
|
||||||
|
MaxPositionTimeHours = maxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
|
||||||
};
|
};
|
||||||
|
|
||||||
var bot = _botFactory.CreateBacktestScalpingBot(config);
|
var bot = _botFactory.CreateBacktestScalpingBot(config);
|
||||||
@@ -215,7 +227,9 @@ namespace Managing.Application.Backtesting
|
|||||||
decimal balance,
|
decimal balance,
|
||||||
User user = null,
|
User user = null,
|
||||||
int cooldownPeriod = 1,
|
int cooldownPeriod = 1,
|
||||||
int maxLossStreak = 0)
|
int maxLossStreak = 0,
|
||||||
|
decimal? maxPositionTimeHours = null,
|
||||||
|
bool flipOnlyWhenInProfit = true)
|
||||||
{
|
{
|
||||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||||
var config = new TradingBotConfig
|
var config = new TradingBotConfig
|
||||||
@@ -230,7 +244,9 @@ namespace Managing.Application.Backtesting
|
|||||||
BotType = BotType.FlippingBot,
|
BotType = BotType.FlippingBot,
|
||||||
IsForBacktest = true,
|
IsForBacktest = true,
|
||||||
CooldownPeriod = cooldownPeriod,
|
CooldownPeriod = cooldownPeriod,
|
||||||
MaxLossStreak = maxLossStreak
|
MaxLossStreak = maxLossStreak,
|
||||||
|
MaxPositionTimeHours = maxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = flipOnlyWhenInProfit
|
||||||
};
|
};
|
||||||
|
|
||||||
var bot = _botFactory.CreateBacktestFlippingBot(config);
|
var bot = _botFactory.CreateBacktestFlippingBot(config);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
public decimal Fee { get; set; }
|
public decimal Fee { get; set; }
|
||||||
public Scenario Scenario { get; set; }
|
public Scenario Scenario { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public TradingBot(
|
public TradingBot(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
ILogger<TradingBot> logger,
|
ILogger<TradingBot> logger,
|
||||||
@@ -297,7 +298,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
if (positionCandle == null)
|
if (positionCandle == null)
|
||||||
{
|
{
|
||||||
await LogWarning($"Cannot find candle for position {position.Identifier} opened at {position.Open.Date}");
|
await LogWarning(
|
||||||
|
$"Cannot find candle for position {position.Identifier} opened at {position.Open.Date}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +326,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// Add the recreated signal to our collection
|
// Add the recreated signal to our collection
|
||||||
Signals.Add(recreatedSignal);
|
Signals.Add(recreatedSignal);
|
||||||
|
|
||||||
await LogInformation($"Successfully recreated signal {recreatedSignal.Identifier} for position {position.Identifier}");
|
await LogInformation(
|
||||||
|
$"Successfully recreated signal {recreatedSignal.Identifier} for position {position.Identifier}");
|
||||||
return recreatedSignal;
|
return recreatedSignal;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -357,7 +360,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// Ensure signal status is correctly set to PositionOpen if position is not finished
|
// Ensure signal status is correctly set to PositionOpen if position is not finished
|
||||||
if (signalForPosition.Status != SignalStatus.PositionOpen)
|
if (signalForPosition.Status != SignalStatus.PositionOpen)
|
||||||
{
|
{
|
||||||
await LogInformation($"Updating signal {signalForPosition.Identifier} status from {signalForPosition.Status} to PositionOpen");
|
await LogInformation(
|
||||||
|
$"Updating signal {signalForPosition.Identifier} status from {signalForPosition.Status} to PositionOpen");
|
||||||
SetSignalStatus(signalForPosition.Identifier, SignalStatus.PositionOpen);
|
SetSignalStatus(signalForPosition.Identifier, SignalStatus.PositionOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +466,33 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
? OptimizedCandles.Last()
|
? OptimizedCandles.Last()
|
||||||
: ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow);
|
: ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
|
var currentTime = Config.IsForBacktest ? lastCandle.Date : DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Check if position has exceeded maximum time limit (only if MaxPositionTimeHours is set)
|
||||||
|
if (Config.MaxPositionTimeHours.HasValue && HasPositionExceededTimeLimit(positionForSignal, currentTime))
|
||||||
|
{
|
||||||
|
// Check if position is in profit or at breakeven before closing
|
||||||
|
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
|
||||||
|
var isAtBreakeven = Math.Abs(lastCandle.Close - positionForSignal.Open.Price) < 0.01m; // Small tolerance for breakeven
|
||||||
|
|
||||||
|
if (isPositionInProfit || isAtBreakeven)
|
||||||
|
{
|
||||||
|
await LogInformation(
|
||||||
|
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
|
||||||
|
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
|
||||||
|
$"Position is {(isPositionInProfit ? "in profit" : "at breakeven")} (entry: {positionForSignal.Open.Price}, current: {lastCandle.Close})");
|
||||||
|
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LogInformation(
|
||||||
|
$"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " +
|
||||||
|
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
|
||||||
|
$"Waiting for profit or breakeven before closing.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
||||||
{
|
{
|
||||||
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
|
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
|
||||||
@@ -584,9 +615,17 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// Check if current position is in profit before flipping
|
// Check if current position is in profit before flipping
|
||||||
var isPositionInProfit = await IsPositionInProfit(openedPosition, lastPrice);
|
var isPositionInProfit = await IsPositionInProfit(openedPosition, lastPrice);
|
||||||
|
|
||||||
if (isPositionInProfit)
|
// Determine if we should flip based on configuration
|
||||||
|
var shouldFlip = !Config.FlipOnlyWhenInProfit || isPositionInProfit;
|
||||||
|
|
||||||
|
if (shouldFlip)
|
||||||
{
|
{
|
||||||
await LogInformation("Try to flip the position because of an opposite direction signal and current position is in profit");
|
var flipReason = Config.FlipOnlyWhenInProfit
|
||||||
|
? "current position is in profit"
|
||||||
|
: "FlipOnlyWhenInProfit is disabled";
|
||||||
|
|
||||||
|
await LogInformation(
|
||||||
|
$"Try to flip the position because of an opposite direction signal and {flipReason}");
|
||||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||||
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
||||||
await OpenPosition(signal);
|
await OpenPosition(signal);
|
||||||
@@ -1111,6 +1150,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
BotTradingBalance = Config.BotTradingBalance,
|
BotTradingBalance = Config.BotTradingBalance,
|
||||||
StartupTime = StartupTime,
|
StartupTime = StartupTime,
|
||||||
CooldownPeriod = Config.CooldownPeriod,
|
CooldownPeriod = Config.CooldownPeriod,
|
||||||
|
MaxLossStreak = Config.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = Config.MaxPositionTimeHours ?? 0m,
|
||||||
|
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||||
};
|
};
|
||||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
|
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
|
||||||
}
|
}
|
||||||
@@ -1131,6 +1173,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
BotType = data.BotType,
|
BotType = data.BotType,
|
||||||
CooldownPeriod = data.CooldownPeriod,
|
CooldownPeriod = data.CooldownPeriod,
|
||||||
MaxLossStreak = data.MaxLossStreak,
|
MaxLossStreak = data.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = data.MaxPositionTimeHours == 0m ? null : data.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = data.FlipOnlyWhenInProfit,
|
||||||
Name = data.Name
|
Name = data.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1205,6 +1249,140 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
throw new ArgumentException("Invalid position direction");
|
throw new ArgumentException("Invalid position direction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a position has exceeded the maximum time limit for being open.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to check</param>
|
||||||
|
/// <param name="currentTime">The current time to compare against</param>
|
||||||
|
/// <returns>True if the position has exceeded the time limit, false otherwise</returns>
|
||||||
|
private bool HasPositionExceededTimeLimit(Position position, DateTime currentTime)
|
||||||
|
{
|
||||||
|
if (!Config.MaxPositionTimeHours.HasValue)
|
||||||
|
{
|
||||||
|
return false; // Time-based closure is disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeOpen = currentTime - position.Open.Date;
|
||||||
|
var maxTimeAllowed = TimeSpan.FromHours((double)Config.MaxPositionTimeHours.Value);
|
||||||
|
|
||||||
|
return timeOpen >= maxTimeAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the trading bot configuration with new settings.
|
||||||
|
/// This method validates the new configuration and applies it to the running bot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfig">The new configuration to apply</param>
|
||||||
|
/// <returns>True if the configuration was successfully updated, false otherwise</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the new configuration is invalid</exception>
|
||||||
|
public async Task<bool> UpdateConfiguration(TradingBotConfig newConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Validate the new configuration
|
||||||
|
if (newConfig == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Configuration cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newConfig.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(newConfig.AccountName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Account name cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Scenario name cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect critical properties that shouldn't change for running bots
|
||||||
|
var protectedBotType = Config.BotType;
|
||||||
|
var protectedIsForBacktest = Config.IsForBacktest;
|
||||||
|
var protectedName = Config.Name;
|
||||||
|
|
||||||
|
// Log the configuration update
|
||||||
|
await LogInformation($"Updating bot configuration. Previous config: " +
|
||||||
|
$"Balance: {Config.BotTradingBalance}, " +
|
||||||
|
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
||||||
|
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
|
||||||
|
$"Cooldown: {Config.CooldownPeriod}, " +
|
||||||
|
$"MaxLoss: {Config.MaxLossStreak}");
|
||||||
|
|
||||||
|
// Update the configuration
|
||||||
|
Config = newConfig;
|
||||||
|
|
||||||
|
// Restore protected properties
|
||||||
|
Config.BotType = protectedBotType;
|
||||||
|
Config.IsForBacktest = protectedIsForBacktest;
|
||||||
|
Config.Name = protectedName;
|
||||||
|
|
||||||
|
// If account changed, reload it
|
||||||
|
if (Config.AccountName != Account?.Name)
|
||||||
|
{
|
||||||
|
await LoadAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If scenario changed, reload it
|
||||||
|
var currentScenario = Scenario?.Name;
|
||||||
|
if (Config.ScenarioName != currentScenario)
|
||||||
|
{
|
||||||
|
LoadScenario(Config.ScenarioName);
|
||||||
|
}
|
||||||
|
|
||||||
|
await LogInformation($"Bot configuration updated successfully. New config: " +
|
||||||
|
$"Balance: {Config.BotTradingBalance}, " +
|
||||||
|
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
||||||
|
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
|
||||||
|
$"Cooldown: {Config.CooldownPeriod}, " +
|
||||||
|
$"MaxLoss: {Config.MaxLossStreak}");
|
||||||
|
|
||||||
|
// Save the updated configuration as backup
|
||||||
|
if (!Config.IsForBacktest)
|
||||||
|
{
|
||||||
|
SaveBackup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await LogWarning($"Failed to update bot configuration: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current trading bot configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A copy of the current configuration</returns>
|
||||||
|
public TradingBotConfig GetConfiguration()
|
||||||
|
{
|
||||||
|
return new TradingBotConfig
|
||||||
|
{
|
||||||
|
AccountName = Config.AccountName,
|
||||||
|
MoneyManagement = Config.MoneyManagement,
|
||||||
|
Ticker = Config.Ticker,
|
||||||
|
ScenarioName = Config.ScenarioName,
|
||||||
|
Timeframe = Config.Timeframe,
|
||||||
|
IsForWatchingOnly = Config.IsForWatchingOnly,
|
||||||
|
BotTradingBalance = Config.BotTradingBalance,
|
||||||
|
BotType = Config.BotType,
|
||||||
|
IsForBacktest = Config.IsForBacktest,
|
||||||
|
CooldownPeriod = Config.CooldownPeriod,
|
||||||
|
MaxLossStreak = Config.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = Config.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||||
|
FlipPosition = Config.FlipPosition,
|
||||||
|
Name = Config.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TradingBotBackup
|
public class TradingBotBackup
|
||||||
@@ -1224,4 +1402,6 @@ public class TradingBotBackup
|
|||||||
public decimal BotTradingBalance { get; set; }
|
public decimal BotTradingBalance { get; set; }
|
||||||
public int CooldownPeriod { get; set; }
|
public int CooldownPeriod { get; set; }
|
||||||
public int MaxLossStreak { get; set; }
|
public int MaxLossStreak { get; set; }
|
||||||
|
public decimal MaxPositionTimeHours { get; set; }
|
||||||
|
public bool FlipOnlyWhenInProfit { get; set; }
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ using Managing.Application.Abstractions.Repositories;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Bots;
|
using Managing.Application.Bots;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -121,39 +120,61 @@ namespace Managing.Application.ManageBot
|
|||||||
|
|
||||||
switch (backupBot.BotType)
|
switch (backupBot.BotType)
|
||||||
{
|
{
|
||||||
// case Enums.BotType.SimpleBot:
|
|
||||||
// bot = CreateSimpleBot(backupBot.Name,
|
|
||||||
// null); // Assuming null is an acceptable parameter for workflow
|
|
||||||
// botTask = Task.Run(() => ((IBot)bot).Start());
|
|
||||||
// break;
|
|
||||||
case BotType.ScalpingBot:
|
case BotType.ScalpingBot:
|
||||||
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||||
var scalpingMoneyManagement =
|
var scalpingMoneyManagement =
|
||||||
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
|
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
|
||||||
bot = CreateScalpingBot(
|
|
||||||
scalpingBotData.AccountName,
|
// Create config from backup data
|
||||||
scalpingMoneyManagement,
|
var scalpingConfig = new TradingBotConfig
|
||||||
scalpingBotData.Name,
|
{
|
||||||
scalpingBotData.Ticker,
|
AccountName = scalpingBotData.AccountName,
|
||||||
scalpingBotData.ScenarioName,
|
MoneyManagement = scalpingMoneyManagement,
|
||||||
scalpingBotData.Timeframe,
|
Ticker = scalpingBotData.Ticker,
|
||||||
scalpingBotData.IsForWatchingOnly,
|
ScenarioName = scalpingBotData.ScenarioName,
|
||||||
scalpingBotData.BotTradingBalance);
|
Timeframe = scalpingBotData.Timeframe,
|
||||||
|
IsForWatchingOnly = scalpingBotData.IsForWatchingOnly,
|
||||||
|
BotTradingBalance = scalpingBotData.BotTradingBalance,
|
||||||
|
BotType = scalpingBotData.BotType,
|
||||||
|
Name = scalpingBotData.Name,
|
||||||
|
CooldownPeriod = scalpingBotData.CooldownPeriod,
|
||||||
|
MaxLossStreak = scalpingBotData.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = scalpingBotData.MaxPositionTimeHours == 0m ? null : scalpingBotData.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = scalpingBotData.FlipOnlyWhenInProfit,
|
||||||
|
IsForBacktest = false,
|
||||||
|
FlipPosition = false
|
||||||
|
};
|
||||||
|
|
||||||
|
bot = CreateScalpingBot(scalpingConfig);
|
||||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||||
var flippingMoneyManagement =
|
var flippingMoneyManagement =
|
||||||
_moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result;
|
_moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result;
|
||||||
bot = CreateFlippingBot(
|
|
||||||
flippingBotData.AccountName,
|
// Create config from backup data
|
||||||
flippingMoneyManagement,
|
var flippingConfig = new TradingBotConfig
|
||||||
flippingBotData.Name,
|
{
|
||||||
flippingBotData.Ticker,
|
AccountName = flippingBotData.AccountName,
|
||||||
flippingBotData.ScenarioName,
|
MoneyManagement = flippingMoneyManagement,
|
||||||
flippingBotData.Timeframe,
|
Ticker = flippingBotData.Ticker,
|
||||||
flippingBotData.IsForWatchingOnly,
|
ScenarioName = flippingBotData.ScenarioName,
|
||||||
flippingBotData.BotTradingBalance);
|
Timeframe = flippingBotData.Timeframe,
|
||||||
|
IsForWatchingOnly = flippingBotData.IsForWatchingOnly,
|
||||||
|
BotTradingBalance = flippingBotData.BotTradingBalance,
|
||||||
|
BotType = flippingBotData.BotType,
|
||||||
|
Name = flippingBotData.Name,
|
||||||
|
CooldownPeriod = flippingBotData.CooldownPeriod,
|
||||||
|
MaxLossStreak = flippingBotData.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = flippingBotData.MaxPositionTimeHours == 0m ? null : flippingBotData.MaxPositionTimeHours,
|
||||||
|
FlipOnlyWhenInProfit = flippingBotData.FlipOnlyWhenInProfit,
|
||||||
|
IsForBacktest = false,
|
||||||
|
FlipPosition = true
|
||||||
|
};
|
||||||
|
|
||||||
|
bot = CreateFlippingBot(flippingConfig);
|
||||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -168,7 +189,8 @@ namespace Managing.Application.ManageBot
|
|||||||
private void InitBot(ITradingBot bot, BotBackup backupBot)
|
private void InitBot(ITradingBot bot, BotBackup backupBot)
|
||||||
{
|
{
|
||||||
var user = _userService.GetUser(backupBot.User.Name);
|
var user = _userService.GetUser(backupBot.User.Name);
|
||||||
backupBot.User = user;
|
bot.User = user;
|
||||||
|
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
|
||||||
bot.LoadBackup(backupBot);
|
bot.LoadBackup(backupBot);
|
||||||
bot.Start();
|
bot.Start();
|
||||||
}
|
}
|
||||||
@@ -241,113 +263,6 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
|
|
||||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
|
||||||
decimal initialTradingBalance)
|
|
||||||
{
|
|
||||||
var config = new TradingBotConfig
|
|
||||||
{
|
|
||||||
AccountName = accountName,
|
|
||||||
MoneyManagement = moneyManagement,
|
|
||||||
Ticker = ticker,
|
|
||||||
ScenarioName = scenario,
|
|
||||||
Timeframe = interval,
|
|
||||||
IsForWatchingOnly = isForWatchingOnly,
|
|
||||||
BotTradingBalance = initialTradingBalance,
|
|
||||||
BotType = BotType.ScalpingBot,
|
|
||||||
Name = name
|
|
||||||
};
|
|
||||||
|
|
||||||
return new ScalpingBot(
|
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement,
|
|
||||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
|
||||||
decimal initialTradingBalance)
|
|
||||||
{
|
|
||||||
var config = new TradingBotConfig
|
|
||||||
{
|
|
||||||
AccountName = accountName,
|
|
||||||
MoneyManagement = moneyManagement,
|
|
||||||
Ticker = ticker,
|
|
||||||
ScenarioName = scenario,
|
|
||||||
Timeframe = interval,
|
|
||||||
IsForWatchingOnly = isForWatchingOnly,
|
|
||||||
BotTradingBalance = initialTradingBalance,
|
|
||||||
BotType = BotType.ScalpingBot,
|
|
||||||
IsForBacktest = true
|
|
||||||
};
|
|
||||||
|
|
||||||
return new ScalpingBot(
|
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name,
|
|
||||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
|
||||||
decimal initialTradingBalance)
|
|
||||||
{
|
|
||||||
var config = new TradingBotConfig
|
|
||||||
{
|
|
||||||
AccountName = accountName,
|
|
||||||
MoneyManagement = moneyManagement,
|
|
||||||
Ticker = ticker,
|
|
||||||
ScenarioName = scenario,
|
|
||||||
Timeframe = interval,
|
|
||||||
IsForWatchingOnly = isForWatchingOnly,
|
|
||||||
BotTradingBalance = initialTradingBalance,
|
|
||||||
BotType = BotType.FlippingBot
|
|
||||||
};
|
|
||||||
|
|
||||||
return new FlippingBot(
|
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement,
|
|
||||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
|
||||||
decimal initialTradingBalance)
|
|
||||||
{
|
|
||||||
var config = new TradingBotConfig
|
|
||||||
{
|
|
||||||
AccountName = accountName,
|
|
||||||
MoneyManagement = moneyManagement,
|
|
||||||
Ticker = ticker,
|
|
||||||
ScenarioName = scenario,
|
|
||||||
Timeframe = interval,
|
|
||||||
IsForWatchingOnly = isForWatchingOnly,
|
|
||||||
BotTradingBalance = initialTradingBalance,
|
|
||||||
BotType = BotType.FlippingBot,
|
|
||||||
IsForBacktest = true
|
|
||||||
};
|
|
||||||
|
|
||||||
return new FlippingBot(
|
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITradingBot CreateScalpingBot(TradingBotConfig config)
|
public ITradingBot CreateScalpingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
return new ScalpingBot(
|
return new ScalpingBot(
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Managing.Domain.Bots;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Managing.Application.ManageBot.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Command to update the configuration of a running trading bot
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateBotConfigCommand : IRequest<string>
|
||||||
|
{
|
||||||
|
public string Identifier { get; }
|
||||||
|
public TradingBotConfig NewConfig { get; }
|
||||||
|
|
||||||
|
public UpdateBotConfigCommand(string identifier, TradingBotConfig newConfig)
|
||||||
|
{
|
||||||
|
Identifier = identifier;
|
||||||
|
NewConfig = newConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
|
using Managing.Common;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -29,6 +31,18 @@ namespace Managing.Application.ManageBot
|
|||||||
{
|
{
|
||||||
BotStatus botStatus = BotStatus.Down;
|
BotStatus botStatus = BotStatus.Down;
|
||||||
|
|
||||||
|
// Validate the configuration
|
||||||
|
if (request.Config == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Bot configuration is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||||
|
}
|
||||||
|
|
||||||
var account = await _accountService.GetAccount(request.Config.AccountName, true, true);
|
var account = await _accountService.GetAccount(request.Config.AccountName, true, true);
|
||||||
|
|
||||||
if (account == null)
|
if (account == null)
|
||||||
@@ -43,32 +57,86 @@ namespace Managing.Application.ManageBot
|
|||||||
throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance");
|
throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure cooldown period is set
|
// Ensure essential configuration values are properly set
|
||||||
if (request.Config.CooldownPeriod <= 0)
|
var configToUse = new TradingBotConfig
|
||||||
{
|
{
|
||||||
request.Config.CooldownPeriod = 1; // Default to 1 minute if not set
|
AccountName = request.Config.AccountName,
|
||||||
}
|
MoneyManagement = request.Config.MoneyManagement,
|
||||||
|
Ticker = request.Config.Ticker,
|
||||||
|
ScenarioName = request.Config.ScenarioName,
|
||||||
|
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.FlipPosition,
|
||||||
|
Name = request.Config.Name ?? request.Name
|
||||||
|
};
|
||||||
|
|
||||||
switch (request.Config.BotType)
|
switch (configToUse.BotType)
|
||||||
{
|
{
|
||||||
case BotType.SimpleBot:
|
case BotType.SimpleBot:
|
||||||
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
||||||
bot.User = request.User;
|
bot.User = request.User;
|
||||||
_botService.AddSimpleBotToCache(bot);
|
_botService.AddSimpleBotToCache(bot);
|
||||||
return bot.GetStatus();
|
return bot.GetStatus();
|
||||||
|
|
||||||
case BotType.ScalpingBot:
|
case BotType.ScalpingBot:
|
||||||
var sBot = _botFactory.CreateScalpingBot(request.Config);
|
var sBot = _botFactory.CreateScalpingBot(configToUse);
|
||||||
sBot.User = request.User;
|
sBot.User = request.User;
|
||||||
|
|
||||||
|
// Log the configuration being used
|
||||||
|
await LogBotConfigurationAsync(sBot, "ScalpingBot created");
|
||||||
|
|
||||||
_botService.AddTradingBotToCache(sBot);
|
_botService.AddTradingBotToCache(sBot);
|
||||||
return sBot.GetStatus();
|
return sBot.GetStatus();
|
||||||
|
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
var fBot = _botFactory.CreateFlippingBot(request.Config);
|
var fBot = _botFactory.CreateFlippingBot(configToUse);
|
||||||
fBot.User = request.User;
|
fBot.User = request.User;
|
||||||
|
|
||||||
|
// Log the configuration being used
|
||||||
|
await LogBotConfigurationAsync(fBot, "FlippingBot created");
|
||||||
|
|
||||||
_botService.AddTradingBotToCache(fBot);
|
_botService.AddTradingBotToCache(fBot);
|
||||||
return fBot.GetStatus();
|
return fBot.GetStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
return botStatus.ToString();
|
return botStatus.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the bot configuration for debugging and audit purposes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bot">The trading bot instance</param>
|
||||||
|
/// <param name="context">Context information for the log</param>
|
||||||
|
private async Task LogBotConfigurationAsync(ITradingBot bot, string context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = bot.GetConfiguration();
|
||||||
|
var logMessage = $"{context} - Bot: {config.Name}, " +
|
||||||
|
$"Type: {config.BotType}, " +
|
||||||
|
$"Account: {config.AccountName}, " +
|
||||||
|
$"Ticker: {config.Ticker}, " +
|
||||||
|
$"Balance: {config.BotTradingBalance}, " +
|
||||||
|
$"MaxTime: {config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
||||||
|
$"FlipOnlyProfit: {config.FlipOnlyWhenInProfit}, " +
|
||||||
|
$"Cooldown: {config.CooldownPeriod}, " +
|
||||||
|
$"MaxLoss: {config.MaxLossStreak}";
|
||||||
|
|
||||||
|
// Log through the bot's logger (this will use the bot's logging mechanism)
|
||||||
|
// For now, we'll just add a comment that this could be enhanced with actual logging
|
||||||
|
// Console.WriteLine(logMessage); // Could be replaced with proper logging
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Ignore logging errors to not affect bot creation
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Managing.Application.Abstractions;
|
||||||
|
using Managing.Application.ManageBot.Commands;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Managing.Application.ManageBot
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handler for updating trading bot configurations
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateBotConfigCommandHandler : IRequestHandler<UpdateBotConfigCommand, string>
|
||||||
|
{
|
||||||
|
private readonly IBotService _botService;
|
||||||
|
|
||||||
|
public UpdateBotConfigCommandHandler(IBotService botService)
|
||||||
|
{
|
||||||
|
_botService = botService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Handle(UpdateBotConfigCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(request.Identifier))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Bot identifier is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.NewConfig == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("New configuration is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the bot from active bots
|
||||||
|
var activeBots = _botService.GetActiveBots();
|
||||||
|
var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier);
|
||||||
|
|
||||||
|
if (bot == null)
|
||||||
|
{
|
||||||
|
return $"Bot with identifier {request.Identifier} not found or is not running";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bot configuration
|
||||||
|
var updateResult = await bot.UpdateConfiguration(request.NewConfig);
|
||||||
|
|
||||||
|
if (updateResult)
|
||||||
|
{
|
||||||
|
return $"Bot configuration updated successfully for {request.Identifier}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"Failed to update bot configuration for {request.Identifier}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return $"Error updating bot configuration: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,9 +58,90 @@ public class Backtest
|
|||||||
[Required] public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
[Required] public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
||||||
[Required] public double Score { get; set; }
|
[Required] public double Score { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot.
|
||||||
|
/// This method ensures all properties are properly copied, including nullable values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountName">The account name to use for the new bot (can override backtest account)</param>
|
||||||
|
/// <param name="botName">The name for the new bot</param>
|
||||||
|
/// <param name="initialTradingBalance">The initial trading balance for the new bot</param>
|
||||||
|
/// <param name="moneyManagement">Optional money management override (uses backtest's if not provided)</param>
|
||||||
|
/// <returns>A new TradingBotConfig ready for bot creation</returns>
|
||||||
|
public TradingBotConfig CreateLiveBotConfig(
|
||||||
|
string accountName,
|
||||||
|
string botName,
|
||||||
|
decimal initialTradingBalance,
|
||||||
|
MoneyManagement moneyManagement = null)
|
||||||
|
{
|
||||||
|
return new TradingBotConfig
|
||||||
|
{
|
||||||
|
AccountName = accountName,
|
||||||
|
MoneyManagement = moneyManagement ?? Config.MoneyManagement,
|
||||||
|
Ticker = Config.Ticker,
|
||||||
|
ScenarioName = Config.ScenarioName,
|
||||||
|
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,
|
||||||
|
MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value
|
||||||
|
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||||
|
FlipPosition = Config.FlipPosition,
|
||||||
|
Name = botName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a copy of the backtest's configuration for a new backtest.
|
||||||
|
/// Useful for running similar backtests with modified parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate">New start date for the backtest</param>
|
||||||
|
/// <param name="endDate">New end date for the backtest</param>
|
||||||
|
/// <param name="balance">New initial balance for the backtest</param>
|
||||||
|
/// <param name="moneyManagement">Optional money management override</param>
|
||||||
|
/// <returns>A new TradingBotConfig for backtesting</returns>
|
||||||
|
public TradingBotConfig CreateBacktestConfig(
|
||||||
|
DateTime startDate,
|
||||||
|
DateTime endDate,
|
||||||
|
decimal balance,
|
||||||
|
MoneyManagement moneyManagement = null)
|
||||||
|
{
|
||||||
|
return new TradingBotConfig
|
||||||
|
{
|
||||||
|
AccountName = Config.AccountName,
|
||||||
|
MoneyManagement = moneyManagement ?? Config.MoneyManagement,
|
||||||
|
Ticker = Config.Ticker,
|
||||||
|
ScenarioName = Config.ScenarioName,
|
||||||
|
Timeframe = Config.Timeframe,
|
||||||
|
IsForWatchingOnly = Config.IsForWatchingOnly,
|
||||||
|
BotTradingBalance = balance,
|
||||||
|
BotType = Config.BotType,
|
||||||
|
IsForBacktest = true,
|
||||||
|
CooldownPeriod = Config.CooldownPeriod,
|
||||||
|
MaxLossStreak = Config.MaxLossStreak,
|
||||||
|
MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value
|
||||||
|
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||||
|
FlipPosition = Config.FlipPosition,
|
||||||
|
Name = $"Backtest-{Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public string GetStringReport()
|
public string GetStringReport()
|
||||||
{
|
{
|
||||||
|
var timeBasedInfo = Config.MaxPositionTimeHours.HasValue
|
||||||
|
? $" | MaxTime: {Config.MaxPositionTimeHours}h"
|
||||||
|
: " | MaxTime: Disabled";
|
||||||
|
|
||||||
|
var flipInfo = Config.FlipPosition
|
||||||
|
? $" | Flip: {(Config.FlipOnlyWhenInProfit ? "Profit-Only" : "Always")}"
|
||||||
|
: "";
|
||||||
|
|
||||||
return
|
return
|
||||||
$"{Config.Ticker} | {Config.Timeframe} | Positions: {Positions.Count} | Winrate: {WinRate}% | Pnl: {FinalPnl:#.##}$ | %Pnl: {GrowthPercentage:#.##}% | %Hodl: {HodlPercentage:#.##}%";
|
$"{Config.Ticker} | {Config.Timeframe} | Positions: {Positions.Count} | Winrate: {WinRate}% | " +
|
||||||
|
$"Pnl: {FinalPnl:#.##}$ | %Pnl: {GrowthPercentage:#.##}% | %Hodl: {HodlPercentage:#.##}%" +
|
||||||
|
$" | Cooldown: {Config.CooldownPeriod} | MaxLoss: {Config.MaxLossStreak}" +
|
||||||
|
timeBasedInfo + flipInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,4 +19,18 @@ public class TradingBotConfig
|
|||||||
[Required] public int MaxLossStreak { get; set; }
|
[Required] public int MaxLossStreak { get; set; }
|
||||||
[Required] public bool FlipPosition { get; set; }
|
[Required] public bool FlipPosition { get; set; }
|
||||||
[Required] public string Name { get; set; }
|
[Required] public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum time in hours that a position can remain open before being automatically closed.
|
||||||
|
/// If null, time-based position closure is disabled.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? MaxPositionTimeHours { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, positions will only be flipped when the current position is in profit.
|
||||||
|
/// If false, positions will be flipped regardless of profit status.
|
||||||
|
/// Default is true for safer trading.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import type { IModalProps } from '../../../global/type'
|
import type {IModalProps} from '../../../global/type'
|
||||||
|
|
||||||
import ModalHeader from './ModalHeader'
|
import ModalHeader from './ModalHeader'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const Modal: React.FC<IModalProps> = ({
|
|||||||
{showModal ? (
|
{showModal ? (
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className="modal modal-bottom sm:modal-middle modal-open">
|
<div className="modal modal-bottom sm:modal-middle modal-open">
|
||||||
<div className="modal-box">
|
<div className="modal-box !max-w-4xl !w-11/12">
|
||||||
<ModalHeader
|
<ModalHeader
|
||||||
titleHeader={titleHeader}
|
titleHeader={titleHeader}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
import {DotsVerticalIcon, TrashIcon} from '@heroicons/react/solid'
|
import {DotsVerticalIcon, TrashIcon} from '@heroicons/react/solid'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import type {Backtest, MoneyManagement, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
|
import type {
|
||||||
import {BacktestClient, BotClient, BotType,} from '../../../generated/ManagingApi'
|
Backtest,
|
||||||
|
MoneyManagement,
|
||||||
|
RunBacktestRequest,
|
||||||
|
StartBotRequest,
|
||||||
|
Ticker,
|
||||||
|
TradingBotConfig
|
||||||
|
} from '../../../generated/ManagingApi'
|
||||||
|
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||||
import type {IBacktestCards} from '../../../global/type'
|
import type {IBacktestCards} from '../../../global/type'
|
||||||
import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
|
import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
|
||||||
import {CardPosition, CardText, Toast} from '../../mollecules'
|
import {CardPosition, CardText, Toast} from '../../mollecules'
|
||||||
import CardPositionItem from '../Trading/CardPositionItem'
|
import CardPositionItem from '../Trading/CardPositionItem'
|
||||||
import TradeChart from '../Trading/TradeChart/TradeChart'
|
import TradeChart from '../Trading/TradeChart/TradeChart'
|
||||||
|
import {BotNameModal} from '../index'
|
||||||
|
|
||||||
function baseBadgeClass(isOutlined = false) {
|
function baseBadgeClass(isOutlined = false) {
|
||||||
let classes = 'text-xs badge '
|
let classes = 'text-xs badge '
|
||||||
@@ -50,33 +59,81 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
React.useState(false)
|
React.useState(false)
|
||||||
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
||||||
React.useState<MoneyManagement>()
|
React.useState<MoneyManagement>()
|
||||||
|
const [showBotNameModal, setShowBotNameModal] = useState(false)
|
||||||
|
const [isForWatchOnly, setIsForWatchOnly] = useState(false)
|
||||||
|
const [currentBacktest, setCurrentBacktest] = useState<Backtest | null>(null)
|
||||||
|
const [selectedMoneyManagementName, setSelectedMoneyManagementName] = useState<string>('')
|
||||||
|
|
||||||
async function runBot(backtest: Backtest, isForWatchOnly: boolean) {
|
// Fetch money managements
|
||||||
|
const { data: moneyManagements } = useQuery({
|
||||||
|
queryFn: async () => {
|
||||||
|
const moneyManagementClient = new MoneyManagementClient({}, apiUrl)
|
||||||
|
return await moneyManagementClient.moneyManagement_GetMoneyManagements()
|
||||||
|
},
|
||||||
|
queryKey: ['moneyManagements'],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the first money management as default when the data is loaded
|
||||||
|
useEffect(() => {
|
||||||
|
if (moneyManagements && moneyManagements.length > 0) {
|
||||||
|
setSelectedMoneyManagementName(moneyManagements[0].name)
|
||||||
|
}
|
||||||
|
}, [moneyManagements])
|
||||||
|
|
||||||
|
async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) {
|
||||||
const t = new Toast('Bot is starting')
|
const t = new Toast('Bot is starting')
|
||||||
const client = new BotClient({}, apiUrl)
|
const client = new BotClient({}, apiUrl)
|
||||||
|
|
||||||
|
// Check if the money management name is "custom" or contains "custom"
|
||||||
|
const isCustomMoneyManagement =
|
||||||
|
!moneyManagementName ||
|
||||||
|
moneyManagementName.toLowerCase() === 'custom' ||
|
||||||
|
moneyManagementName.toLowerCase().includes('custom');
|
||||||
|
|
||||||
const request: StartBotRequest = {
|
const request: StartBotRequest = {
|
||||||
accountName: backtest.config.accountName,
|
accountName: backtest.config.accountName,
|
||||||
name: backtest.config.ticker + '-' + backtest.config.timeframe?.toString(),
|
name: botName,
|
||||||
botType: BotType.ScalpingBot,
|
botType: backtest.config.botType,
|
||||||
isForWatchOnly: isForWatchOnly,
|
isForWatchOnly: isForWatchOnly,
|
||||||
moneyManagementName: backtest.config.moneyManagement?.name,
|
// Only use the money management name if it's not a custom money management, otherwise use optimized
|
||||||
|
moneyManagementName: isCustomMoneyManagement ?
|
||||||
|
(backtest.optimizedMoneyManagement?.name || backtest.config.moneyManagement?.name) :
|
||||||
|
moneyManagementName,
|
||||||
scenario: backtest.config.scenarioName,
|
scenario: backtest.config.scenarioName,
|
||||||
ticker: backtest.config.ticker as Ticker,
|
ticker: backtest.config.ticker as Ticker,
|
||||||
timeframe: backtest.config.timeframe,
|
timeframe: backtest.config.timeframe,
|
||||||
initialTradingBalance: 1000,
|
initialTradingBalance: initialTradingBalance,
|
||||||
|
cooldownPeriod: backtest.config.cooldownPeriod,
|
||||||
|
maxLossStreak: backtest.config.maxLossStreak,
|
||||||
|
maxPositionTimeHours: backtest.config.maxPositionTimeHours,
|
||||||
|
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit
|
||||||
}
|
}
|
||||||
|
|
||||||
await client
|
await client
|
||||||
.bot_Start(request)
|
.bot_Start(request)
|
||||||
.then((botStatus: string) => {
|
.then((botStatus: string) => {
|
||||||
t.update('info', 'Bot status :' + botStatus)
|
t.update('info', 'Bot status: ' + botStatus)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
t.update('error', 'Error :' + err)
|
t.update('error', 'Error: ' + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenBotNameModal = (backtest: Backtest, isForWatchOnly: boolean) => {
|
||||||
|
setCurrentBacktest(backtest)
|
||||||
|
setIsForWatchOnly(isForWatchOnly)
|
||||||
|
setShowBotNameModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseBotNameModal = () => {
|
||||||
|
setShowBotNameModal(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => {
|
||||||
|
runBot(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
|
||||||
|
setShowBotNameModal(false)
|
||||||
|
}
|
||||||
|
|
||||||
async function runOptimizedBacktest(backtest: Backtest) {
|
async function runOptimizedBacktest(backtest: Backtest) {
|
||||||
const t = new Toast('Optimized backtest is running')
|
const t = new Toast('Optimized backtest is running')
|
||||||
const client = new BacktestClient({}, apiUrl)
|
const client = new BacktestClient({}, apiUrl)
|
||||||
@@ -85,23 +142,26 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
const startDate = backtest.candles[0].date
|
const startDate = backtest.candles[0].date
|
||||||
const endDate = backtest.candles[backtest.candles.length - 1].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
|
||||||
|
}
|
||||||
|
|
||||||
|
const request: RunBacktestRequest = {
|
||||||
|
config: optimizedConfig,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
balance: backtest.walletBalances[0].value,
|
||||||
|
watchOnly: false,
|
||||||
|
save: false,
|
||||||
|
moneyManagementName: undefined, // We're passing the moneyManagement object directly
|
||||||
|
moneyManagement: optimizedConfig.moneyManagement
|
||||||
|
}
|
||||||
|
|
||||||
await client
|
await client
|
||||||
.backtest_Run(
|
.backtest_Run(request)
|
||||||
backtest.config.accountName,
|
|
||||||
backtest.config.botType,
|
|
||||||
backtest.config.ticker as Ticker,
|
|
||||||
backtest.config.scenarioName,
|
|
||||||
backtest.config.timeframe,
|
|
||||||
false, // watchOnly
|
|
||||||
backtest.walletBalances[0].value, // balance
|
|
||||||
'', // moneyManagementName (empty since we're passing the optimized moneyManagement object)
|
|
||||||
startDate, // startDate
|
|
||||||
endDate, // endDate
|
|
||||||
false, // save
|
|
||||||
backtest.config.cooldownPeriod,
|
|
||||||
backtest.config.maxLossStreak,
|
|
||||||
backtest.config.moneyManagement as MoneyManagement, // moneyManagement object
|
|
||||||
)
|
|
||||||
.then((backtest: Backtest) => {
|
.then((backtest: Backtest) => {
|
||||||
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
||||||
setBacktests((arr) => [...arr, backtest])
|
setBacktests((arr) => [...arr, backtest])
|
||||||
@@ -162,7 +222,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
onClick={() => runBot(backtest, false)}
|
onClick={() => handleOpenBotNameModal(backtest, false)}
|
||||||
>
|
>
|
||||||
Run bot
|
Run bot
|
||||||
</button>
|
</button>
|
||||||
@@ -170,7 +230,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
onClick={() => runBot(backtest, true)}
|
onClick={() => handleOpenBotNameModal(backtest, true)}
|
||||||
>
|
>
|
||||||
Run watcher
|
Run watcher
|
||||||
</button>
|
</button>
|
||||||
@@ -303,6 +363,21 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
moneyManagement={selectedMoneyManagement}
|
moneyManagement={selectedMoneyManagement}
|
||||||
onClose={() => setShowMoneyManagementModal(false)}
|
onClose={() => setShowMoneyManagementModal(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{showBotNameModal && currentBacktest && moneyManagements && (
|
||||||
|
<BotNameModal
|
||||||
|
showModal={showBotNameModal}
|
||||||
|
onClose={handleCloseBotNameModal}
|
||||||
|
backtest={currentBacktest}
|
||||||
|
isForWatchOnly={isForWatchOnly}
|
||||||
|
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) =>
|
||||||
|
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
|
||||||
|
}
|
||||||
|
moneyManagements={moneyManagements}
|
||||||
|
selectedMoneyManagement={selectedMoneyManagementName}
|
||||||
|
setSelectedMoneyManagement={setSelectedMoneyManagementName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import {
|
|||||||
DataClient,
|
DataClient,
|
||||||
MoneyManagement,
|
MoneyManagement,
|
||||||
MoneyManagementClient,
|
MoneyManagementClient,
|
||||||
|
RunBacktestRequest,
|
||||||
ScenarioClient,
|
ScenarioClient,
|
||||||
Ticker,
|
Ticker,
|
||||||
Timeframe,
|
Timeframe,
|
||||||
|
TradingBotConfig,
|
||||||
} from '../../../generated/ManagingApi'
|
} from '../../../generated/ManagingApi'
|
||||||
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
||||||
import {Loader, Slider} from '../../atoms'
|
import {Loader, Slider} from '../../atoms'
|
||||||
@@ -42,8 +44,10 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
startDate: defaultStartDateString,
|
startDate: defaultStartDateString,
|
||||||
endDate: defaultEndDateString,
|
endDate: defaultEndDateString,
|
||||||
cooldownPeriod: 1, // Default cooldown period of 1 minute
|
cooldownPeriod: 10, // Default cooldown period of 10 minutes
|
||||||
maxLossStreak: 0 // Default max loss streak of 0 (no limit)
|
maxLossStreak: 0, // Default max loss streak of 0 (no limit)
|
||||||
|
maxPositionTimeHours: null, // Default to null (disabled)
|
||||||
|
flipOnlyWhenInProfit: true // Default to true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
||||||
@@ -100,28 +104,48 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
loopCount: number
|
loopCount: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const t = new Toast(ticker + ' is running')
|
const t = new Toast(ticker + ' is running')
|
||||||
// Use the name of the money management strategy if custom is not provided
|
|
||||||
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
|
|
||||||
|
|
||||||
console.log(customMoneyManagement)
|
console.log(customMoneyManagement)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const backtest = await backtestClient.backtest_Run(
|
// Create the TradingBotConfig
|
||||||
form.accountName,
|
const tradingBotConfig: TradingBotConfig = {
|
||||||
form.botType,
|
accountName: form.accountName,
|
||||||
ticker as Ticker,
|
ticker: ticker as Ticker,
|
||||||
scenarioName,
|
scenarioName: scenarioName,
|
||||||
form.timeframe,
|
timeframe: form.timeframe,
|
||||||
false, // watchOnly
|
botType: form.botType,
|
||||||
balance,
|
isForWatchingOnly: false, // Always false for backtests
|
||||||
moneyManagementName,
|
isForBacktest: true, // Always true for backtests
|
||||||
new Date(form.startDate), // startDate
|
cooldownPeriod: form.cooldownPeriod || 1,
|
||||||
new Date(form.endDate), // endDate
|
maxLossStreak: form.maxLossStreak || 0,
|
||||||
form.save,
|
maxPositionTimeHours: form.maxPositionTimeHours || null,
|
||||||
form.cooldownPeriod, // Use the cooldown period from the form
|
flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true,
|
||||||
form.maxLossStreak, // Add the max loss streak parameter
|
flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type
|
||||||
customMoneyManagement
|
name: `Backtest-${scenarioName}-${ticker}-${new Date().toISOString()}`,
|
||||||
);
|
botTradingBalance: 0, // Will be set in the request
|
||||||
|
moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || {
|
||||||
|
name: 'placeholder',
|
||||||
|
leverage: 1,
|
||||||
|
stopLoss: 0.01,
|
||||||
|
takeProfit: 0.02,
|
||||||
|
timeframe: form.timeframe
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the RunBacktestRequest
|
||||||
|
const request: RunBacktestRequest = {
|
||||||
|
config: tradingBotConfig,
|
||||||
|
startDate: new Date(form.startDate),
|
||||||
|
endDate: new Date(form.endDate),
|
||||||
|
balance: balance,
|
||||||
|
watchOnly: false,
|
||||||
|
save: form.save || false,
|
||||||
|
moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement,
|
||||||
|
moneyManagement: customMoneyManagement
|
||||||
|
};
|
||||||
|
|
||||||
|
const backtest = await backtestClient.backtest_Run(request);
|
||||||
|
|
||||||
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
||||||
setBacktests((arr) => [...arr, backtest])
|
setBacktests((arr) => [...arr, backtest])
|
||||||
@@ -228,6 +252,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
titleHeader="Run Backtest"
|
titleHeader="Run Backtest"
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* First Row: Account & Timeframe */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormInput label="Account" htmlFor="accountName">
|
<FormInput label="Account" htmlFor="accountName">
|
||||||
<select
|
<select
|
||||||
className="select select-bordered w-full"
|
className="select select-bordered w-full"
|
||||||
@@ -248,10 +274,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
|
|
||||||
<FormInput label="Timeframe" htmlFor="timeframe">
|
<FormInput label="Timeframe" htmlFor="timeframe">
|
||||||
<select
|
<select
|
||||||
className="select w-full max-w-xs"
|
className="select select-bordered w-full"
|
||||||
{...register('timeframe', {
|
{...register('timeframe', {
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
setSelectedTimeframeEvent(event)
|
setSelectedTimeframeEvent(event)
|
||||||
@@ -266,7 +291,10 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second Row: Money Management & Bot Type */}
|
||||||
|
<div className="grid 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"
|
||||||
@@ -287,6 +315,17 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
|
<FormInput label="Bot Type" htmlFor="botType">
|
||||||
|
<select className="select select-bordered w-full" {...register('botType')}>
|
||||||
|
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
|
||||||
|
<option key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</FormInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Losing streak info */}
|
{/* Losing streak info */}
|
||||||
{(() => {
|
{(() => {
|
||||||
// Find the selected money management object
|
// Find the selected money management object
|
||||||
@@ -323,17 +362,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormInput label="Type" htmlFor="botType">
|
{/* Third Row: Scenario & Tickers (full width since they need more space) */}
|
||||||
<select className="select w-full max-w-xs" {...register('botType')}>
|
<div className="grid grid-cols-1 gap-4">
|
||||||
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</FormInput>
|
|
||||||
|
|
||||||
|
|
||||||
<FormInput label="Scenario" htmlFor="scenarioName">
|
<FormInput label="Scenario" htmlFor="scenarioName">
|
||||||
<select
|
<select
|
||||||
className="select select-bordered w-full"
|
className="select select-bordered w-full"
|
||||||
@@ -362,7 +392,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fourth Row: Balance & Cooldown Period */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormInput label="Balance" htmlFor="balance">
|
<FormInput label="Balance" htmlFor="balance">
|
||||||
<input
|
<input
|
||||||
@@ -384,6 +416,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</FormInput>
|
</FormInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Fifth Row: Max Loss Streak & Max Position Time */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
|
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
|
||||||
<input
|
<input
|
||||||
@@ -395,6 +428,34 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
|
<FormInput label="Max Position Time (hours)" htmlFor="maxPositionTimeHours">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
min="0"
|
||||||
|
step="0.5"
|
||||||
|
placeholder="Leave empty to disable"
|
||||||
|
{...register('maxPositionTimeHours', { valueAsNumber: true })}
|
||||||
|
/>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Leave empty to disable time-based position closure
|
||||||
|
</div>
|
||||||
|
</FormInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sixth Row: Flip Only When In Profit & Save */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="toggle toggle-primary"
|
||||||
|
{...register('flipOnlyWhenInProfit')}
|
||||||
|
/>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
If enabled, positions will only flip when current position is profitable
|
||||||
|
</div>
|
||||||
|
</FormInput>
|
||||||
|
|
||||||
<FormInput label="Save" htmlFor="save">
|
<FormInput label="Save" htmlFor="save">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -404,6 +465,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</FormInput>
|
</FormInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Seventh Row: Start Date & End Date */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormInput label="Start Date" htmlFor="startDate">
|
<FormInput label="Start Date" htmlFor="startDate">
|
||||||
<input
|
<input
|
||||||
@@ -430,6 +492,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</FormInput>
|
</FormInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Loop Slider (if enabled) */}
|
||||||
{showLoopSlider && (
|
{showLoopSlider && (
|
||||||
<FormInput label="Loop" htmlFor="loop">
|
<FormInput label="Loop" htmlFor="loop">
|
||||||
<Slider
|
<Slider
|
||||||
@@ -443,7 +506,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="modal-action">
|
<div className="modal-action">
|
||||||
<button type="submit" className="btn">
|
<button type="submit" className="btn">
|
||||||
Run
|
Run
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {Backtest, StartBotRequest, Ticker,} from '../../../generated/Managi
|
|||||||
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||||
import type {IBacktestCards} from '../../../global/type'
|
import type {IBacktestCards} from '../../../global/type'
|
||||||
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
|
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
|
||||||
import BotNameModal from '../../mollecules/Modal/BotNameModal'
|
import {BotNameModal} from '../index'
|
||||||
|
|
||||||
import BacktestRowDetails from './backtestRowDetails'
|
import BacktestRowDetails from './backtestRowDetails'
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import React, {useEffect, useState} from 'react'
|
|||||||
|
|
||||||
import type {Backtest, MoneyManagement} from '../../../generated/ManagingApi'
|
import type {Backtest, MoneyManagement} from '../../../generated/ManagingApi'
|
||||||
import type {IModalProps} from '../../../global/type'
|
import type {IModalProps} from '../../../global/type'
|
||||||
|
import {Modal} from '../../mollecules'
|
||||||
import Modal from './Modal'
|
|
||||||
|
|
||||||
interface IBotNameModalProps extends IModalProps {
|
interface IBotNameModalProps extends IModalProps {
|
||||||
backtest: Backtest
|
backtest: Backtest
|
||||||
@@ -30,7 +29,7 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
|
|||||||
// Initialize botName when backtest changes
|
// Initialize botName when backtest changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (backtest) {
|
if (backtest) {
|
||||||
setBotName(`${backtest.ticker}-${backtest.timeframe}`)
|
setBotName(`${backtest.config.ticker}-${backtest.config.timeframe}`)
|
||||||
}
|
}
|
||||||
}, [backtest])
|
}, [backtest])
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
Custom MoneyManagement
|
Custom MoneyManagement
|
||||||
</div>
|
</div>
|
||||||
<div className="collapse-content">
|
<div className="collapse-content">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
|
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
|
||||||
<input
|
<input
|
||||||
id="leverage"
|
id="leverage"
|
||||||
@@ -55,7 +56,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
step="1"
|
step="1"
|
||||||
onChange={(e: any) => setLeverage(e.target.value)}
|
onChange={(e: any) => setLeverage(e.target.value)}
|
||||||
type='number'
|
type='number'
|
||||||
className='input input-bordered'
|
className='input input-bordered w-full'
|
||||||
></input>
|
></input>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
max="100"
|
max="100"
|
||||||
type='number'
|
type='number'
|
||||||
className='input input-bordered'
|
className='input input-bordered w-full'
|
||||||
></input>
|
></input>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
@@ -79,11 +80,12 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
max="100"
|
max="100"
|
||||||
type='number'
|
type='number'
|
||||||
className='input input-bordered'
|
className='input input-bordered w-full'
|
||||||
></input>
|
></input>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
|||||||
export { default as PositionsList } from './Positions/PositionList'
|
export { default as PositionsList } from './Positions/PositionList'
|
||||||
export { default as WorkflowCanvas } from './Workflow/workflowCanvas'
|
export { default as WorkflowCanvas } from './Workflow/workflowCanvas'
|
||||||
export { default as ScenarioModal } from './ScenarioModal'
|
export { default as ScenarioModal } from './ScenarioModal'
|
||||||
|
export { default as BotNameModal } from './BotNameModal/BotNameModal'
|
||||||
|
|||||||
@@ -337,57 +337,11 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<Backtest>(null as any);
|
return Promise.resolve<Backtest>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
backtest_Run(accountName: string | null | undefined, botType: BotType | undefined, ticker: Ticker | undefined, scenarioName: string | null | undefined, timeframe: Timeframe | undefined, watchOnly: boolean | undefined, balance: number | undefined, moneyManagementName: string | null | undefined, startDate: Date | undefined, endDate: Date | undefined, save: boolean | undefined, cooldownPeriod: number | undefined, maxLossStreak: number | undefined, moneyManagement: MoneyManagement | undefined): Promise<Backtest> {
|
backtest_Run(request: RunBacktestRequest): Promise<Backtest> {
|
||||||
let url_ = this.baseUrl + "/Backtest/Run?";
|
let url_ = this.baseUrl + "/Backtest/Run";
|
||||||
if (accountName !== undefined && accountName !== null)
|
|
||||||
url_ += "accountName=" + encodeURIComponent("" + accountName) + "&";
|
|
||||||
if (botType === null)
|
|
||||||
throw new Error("The parameter 'botType' cannot be null.");
|
|
||||||
else if (botType !== undefined)
|
|
||||||
url_ += "botType=" + encodeURIComponent("" + botType) + "&";
|
|
||||||
if (ticker === null)
|
|
||||||
throw new Error("The parameter 'ticker' cannot be null.");
|
|
||||||
else if (ticker !== undefined)
|
|
||||||
url_ += "ticker=" + encodeURIComponent("" + ticker) + "&";
|
|
||||||
if (scenarioName !== undefined && scenarioName !== null)
|
|
||||||
url_ += "scenarioName=" + encodeURIComponent("" + scenarioName) + "&";
|
|
||||||
if (timeframe === null)
|
|
||||||
throw new Error("The parameter 'timeframe' cannot be null.");
|
|
||||||
else if (timeframe !== undefined)
|
|
||||||
url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&";
|
|
||||||
if (watchOnly === null)
|
|
||||||
throw new Error("The parameter 'watchOnly' cannot be null.");
|
|
||||||
else if (watchOnly !== undefined)
|
|
||||||
url_ += "watchOnly=" + encodeURIComponent("" + watchOnly) + "&";
|
|
||||||
if (balance === null)
|
|
||||||
throw new Error("The parameter 'balance' cannot be null.");
|
|
||||||
else if (balance !== undefined)
|
|
||||||
url_ += "balance=" + encodeURIComponent("" + balance) + "&";
|
|
||||||
if (moneyManagementName !== undefined && moneyManagementName !== null)
|
|
||||||
url_ += "moneyManagementName=" + encodeURIComponent("" + moneyManagementName) + "&";
|
|
||||||
if (startDate === null)
|
|
||||||
throw new Error("The parameter 'startDate' cannot be null.");
|
|
||||||
else if (startDate !== undefined)
|
|
||||||
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
|
|
||||||
if (endDate === null)
|
|
||||||
throw new Error("The parameter 'endDate' cannot be null.");
|
|
||||||
else if (endDate !== undefined)
|
|
||||||
url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&";
|
|
||||||
if (save === null)
|
|
||||||
throw new Error("The parameter 'save' cannot be null.");
|
|
||||||
else if (save !== undefined)
|
|
||||||
url_ += "save=" + encodeURIComponent("" + save) + "&";
|
|
||||||
if (cooldownPeriod === null)
|
|
||||||
throw new Error("The parameter 'cooldownPeriod' cannot be null.");
|
|
||||||
else if (cooldownPeriod !== undefined)
|
|
||||||
url_ += "cooldownPeriod=" + encodeURIComponent("" + cooldownPeriod) + "&";
|
|
||||||
if (maxLossStreak === null)
|
|
||||||
throw new Error("The parameter 'maxLossStreak' cannot be null.");
|
|
||||||
else if (maxLossStreak !== undefined)
|
|
||||||
url_ += "maxLossStreak=" + encodeURIComponent("" + maxLossStreak) + "&";
|
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
const content_ = JSON.stringify(moneyManagement);
|
const content_ = JSON.stringify(request);
|
||||||
|
|
||||||
let options_: RequestInit = {
|
let options_: RequestInit = {
|
||||||
body: content_,
|
body: content_,
|
||||||
@@ -811,6 +765,45 @@ export class BotClient extends AuthorizedApiBase {
|
|||||||
}
|
}
|
||||||
return Promise.resolve<Position>(null as any);
|
return Promise.resolve<Position>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bot_UpdateBotConfig(request: UpdateBotConfigRequest): Promise<string> {
|
||||||
|
let url_ = this.baseUrl + "/Bot/UpdateConfig";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
const content_ = JSON.stringify(request);
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
body: content_,
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||||
|
return this.http.fetch(url_, transformedOptions_);
|
||||||
|
}).then((_response: Response) => {
|
||||||
|
return this.processBot_UpdateBotConfig(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processBot_UpdateBotConfig(response: Response): Promise<string> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string;
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<string>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataClient extends AuthorizedApiBase {
|
export class DataClient extends AuthorizedApiBase {
|
||||||
@@ -2705,6 +2698,8 @@ export interface TradingBotConfig {
|
|||||||
maxLossStreak: number;
|
maxLossStreak: number;
|
||||||
flipPosition: boolean;
|
flipPosition: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
maxPositionTimeHours?: number | null;
|
||||||
|
flipOnlyWhenInProfit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MoneyManagement {
|
export interface MoneyManagement {
|
||||||
@@ -3106,6 +3101,17 @@ export interface SuperTrendResult extends ResultBase {
|
|||||||
lowerBand?: number | null;
|
lowerBand?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RunBacktestRequest {
|
||||||
|
config?: TradingBotConfig | null;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
balance?: number;
|
||||||
|
watchOnly?: boolean;
|
||||||
|
save?: boolean;
|
||||||
|
moneyManagementName?: string | null;
|
||||||
|
moneyManagement?: MoneyManagement | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StartBotRequest {
|
export interface StartBotRequest {
|
||||||
botType?: BotType;
|
botType?: BotType;
|
||||||
identifier?: string | null;
|
identifier?: string | null;
|
||||||
@@ -3119,6 +3125,8 @@ export interface StartBotRequest {
|
|||||||
cooldownPeriod?: number;
|
cooldownPeriod?: number;
|
||||||
maxLossStreak?: number;
|
maxLossStreak?: number;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
|
maxPositionTimeHours?: number | null;
|
||||||
|
flipOnlyWhenInProfit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBot {
|
export interface TradingBot {
|
||||||
@@ -3138,6 +3146,8 @@ export interface TradingBot {
|
|||||||
moneyManagement: MoneyManagement;
|
moneyManagement: MoneyManagement;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
agentName: string;
|
agentName: string;
|
||||||
|
maxPositionTimeHours: number;
|
||||||
|
flipOnlyWhenInProfit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenPositionManuallyRequest {
|
export interface OpenPositionManuallyRequest {
|
||||||
@@ -3150,6 +3160,23 @@ export interface ClosePositionRequest {
|
|||||||
positionId?: string | null;
|
positionId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateBotConfigRequest {
|
||||||
|
identifier?: string | null;
|
||||||
|
accountName?: string | null;
|
||||||
|
moneyManagementName?: string | null;
|
||||||
|
ticker?: Ticker | null;
|
||||||
|
scenarioName?: string | null;
|
||||||
|
timeframe?: Timeframe | null;
|
||||||
|
isForWatchingOnly?: boolean | null;
|
||||||
|
botTradingBalance?: number | null;
|
||||||
|
cooldownPeriod?: number | null;
|
||||||
|
maxLossStreak?: number | null;
|
||||||
|
maxPositionTimeHours?: number | null;
|
||||||
|
flipOnlyWhenInProfit?: boolean | null;
|
||||||
|
flipPosition?: boolean | null;
|
||||||
|
name?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TickerInfos {
|
export interface TickerInfos {
|
||||||
ticker?: Ticker;
|
ticker?: Ticker;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ export type IBacktestsFormInput = {
|
|||||||
endDate: string
|
endDate: string
|
||||||
cooldownPeriod: number
|
cooldownPeriod: number
|
||||||
maxLossStreak: number
|
maxLossStreak: number
|
||||||
|
maxPositionTimeHours?: number | null
|
||||||
|
flipOnlyWhenInProfit?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IBacktestCards = {
|
export type IBacktestCards = {
|
||||||
|
|||||||
Reference in New Issue
Block a user