feat(core): add shared DTOs, MqttTopics constants, DatabaseBootstrapper, and ManagedMqttClient extensions

This commit is contained in:
2026-08-24 21:35:24 +02:00
parent 6ab84fe1de
commit 44b161d509
39 changed files with 2545 additions and 709 deletions
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
namespace FinlyticCore.Dtos.TechnicalAnalysis;
/// <summary>
/// Execution context supplied to pattern detectors and strategy evaluators containing multi-timeframe candles and indicators.
/// </summary>
public class TechnicalContext
{
public string Isin { get; init; } = string.Empty;
public string Symbol { get; init; } = string.Empty;
public string Timeframe { get; init; } = "15m";
public DateTime TimestampUtc { get; init; } = DateTime.UtcNow;
public decimal CurrentPrice { get; init; }
public decimal CurrentSpread { get; init; }
public bool IsSpreadVolatile { get; init; }
public decimal CurrentAtr { get; init; }
public MarketRegime Regime { get; init; } = MarketRegime.LowVolatilityRangebound;
/// <summary>
/// Multi-timeframe historical candles (e.g. "1m", "5m", "15m", "1h", "1d").
/// </summary>
public Dictionary<string, IReadOnlyList<CandleDto>> MultiTimeframeCandles { get; init; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Pre-calculated mathematical indicator values for the primary timeframe.
/// </summary>
public Dictionary<string, decimal> Indicators { get; init; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Per-run overrides for a strategy's tunable indicator parameters (e.g. <c>"MeanReversion.RsiOversold"</c>),
/// keyed by <c>"{StrategyKey}.{ParameterName}"</c> so a single context could in principle carry overrides
/// for more than one strategy without name collisions. Always empty for live scanning
/// (<c>TechnicalScoringEngine</c> never populates this - Rules.md §4: no silent behavior change to live
/// trade generation as a side effect of a backtesting feature); populated only by
/// <c>FinlyticSimulation.Engine.HistoricalReplayRunner</c> from <c>BacktestRequestDto.StrategyParameters</c>,
/// so per-asset/per-strategy tuning is opt-in and scoped to backtesting. See <see cref="GetParameter"/>.
/// </summary>
public Dictionary<string, decimal> ParameterOverrides { get; init; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Resolves a tunable strategy parameter: the override in <see cref="ParameterOverrides"/> under
/// <c>"{strategyKey}.{parameterName}"</c> if present, otherwise <paramref name="defaultValue"/> (the
/// strategy's own hardcoded default, unchanged from before parametrization existed).
/// </summary>
public decimal GetParameter(string strategyKey, string parameterName, decimal defaultValue)
{
return ParameterOverrides.TryGetValue($"{strategyKey}.{parameterName}", out var v) ? v : defaultValue;
}
/// <summary>
/// Gets the candles for a specific timeframe (defaults to empty list if not found).
/// </summary>
public IReadOnlyList<CandleDto> GetCandles(string timeframe)
{
if (MultiTimeframeCandles.TryGetValue(timeframe, out var list))
{
return list;
}
return [];
}
/// <summary>
/// Gets the primary timeframe candle sequence.
/// </summary>
public IReadOnlyList<CandleDto> PrimaryCandles => GetCandles(Timeframe);
/// <summary>
/// Gets a specific indicator value or null if not computed.
/// </summary>
public decimal? GetIndicator(string key)
{
if (Indicators.TryGetValue(key, out var val))
{
return val;
}
return null;
}
}