feat(derivatives): fix Trade Republic derivative pagination streaming and picker modal

This commit is contained in:
2026-08-15 19:30:15 +02:00
parent 7e18257e3e
commit 882d24a316
7 changed files with 1045 additions and 26 deletions
@@ -268,6 +268,101 @@ public class AssetsController : ControllerBase
return NotFound(new { message = $"Keine technische Analyse für Asset '{normalizedSymbol}' verfügbar." });
}
/// <summary>
/// Liest verfügbare Derivate (Knock-Outs, Optionsscheine etc.) für ein Basiswert-Asset via FinlyticAssets MQTT RPC.
/// </summary>
[HttpGet("{isin}/derivatives")]
public async Task<IActionResult> GetDerivatives(
[FromRoute] string isin,
[FromQuery] string optionType = "long",
[FromQuery] decimal? targetLeverage = null,
[FromQuery] decimal? minLeverage = null,
[FromQuery] decimal? maxLeverage = null,
[FromQuery] string? search = null,
[FromQuery] string? after = null,
[FromQuery] int? page = null,
[FromQuery] int? pageSize = null,
[FromQuery] bool forceRefresh = false,
CancellationToken cancellationToken = default)
{
string cleanIsin = isin.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(cleanIsin))
{
return BadRequest(new { message = "Eine gültige ISIN ist erforderlich." });
}
try
{
if (_mqttClient.IsConnected)
{
var req = new GetDerivativesRequest(
UnderlyingIsin: cleanIsin,
OptionType: optionType.ToLowerInvariant(),
TargetLeverage: targetLeverage,
After: after,
Page: page,
ForceRefresh: forceRefresh
);
var rpcResult = await _mqttClient.SendRpcRequestAsync<List<AssetDto>, GetDerivativesRequest>(
"assets_GetDerivatives",
req,
TimeSpan.FromSeconds(30)
);
if (rpcResult != null)
{
var derivatives = rpcResult.OfType<DerivativeDto>().ToList();
if (minLeverage.HasValue)
{
derivatives = derivatives.Where(d => d.Leverage >= minLeverage.Value).ToList();
}
if (maxLeverage.HasValue)
{
derivatives = derivatives.Where(d => d.Leverage <= maxLeverage.Value).ToList();
}
if (!string.IsNullOrWhiteSpace(search))
{
string q = search.Trim();
derivatives = derivatives.Where(d =>
(d.Isin != null && d.Isin.Contains(q, StringComparison.OrdinalIgnoreCase)) ||
(d.Issuer != null && d.Issuer.Contains(q, StringComparison.OrdinalIgnoreCase)) ||
(d.IssuerDisplayName != null && d.IssuerDisplayName.Contains(q, StringComparison.OrdinalIgnoreCase)) ||
(d.ProductCategoryName != null && d.ProductCategoryName.Contains(q, StringComparison.OrdinalIgnoreCase))
).ToList();
}
derivatives = derivatives.OrderBy(d => d.Leverage).ToList();
int totalCount = derivatives.Count;
if (page.HasValue && pageSize.HasValue && page.Value > 0 && pageSize.Value > 0)
{
int pSize = Math.Clamp(pageSize.Value, 1, 200);
int pIndex = Math.Max(1, page.Value);
int totalPages = (int)Math.Ceiling((double)totalCount / pSize);
Response.Headers["X-Total-Count"] = totalCount.ToString();
Response.Headers["X-Total-Pages"] = totalPages.ToString();
Response.Headers["X-Current-Page"] = pIndex.ToString();
derivatives = derivatives.Skip((pIndex - 1) * pSize).Take(pSize).ToList();
}
return Ok(derivatives);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[AssetsController] Fehler beim Abrufen der Derivate für {Isin}", cleanIsin);
}
return Ok(new List<DerivativeDto>());
}
/// <summary>
/// Serviert das SVG-Logo direkt aus dem gemounteten Docker Volume (Volumes.LogosRelativePath).
/// </summary>