202 lines
7.2 KiB
C#
202 lines
7.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using FinlyticCore.Dtos;
|
|
using FinlyticCore.Dtos.Simulation;
|
|
using FinlyticBackend.Util;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace FinlyticBackend.Controllers;
|
|
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("api/v1/simulation")]
|
|
public class SimulationController : ControllerBase
|
|
{
|
|
private readonly BackendMqttBridge _mqttBridge;
|
|
private readonly ILogger<SimulationController> _logger;
|
|
|
|
public SimulationController(BackendMqttBridge mqttBridge, ILogger<SimulationController> logger)
|
|
{
|
|
_mqttBridge = mqttBridge;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpPost("run")]
|
|
public async Task<IActionResult> RunBacktest([FromBody] BacktestRequestDto request)
|
|
{
|
|
if (request == null || string.IsNullOrWhiteSpace(request.Isin) || string.IsNullOrWhiteSpace(request.StrategyKey))
|
|
{
|
|
return BadRequest(new { Message = "ISIN and StrategyKey are required for backtesting." });
|
|
}
|
|
|
|
try
|
|
{
|
|
_logger.LogInformation("Starting backtest via REST for {Isin} ({StrategyKey}) on {Timeframe}", request.Isin, request.StrategyKey, request.Timeframe);
|
|
|
|
var report = await _mqttBridge.SendRpcRequestAsync<BacktestReportDto, BacktestRequestDto>(
|
|
"sim_RunBacktest",
|
|
request,
|
|
TimeSpan.FromSeconds(25)
|
|
);
|
|
|
|
if (report == null)
|
|
{
|
|
return StatusCode(504, new { Message = "Simulation service timed out or did not return a report." });
|
|
}
|
|
|
|
return Ok(report);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error running backtest for {Isin}", request.Isin);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet("matrix/{isin}")]
|
|
public async Task<IActionResult> GetStrategyMatrix(string isin)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(isin))
|
|
{
|
|
return BadRequest(new { Message = "ISIN is required." });
|
|
}
|
|
|
|
try
|
|
{
|
|
var matrix = await _mqttBridge.SendRpcRequestAsync<List<StrategyAssetReliabilityDto>, IsinRequest>(
|
|
"sim_GetMatrixForAsset",
|
|
new IsinRequest(isin, null, false),
|
|
TimeSpan.FromSeconds(5)
|
|
);
|
|
|
|
return Ok(matrix ?? new List<StrategyAssetReliabilityDto>());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching strategy matrix for {Isin}", isin);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lightweight history of past backtest runs for an ISIN, optionally narrowed to one strategy - every run
|
|
/// is already persisted server-side (<c>sim_RunBacktest</c>) but was previously only reachable indirectly
|
|
/// via the reliability matrix, never queryable as a history in its own right.
|
|
/// </summary>
|
|
[HttpGet("history/{isin}")]
|
|
public async Task<IActionResult> GetBacktestHistory(string isin, [FromQuery] string? strategyKey = null, [FromQuery] int limit = 20)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(isin))
|
|
{
|
|
return BadRequest(new { Message = "ISIN is required." });
|
|
}
|
|
|
|
try
|
|
{
|
|
var history = await _mqttBridge.SendRpcRequestAsync<List<BacktestHistoryEntryDto>, GetBacktestHistoryRequest>(
|
|
"sim_GetBacktestHistory",
|
|
new GetBacktestHistoryRequest(isin, strategyKey, limit),
|
|
TimeSpan.FromSeconds(5)
|
|
);
|
|
|
|
return Ok(history ?? new List<BacktestHistoryEntryDto>());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching backtest history for {Isin}", isin);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>Full report (trades + equity curve) for one specific past backtest run, for drilling into a history entry.</summary>
|
|
[HttpGet("history/run/{runId:guid}")]
|
|
public async Task<IActionResult> GetBacktestRunDetail(Guid runId)
|
|
{
|
|
try
|
|
{
|
|
var report = await _mqttBridge.SendRpcRequestAsync<BacktestReportDto, GetBacktestRunDetailRequest>(
|
|
"sim_GetBacktestRunDetail",
|
|
new GetBacktestRunDetailRequest(runId),
|
|
TimeSpan.FromSeconds(5)
|
|
);
|
|
|
|
if (report == null)
|
|
{
|
|
return NotFound(new { Message = $"No backtest run found for RunId {runId}." });
|
|
}
|
|
|
|
return Ok(report);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching backtest run detail for {RunId}", runId);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>Saved indicator-parameter profile for one (Isin, StrategyKey) pair, or 404 if none was ever saved.</summary>
|
|
[HttpGet("parameters/{isin}/{strategyKey}")]
|
|
public async Task<IActionResult> GetStrategyParameters(string isin, string strategyKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(isin) || string.IsNullOrWhiteSpace(strategyKey))
|
|
{
|
|
return BadRequest(new { Message = "ISIN and StrategyKey are required." });
|
|
}
|
|
|
|
try
|
|
{
|
|
var profile = await _mqttBridge.SendRpcRequestAsync<StrategyParameterProfileDto, GetStrategyParametersRequest>(
|
|
"sim_GetStrategyParameters",
|
|
new GetStrategyParametersRequest(isin, strategyKey),
|
|
TimeSpan.FromSeconds(5)
|
|
);
|
|
|
|
if (profile == null)
|
|
{
|
|
return NotFound(new { Message = $"No saved parameter profile for {isin}/{strategyKey}." });
|
|
}
|
|
|
|
return Ok(profile);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching strategy parameters for {Isin}/{StrategyKey}", isin, strategyKey);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>Saves/updates a per-asset/per-strategy indicator-parameter profile for future backtests to reuse.</summary>
|
|
[HttpPost("parameters")]
|
|
public async Task<IActionResult> SaveStrategyParameters([FromBody] SaveStrategyParametersRequest request)
|
|
{
|
|
if (request == null || string.IsNullOrWhiteSpace(request.Isin) || string.IsNullOrWhiteSpace(request.StrategyKey))
|
|
{
|
|
return BadRequest(new { Message = "ISIN and StrategyKey are required." });
|
|
}
|
|
|
|
try
|
|
{
|
|
var profile = await _mqttBridge.SendRpcRequestAsync<StrategyParameterProfileDto, SaveStrategyParametersRequest>(
|
|
"sim_SaveStrategyParameters",
|
|
request,
|
|
TimeSpan.FromSeconds(5)
|
|
);
|
|
|
|
if (profile == null)
|
|
{
|
|
return StatusCode(504, new { Message = "Simulation service timed out or did not confirm the save." });
|
|
}
|
|
|
|
return Ok(profile);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error saving strategy parameters for {Isin}/{StrategyKey}", request.Isin, request.StrategyKey);
|
|
return StatusCode(500, new { Message = ex.Message });
|
|
}
|
|
}
|
|
}
|