feat(core): add shared DTOs, MqttTopics constants, DatabaseBootstrapper, and ManagedMqttClient extensions
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Individual take-profit tier in a staged scale-out exit plan.
|
||||
/// </summary>
|
||||
public record TakeProfitStage(
|
||||
[property: JsonPropertyName("stageNumber")] int StageNumber,
|
||||
[property: JsonPropertyName("targetPrice")] decimal TargetPrice,
|
||||
[property: JsonPropertyName("percentToClose")] decimal PercentToClose,
|
||||
[property: JsonPropertyName("rMultiple")] decimal RMultiple,
|
||||
[property: JsonPropertyName("description")] string Description
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Break-even trigger rule for locking in free-rolls.
|
||||
/// </summary>
|
||||
public record BreakEvenRule(
|
||||
[property: JsonPropertyName("enabled")] bool Enabled,
|
||||
[property: JsonPropertyName("triggerPrice")] decimal TriggerPrice,
|
||||
[property: JsonPropertyName("offsetToCoverFees")] decimal OffsetToCoverFees
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Trailing stop management rule for trend following.
|
||||
/// </summary>
|
||||
public record TrailingStopRule(
|
||||
[property: JsonPropertyName("type")] TrailingStopType Type,
|
||||
[property: JsonPropertyName("multiplier")] decimal Multiplier,
|
||||
[property: JsonPropertyName("activationPrice")] decimal ActivationPrice,
|
||||
[property: JsonPropertyName("indicatorKey")] string IndicatorKey
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Indicator or structural reversal condition that triggers an early trade exit.
|
||||
/// </summary>
|
||||
public record ReversalCondition(
|
||||
[property: JsonPropertyName("ruleDescription")] string RuleDescription,
|
||||
[property: JsonPropertyName("indicatorTrigger")] string IndicatorTrigger
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Composable, complete exit plan decoupling entry strategy logic from execution management.
|
||||
/// </summary>
|
||||
public record ExitPlan(
|
||||
[property: JsonPropertyName("strategyType")] ExitStrategyType StrategyType,
|
||||
[property: JsonPropertyName("initialStopLoss")] decimal InitialStopLoss,
|
||||
[property: JsonPropertyName("takeProfitStages")] List<TakeProfitStage> TakeProfitStages,
|
||||
[property: JsonPropertyName("breakEvenRule")] BreakEvenRule? BreakEvenRule = null,
|
||||
[property: JsonPropertyName("trailingStopRule")] TrailingStopRule? TrailingStopRule = null,
|
||||
[property: JsonPropertyName("reversalCondition")] ReversalCondition? ReversalCondition = null,
|
||||
[property: JsonPropertyName("maxHoldingBars")] int? MaxHoldingBars = null
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Output result of an isolated pattern detection evaluation.
|
||||
/// </summary>
|
||||
public record PatternResultDto(
|
||||
[property: JsonPropertyName("id")] Guid Id,
|
||||
[property: JsonPropertyName("type")] PatternType Type,
|
||||
[property: JsonPropertyName("category")] PatternCategory Category,
|
||||
[property: JsonPropertyName("bias")] PatternBias Bias,
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("timeframe")] string Timeframe,
|
||||
[property: JsonPropertyName("detectedAt")] DateTime DetectedAt,
|
||||
[property: JsonPropertyName("keyPriceLevel")] decimal KeyPriceLevel,
|
||||
[property: JsonPropertyName("upperBoundary")] decimal UpperBoundary,
|
||||
[property: JsonPropertyName("lowerBoundary")] decimal LowerBoundary,
|
||||
[property: JsonPropertyName("invalidationLevel")] decimal InvalidationLevel,
|
||||
[property: JsonPropertyName("qualityScore")] decimal QualityScore,
|
||||
[property: JsonPropertyName("description")] string Description,
|
||||
[property: JsonPropertyName("extraData")] Dictionary<string, object>? ExtraData = null
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Fully evaluated technical trading setup output from an ITechnicalStrategy.
|
||||
/// </summary>
|
||||
/// <param name="UniverseSource">
|
||||
/// Which FinlyticTechnicals universe-selection mechanism this ISIN was being monitored under at analysis time
|
||||
/// (favorite/discovery/sentiment-spike), or <see langword="null"/> if it was analyzed ad hoc (e.g. a manual
|
||||
/// "Analyze now" call for an ISIN not currently in the scan universe). Carried through unchanged onto
|
||||
/// <c>EngineEvaluationSnapshotEntity</c> so the admin "why no proposals" Web UI can show not just an
|
||||
/// evaluation's scores but why the asset was being watched in the first place.
|
||||
/// </param>
|
||||
/// <param name="UniverseEnteredAtUtc">When the ISIN above entered that scan universe, alongside <paramref name="UniverseSource"/>.</param>
|
||||
/// <param name="Regime">
|
||||
/// The overall market/asset technical regime (<see cref="TechnicalContext.Regime"/>) at analysis time - e.g.
|
||||
/// whether this setup fired during a strong trend or a choppy/rangebound market. Forwarded onto the AI
|
||||
/// validation payload (<c>AiReasoningGateService</c>) so the model has the same regime context a human trader
|
||||
/// would use to judge whether a breakout is likely to follow through.
|
||||
/// </param>
|
||||
public record StrategyResultDto(
|
||||
[property: JsonPropertyName("setupId")] Guid SetupId,
|
||||
[property: JsonPropertyName("isin")] string Isin,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("timeframe")] string Timeframe,
|
||||
[property: JsonPropertyName("strategyKey")] string StrategyKey,
|
||||
[property: JsonPropertyName("strategyName")] string StrategyName,
|
||||
[property: JsonPropertyName("direction")] SignalDirection Direction,
|
||||
[property: JsonPropertyName("qualityScore")] decimal QualityScore,
|
||||
[property: JsonPropertyName("currentPrice")] decimal CurrentPrice,
|
||||
[property: JsonPropertyName("entryPrice")] decimal EntryPrice,
|
||||
[property: JsonPropertyName("invalidationPrice")] decimal InvalidationPrice,
|
||||
[property: JsonPropertyName("currentAtr")] decimal CurrentAtr,
|
||||
[property: JsonPropertyName("estimatedRiskRewardRatio")] decimal EstimatedRiskRewardRatio,
|
||||
[property: JsonPropertyName("exitPlan")] ExitPlan ExitPlan,
|
||||
[property: JsonPropertyName("technicalRationale")] string TechnicalRationale,
|
||||
[property: JsonPropertyName("triggeringPatterns")] List<PatternResultDto> TriggeringPatterns,
|
||||
[property: JsonPropertyName("indicatorSnapshot")] Dictionary<string, decimal> IndicatorSnapshot,
|
||||
[property: JsonPropertyName("createdAt")] DateTime CreatedAt,
|
||||
[property: JsonPropertyName("expiresAt")] DateTime ExpiresAt,
|
||||
[property: JsonPropertyName("isTopPick")] bool IsTopPick = false,
|
||||
[property: JsonPropertyName("rating")] string Rating = "B",
|
||||
[property: JsonPropertyName("universeSource")] UniverseSource? UniverseSource = null,
|
||||
[property: JsonPropertyName("universeEnteredAtUtc")] DateTime? UniverseEnteredAtUtc = null,
|
||||
[property: JsonPropertyName("regime")] MarketRegime? Regime = null
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Major category of a chart pattern.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PatternCategory>))]
|
||||
public enum PatternCategory
|
||||
{
|
||||
Candlestick,
|
||||
Chart,
|
||||
SmartMoney
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directional bias indicated by a pattern or technical setup.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PatternBias>))]
|
||||
public enum PatternBias
|
||||
{
|
||||
Bullish,
|
||||
Bearish,
|
||||
Neutral
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specific pattern type recognized by pattern detection engines.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PatternType>))]
|
||||
public enum PatternType
|
||||
{
|
||||
// Candlestick Patterns
|
||||
Hammer,
|
||||
ShootingStar,
|
||||
BullishEngulfing,
|
||||
BearishEngulfing,
|
||||
MorningStar,
|
||||
EveningStar,
|
||||
Doji,
|
||||
|
||||
// Classical Chart Patterns
|
||||
DoubleBottom,
|
||||
DoubleTop,
|
||||
HeadAndShoulders,
|
||||
InverseHeadAndShoulders,
|
||||
AscendingTriangle,
|
||||
DescendingTriangle,
|
||||
|
||||
// Smart Money Concepts (SMC)
|
||||
FairValueGapBullish,
|
||||
FairValueGapBearish,
|
||||
LiquiditySweepHigh,
|
||||
LiquiditySweepLow,
|
||||
BreakOfStructure,
|
||||
ChangeOfCharacter,
|
||||
OrderBlock
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strategy exit model defining how positions are closed or trailed.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ExitStrategyType>))]
|
||||
public enum ExitStrategyType
|
||||
{
|
||||
StagedScaleOutWithBreakEven,
|
||||
PureTrailingStop,
|
||||
DynamicBandTouch,
|
||||
FixedSingleTarget,
|
||||
IndicatorReversal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of trailing stop mechanic.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<TrailingStopType>))]
|
||||
public enum TrailingStopType
|
||||
{
|
||||
AtrMultiplier,
|
||||
SuperTrendLine,
|
||||
SwingPoints
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction of a technical trading setup signal.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<SignalDirection>))]
|
||||
public enum SignalDirection
|
||||
{
|
||||
Buy,
|
||||
Sell,
|
||||
Neutral
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overall market or asset technical regime.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<MarketRegime>))]
|
||||
public enum MarketRegime
|
||||
{
|
||||
BullishTrending,
|
||||
BearishTrending,
|
||||
HighVolatilityChoppy,
|
||||
LowVolatilityRangebound
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which recurring FinlyticTechnicals selection mechanism added an ISIN to the continuously-scanned universe
|
||||
/// (<c>TechnicalUniverseManager</c> in FinlyticTechnicals). Defined here rather than in FinlyticTechnicals
|
||||
/// because it is carried on <see cref="StrategyResultDto.UniverseSource"/> across the MQTT boundary into
|
||||
/// FinlyticEngine's evaluation snapshot, so more than one service needs it (Rules.md §3).
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<UniverseSource>))]
|
||||
public enum UniverseSource
|
||||
{
|
||||
/// <summary>Promoted temporarily because FinlyticSentiment reported a strong/shifting sentiment reading.</summary>
|
||||
SentimentSpike = 1,
|
||||
|
||||
/// <summary>Favorited by at least one user, aggregated across all users via FinlyticBackend.</summary>
|
||||
UserFavorite = 2,
|
||||
|
||||
/// <summary>Part of FinlyticAssets' curated discovery/watchlist asset set.</summary>
|
||||
Discovery = 3
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// A single entry of FinlyticTechnicals' currently monitored scan universe ("watchlist") - the DB-backed set
|
||||
/// of assets <c>TechnicalScannerBackgroundService</c> actually evaluates every cycle. Exposed to the admin web
|
||||
/// UI so it's possible to verify assets are actually being watched, rather than only inferring it indirectly
|
||||
/// from downstream evaluation results.
|
||||
/// </summary>
|
||||
public record WatchlistEntryDto(
|
||||
string Isin,
|
||||
string? Symbol,
|
||||
string Source,
|
||||
int Priority,
|
||||
DateTime AddedAtUtc,
|
||||
DateTime? ExpiresAtUtc
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Requests the last <paramref name="Limit"/> technical-analysis setups computed for <paramref name="Isin"/>,
|
||||
/// most recent first, regardless of whether they were active/top-pick at the time - i.e. the raw scoring
|
||||
/// history (including setups the engine's opportunity poller would have rejected as too weak), so a caller can
|
||||
/// see whether an asset's quality score is trending up or down across recent scan cycles.
|
||||
/// </summary>
|
||||
public record GetRecentSetupHistoryRequest(string Isin, int Limit = 8);
|
||||
Reference in New Issue
Block a user