257 lines
9.6 KiB
C#
257 lines
9.6 KiB
C#
using Managing.Application.Abstractions;
|
|
using Managing.Application.Abstractions.Services;
|
|
using Managing.Application.Hubs;
|
|
using Managing.Domain.Backtests;
|
|
using Managing.Domain.Bots;
|
|
using Managing.Domain.MoneyManagements;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using static Managing.Common.Enums;
|
|
|
|
namespace Managing.Api.Controllers;
|
|
|
|
/// <summary>
|
|
/// Controller for managing backtest operations.
|
|
/// Provides endpoints for creating, retrieving, and deleting backtests.
|
|
/// Returns complete backtest configurations for easy bot deployment.
|
|
/// Requires authorization for access.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
[Produces("application/json")]
|
|
public class BacktestController : BaseController
|
|
{
|
|
private readonly IHubContext<BotHub> _hubContext;
|
|
private readonly IBacktester _backtester;
|
|
private readonly IScenarioService _scenarioService;
|
|
private readonly IAccountService _accountService;
|
|
private readonly IMoneyManagementService _moneyManagementService;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BacktestController"/> class.
|
|
/// </summary>
|
|
/// <param name="hubContext">The SignalR hub context for real-time communication.</param>
|
|
/// <param name="backtester">The service for backtesting strategies.</param>
|
|
/// <param name="scenarioService">The service for managing scenarios.</param>
|
|
/// <param name="accountService">The service for account management.</param>
|
|
/// <param name="moneyManagementService">The service for money management strategies.</param>
|
|
public BacktestController(
|
|
IHubContext<BotHub> hubContext,
|
|
IBacktester backtester,
|
|
IScenarioService scenarioService,
|
|
IAccountService accountService,
|
|
IMoneyManagementService moneyManagementService,
|
|
IUserService userService) : base(userService)
|
|
{
|
|
_hubContext = hubContext;
|
|
_backtester = backtester;
|
|
_scenarioService = scenarioService;
|
|
_accountService = accountService;
|
|
_moneyManagementService = moneyManagementService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all backtests for the authenticated user.
|
|
/// Each backtest includes the complete TradingBotConfig for easy bot deployment.
|
|
/// </summary>
|
|
/// <returns>A list of backtests with complete configurations.</returns>
|
|
[HttpGet]
|
|
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
|
|
{
|
|
var user = await GetUser();
|
|
return Ok(await _backtester.GetBacktestsByUser(user));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a specific backtest by ID for the authenticated user.
|
|
/// This endpoint will also populate the candles for visualization and includes
|
|
/// the complete TradingBotConfig that can be used to start a new bot.
|
|
/// </summary>
|
|
/// <param name="id">The ID of the backtest to retrieve.</param>
|
|
/// <returns>The requested backtest with populated candle data and complete configuration.</returns>
|
|
[HttpGet("{id}")]
|
|
public async Task<ActionResult<Backtest>> Backtest(string id)
|
|
{
|
|
var user = await GetUser();
|
|
var backtest = _backtester.GetBacktestByIdForUser(user, id);
|
|
|
|
if (backtest == null)
|
|
{
|
|
return NotFound($"Backtest with ID {id} not found or doesn't belong to the current user.");
|
|
}
|
|
|
|
return Ok(backtest);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a specific backtest by ID for the authenticated user.
|
|
/// </summary>
|
|
/// <param name="id">The ID of the backtest to delete.</param>
|
|
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
|
[HttpDelete]
|
|
public async Task<ActionResult> DeleteBacktest(string id)
|
|
{
|
|
var user = await GetUser();
|
|
return Ok(_backtester.DeleteBacktestByUser(user, id));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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>
|
|
/// <param name="request">The backtest request containing configuration and parameters.</param>
|
|
/// <returns>The result of the backtest with complete configuration.</returns>
|
|
[HttpPost]
|
|
[Route("Run")]
|
|
public async Task<ActionResult<Backtest>> Run([FromBody] RunBacktestRequest request)
|
|
{
|
|
if (request?.Config == null)
|
|
{
|
|
return BadRequest("Backtest configuration is required");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(request.Config.AccountName))
|
|
{
|
|
return BadRequest("Account name is required");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(request.Config.ScenarioName) && request.Config.Scenario == null)
|
|
{
|
|
return BadRequest("Either scenario name or scenario object is required");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(request.MoneyManagementName) && request.MoneyManagement == null)
|
|
{
|
|
return BadRequest("Either money management name or money management object is required");
|
|
}
|
|
|
|
try
|
|
{
|
|
Backtest backtestResult = null;
|
|
var account = await _accountService.GetAccount(request.Config.AccountName, true, false);
|
|
var user = await GetUser();
|
|
|
|
// 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 - TradingBot will handle scenario loading
|
|
var backtestConfig = new TradingBotConfig
|
|
{
|
|
AccountName = request.Config.AccountName,
|
|
MoneyManagement = moneyManagement,
|
|
Ticker = request.Config.Ticker,
|
|
ScenarioName = request.Config.ScenarioName,
|
|
Scenario = request.Config.Scenario,
|
|
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 ?? request.Config.Scenario?.Name ?? "Custom"}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
|
|
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
|
|
};
|
|
|
|
switch (request.Config.BotType)
|
|
{
|
|
case BotType.SimpleBot:
|
|
// SimpleBot backtest not implemented yet
|
|
break;
|
|
case BotType.ScalpingBot:
|
|
case BotType.FlippingBot:
|
|
backtestResult = await _backtester.RunTradingBotBacktest(
|
|
backtestConfig,
|
|
request.StartDate,
|
|
request.EndDate,
|
|
user,
|
|
request.Save);
|
|
break;
|
|
}
|
|
|
|
await NotifyBacktesingSubscriberAsync(backtestResult);
|
|
|
|
return Ok(backtestResult);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, $"Error running backtest: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notifies subscribers about the backtesting results via SignalR.
|
|
/// </summary>
|
|
/// <param name="backtesting">The backtest result to notify subscribers about.</param>
|
|
private async Task NotifyBacktesingSubscriberAsync(Backtest backtesting)
|
|
{
|
|
if (backtesting != null)
|
|
{
|
|
await _hubContext.Clients.All.SendAsync("BacktestsSubscription", backtesting);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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 to use (optional if MoneyManagement is provided)
|
|
/// </summary>
|
|
public string? MoneyManagementName { get; set; }
|
|
|
|
/// <summary>
|
|
/// The money management details (optional if MoneyManagementName is provided)
|
|
/// </summary>
|
|
public MoneyManagement? MoneyManagement { get; set; }
|
|
} |