Update config
This commit is contained in:
@@ -169,7 +169,8 @@ public class BacktestController : BaseController
|
||||
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
FlipPosition = request.Config.FlipPosition,
|
||||
Name = request.Config.Name ?? $"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"
|
||||
Name = request.Config.Name ?? $"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
|
||||
};
|
||||
|
||||
switch (request.Config.BotType)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
@@ -11,7 +13,6 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using static Managing.Common.Enums;
|
||||
using ApiTradingBot = Managing.Api.Models.Responses.TradingBot;
|
||||
|
||||
namespace Managing.Api.Controllers;
|
||||
|
||||
@@ -142,6 +143,30 @@ public class BotController : BaseController
|
||||
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||
}
|
||||
|
||||
// Validate cooldown period
|
||||
if (request.Config.CooldownPeriod < 1)
|
||||
{
|
||||
return BadRequest("Cooldown period must be at least 1 candle");
|
||||
}
|
||||
|
||||
// Validate max loss streak
|
||||
if (request.Config.MaxLossStreak < 0)
|
||||
{
|
||||
return BadRequest("Max loss streak cannot be negative");
|
||||
}
|
||||
|
||||
// Validate max position time hours
|
||||
if (request.Config.MaxPositionTimeHours.HasValue && request.Config.MaxPositionTimeHours.Value <= 0)
|
||||
{
|
||||
return BadRequest("Max position time hours must be greater than 0 if specified");
|
||||
}
|
||||
|
||||
// Validate CloseEarlyWhenProfitable consistency
|
||||
if (request.Config.CloseEarlyWhenProfitable && !request.Config.MaxPositionTimeHours.HasValue)
|
||||
{
|
||||
return BadRequest("CloseEarlyWhenProfitable can only be enabled when MaxPositionTimeHours is set");
|
||||
}
|
||||
|
||||
// Update the config with final money management
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
@@ -159,7 +184,8 @@ public class BotController : BaseController
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
IsForBacktest = false,
|
||||
FlipPosition = request.Config.BotType == BotType.FlippingBot,
|
||||
Name = request.Config.Name
|
||||
Name = request.Config.Name,
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
|
||||
};
|
||||
|
||||
var result = await _mediator.Send(new StartBotCommand(config, request.Config.Name, user));
|
||||
@@ -250,11 +276,11 @@ public class BotController : BaseController
|
||||
{
|
||||
var bots = await GetBotList();
|
||||
// Filter to only include bots owned by the current user
|
||||
var userBots = new List<ApiTradingBot>();
|
||||
var userBots = new List<TradingBotResponse>();
|
||||
|
||||
foreach (var bot in bots)
|
||||
{
|
||||
var account = await _accountService.GetAccount(bot.AccountName, true, false);
|
||||
var account = await _accountService.GetAccount(bot.Config.AccountName, true, false);
|
||||
// Compare the user names
|
||||
if (account != null && account.User != null && account.User.Name == user.Name)
|
||||
{
|
||||
@@ -264,7 +290,7 @@ public class BotController : BaseController
|
||||
|
||||
foreach (var bot in userBots)
|
||||
{
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier));
|
||||
await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier));
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {bot.Identifier} paused by {user.Name}.", "Info");
|
||||
}
|
||||
@@ -326,12 +352,12 @@ public class BotController : BaseController
|
||||
{
|
||||
var bots = await GetBotList();
|
||||
// Filter to only include bots owned by the current user
|
||||
var userBots = new List<ApiTradingBot>();
|
||||
var userBots = new List<TradingBotResponse>();
|
||||
var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
|
||||
|
||||
foreach (var bot in bots)
|
||||
{
|
||||
var account = await accountService.GetAccount(bot.AccountName, true, false);
|
||||
var account = await accountService.GetAccount(bot.Config.AccountName, true, false);
|
||||
// Compare the user names
|
||||
if (account != null && account.User != null && account.User.Name == user.Name)
|
||||
{
|
||||
@@ -343,7 +369,7 @@ public class BotController : BaseController
|
||||
{
|
||||
// We can't directly restart a bot with just BotType and Name
|
||||
// Instead, stop the bot and then retrieve the backup to start it again
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier));
|
||||
await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier));
|
||||
|
||||
// Get the saved bot backup
|
||||
var backup = _botService.GetBotBackup(bot.Identifier);
|
||||
@@ -401,7 +427,7 @@ public class BotController : BaseController
|
||||
/// </summary>
|
||||
/// <returns>A list of active trading bots.</returns>
|
||||
[HttpGet]
|
||||
public async Task<List<ApiTradingBot>> GetActiveBots()
|
||||
public async Task<List<TradingBotResponse>> GetActiveBots()
|
||||
{
|
||||
return await GetBotList();
|
||||
}
|
||||
@@ -410,33 +436,24 @@ public class BotController : BaseController
|
||||
/// Retrieves a list of active bots by sending a command to the mediator.
|
||||
/// </summary>
|
||||
/// <returns>A list of trading bots.</returns>
|
||||
private async Task<List<ApiTradingBot>> GetBotList()
|
||||
private async Task<List<TradingBotResponse>> GetBotList()
|
||||
{
|
||||
var result = await _mediator.Send(new GetActiveBotsCommand());
|
||||
var list = new List<ApiTradingBot>();
|
||||
var list = new List<TradingBotResponse>();
|
||||
|
||||
foreach (var item in result)
|
||||
{
|
||||
list.Add(new ApiTradingBot
|
||||
list.Add(new TradingBotResponse
|
||||
{
|
||||
Status = item.GetStatus(),
|
||||
Name = item.Name,
|
||||
Signals = item.Signals.ToList(),
|
||||
Positions = item.Positions,
|
||||
Candles = item.Candles.DistinctBy(c => c.Date).ToList(),
|
||||
WinRate = item.GetWinRate(),
|
||||
ProfitAndLoss = item.GetProfitAndLoss(),
|
||||
Timeframe = item.Config.Timeframe,
|
||||
Ticker = item.Config.Ticker,
|
||||
Scenario = item.Config.ScenarioName,
|
||||
IsForWatchingOnly = item.Config.IsForWatchingOnly,
|
||||
BotType = item.Config.BotType,
|
||||
AccountName = item.Config.AccountName,
|
||||
MoneyManagement = item.Config.MoneyManagement,
|
||||
Identifier = item.Identifier,
|
||||
AgentName = item.User.AgentName,
|
||||
MaxPositionTimeHours = item.Config.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = item.Config.FlipOnlyWhenInProfit
|
||||
Config = item.Config // Contains all configuration properties
|
||||
});
|
||||
}
|
||||
|
||||
@@ -562,97 +579,107 @@ public class BotController : BaseController
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the configuration of a running bot
|
||||
/// Updates the configuration of an existing bot.
|
||||
/// </summary>
|
||||
/// <param name="request">The request containing the new bot configuration</param>
|
||||
/// <returns>A response indicating the result of the operation</returns>
|
||||
/// <param name="request">The update request containing the bot identifier and new configuration</param>
|
||||
/// <returns>Success message</returns>
|
||||
[HttpPut]
|
||||
[Route("UpdateConfig")]
|
||||
public async Task<ActionResult<string>> UpdateBotConfig([FromBody] UpdateBotConfigRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
var user = await GetUser();
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized("User not found");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.Identifier))
|
||||
{
|
||||
return BadRequest("Bot identifier is required");
|
||||
}
|
||||
|
||||
if (request.Config == null)
|
||||
{
|
||||
return BadRequest("Bot configuration is required");
|
||||
}
|
||||
|
||||
// First, check if the user owns the existing bot
|
||||
if (!await UserOwnsBotAccount(request.Identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to update this bot's configuration");
|
||||
return Forbid("You don't have permission to update this bot");
|
||||
}
|
||||
|
||||
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();
|
||||
// Get the existing bot to ensure it exists and get current config
|
||||
var bots = _botService.GetActiveBots();
|
||||
var existingBot = bots.FirstOrDefault(b => b.Identifier == request.Identifier);
|
||||
|
||||
// Validate money management if provided
|
||||
MoneyManagement moneyManagement = null;
|
||||
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
if (existingBot == null)
|
||||
{
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
return NotFound($"Bot with identifier '{request.Identifier}' not found");
|
||||
}
|
||||
|
||||
// If the account is being changed, verify the user owns the new account too
|
||||
if (existingBot.Config.AccountName != request.Config.AccountName)
|
||||
{
|
||||
if (!await UserOwnsBotAccount(null, request.Config.AccountName))
|
||||
{
|
||||
return Forbid("You don't have permission to use this account");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the money management if provided
|
||||
if (request.Config.MoneyManagement != null)
|
||||
{
|
||||
// Check if the money management belongs to the user
|
||||
var userMoneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagement.Name);
|
||||
if (userMoneyManagement != null && userMoneyManagement.User?.Name != user.Name)
|
||||
{
|
||||
return Forbid("You don't have permission to use this money management");
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
{
|
||||
// If MoneyManagement is null but MoneyManagementName is provided, load it
|
||||
var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
if (moneyManagement == null)
|
||||
{
|
||||
return BadRequest("Money management not found");
|
||||
return BadRequest($"Money management '{request.MoneyManagementName}' not found");
|
||||
}
|
||||
|
||||
if (moneyManagement.User?.Name != user.Name)
|
||||
{
|
||||
return Forbid("You don't have permission to use this money management");
|
||||
}
|
||||
|
||||
request.Config.MoneyManagement = moneyManagement;
|
||||
}
|
||||
|
||||
// Validate CloseEarlyWhenProfitable requires MaxPositionTimeHours
|
||||
if (request.Config.CloseEarlyWhenProfitable && !request.Config.MaxPositionTimeHours.HasValue)
|
||||
{
|
||||
return BadRequest("CloseEarlyWhenProfitable requires MaxPositionTimeHours to be set");
|
||||
}
|
||||
|
||||
// Update the bot configuration using the new method
|
||||
var success = await _botService.UpdateBotConfiguration(request.Identifier, request.Config);
|
||||
|
||||
if (success)
|
||||
{
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {request.Identifier} configuration updated successfully by {user.Name}.", "Info");
|
||||
|
||||
return Ok("Bot configuration updated successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep existing money management if not provided
|
||||
moneyManagement = bot.Config.MoneyManagement;
|
||||
return BadRequest("Failed to update bot configuration");
|
||||
}
|
||||
|
||||
// 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");
|
||||
_logger.LogError(ex, "Error updating bot configuration for identifier {Identifier}", request.Identifier);
|
||||
return StatusCode(500, $"Error updating bot configuration: {ex.Message}");
|
||||
}
|
||||
}
|
||||
@@ -712,72 +739,19 @@ public class StartBotRequest
|
||||
public class UpdateBotConfigRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier of the bot to update
|
||||
/// The unique identifier of the bot to update
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The account name to use (optional - will keep existing if not provided)
|
||||
/// The new trading bot configuration
|
||||
/// </summary>
|
||||
public string? AccountName { get; set; }
|
||||
[Required]
|
||||
public TradingBotConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The money management name to use (optional - will keep existing if not provided)
|
||||
/// Optional: Money management name to load if Config.MoneyManagement is null
|
||||
/// </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; }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses
|
||||
{
|
||||
public class TradingBot
|
||||
{
|
||||
[Required] public string Name { get; internal set; }
|
||||
[Required] public string Status { get; internal set; }
|
||||
[Required] public List<Signal> Signals { get; internal set; }
|
||||
[Required] public List<Position> Positions { get; internal set; }
|
||||
[Required] public List<Candle> Candles { get; internal set; }
|
||||
[Required] public int WinRate { get; internal set; }
|
||||
[Required] public decimal ProfitAndLoss { get; internal set; }
|
||||
[Required] public Timeframe Timeframe { get; internal set; }
|
||||
[Required] public Ticker Ticker { get; internal set; }
|
||||
[Required] public string Scenario { get; internal set; }
|
||||
[Required] public bool IsForWatchingOnly { get; internal set; }
|
||||
[Required] public BotType BotType { get; internal set; }
|
||||
[Required] public string AccountName { get; internal set; }
|
||||
[Required] public MoneyManagement MoneyManagement { get; internal set; }
|
||||
[Required] public string Identifier { 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; }
|
||||
}
|
||||
}
|
||||
56
src/Managing.Api/Models/Responses/TradingBotResponse.cs
Normal file
56
src/Managing.Api/Models/Responses/TradingBotResponse.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
|
||||
namespace Managing.Api.Models.Responses
|
||||
{
|
||||
public class TradingBotResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Current status of the bot (Up, Down, etc.)
|
||||
/// </summary>
|
||||
[Required] public string Status { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of signals generated by the bot
|
||||
/// </summary>
|
||||
[Required] public List<Signal> Signals { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of positions opened by the bot
|
||||
/// </summary>
|
||||
[Required] public List<Position> Positions { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Candles used by the bot for analysis
|
||||
/// </summary>
|
||||
[Required] public List<Candle> Candles { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current win rate percentage
|
||||
/// </summary>
|
||||
[Required] public int WinRate { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current profit and loss
|
||||
/// </summary>
|
||||
[Required] public decimal ProfitAndLoss { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the bot
|
||||
/// </summary>
|
||||
[Required] public string Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Agent name associated with the bot
|
||||
/// </summary>
|
||||
[Required] public string AgentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full trading bot configuration
|
||||
/// </summary>
|
||||
[Required] public TradingBotConfig Config { get; internal set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user