Files

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 });
}
}
}