Add synthApi (#27)

* Add synthApi

* Put confidence for Synth proba

* Update the code

* Update readme

* Fix bootstraping

* fix github build

* Update the endpoints for scenario

* Add scenario and update backtest modal

* Update bot modal

* Update interfaces for synth

* add synth to backtest

* Add Kelly criterion and better signal

* Update signal confidence

* update doc

* save leaderboard and prediction

* Update nswag to generate ApiClient in the correct path

* Unify the trading modal

* Save miner and prediction

* Update messaging and block new signal until position not close when flipping off

* Rename strategies to indicators

* Update doc

* Update chart + add signal name

* Fix signal direction

* Update docker webui

* remove crypto npm

* Clean
This commit is contained in:
Oda
2025-07-03 00:13:42 +07:00
committed by GitHub
parent 453806356d
commit a547c4a040
103 changed files with 9916 additions and 810 deletions

View File

@@ -108,15 +108,21 @@ public static class TradingBox
}
}
// Keep only the latest signal per indicator to avoid count mismatch
var latestSignalsPerIndicator = signalOnCandles
.GroupBy(s => s.IndicatorName)
.Select(g => g.OrderByDescending(s => s.Date).First())
.ToHashSet();
// Remove the restrictive requirement that ALL strategies must produce signals
// Instead, let ComputeSignals handle the logic based on what we have
if (!signalOnCandles.Any())
if (!latestSignalsPerIndicator.Any())
{
return null; // No signals from any strategy
}
var data = newCandles.First();
return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
return ComputeSignals(strategies, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
data.Timeframe, config);
}
@@ -136,51 +142,88 @@ public static class TradingBox
}
// Check if all strategies produced signals - this is required for composite signals
if (signalOnCandles.Count != strategies.Count)
var strategyNames = strategies.Select(s => s.Name).ToHashSet();
var signalIndicatorNames = signalOnCandles.Select(s => s.IndicatorName).ToHashSet();
if (!strategyNames.SetEquals(signalIndicatorNames))
{
// Not all strategies produced signals - composite signal requires all strategies to contribute
return null;
}
// Group signals by type for analysis
var signalStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
var trendStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
var contextStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
var trendSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
var contextSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
// Context validation - evaluates market conditions based on confidence levels
if (!ValidateContextStrategies(strategies, contextStrategies, config))
if (!ValidateContextStrategies(strategies, contextSignals, config))
{
return null; // Context strategies are blocking the trade
}
// Trend analysis - evaluate overall market direction
var trendDirection = EvaluateTrendDirection(trendStrategies, config);
// Check for 100% agreement across ALL signals (no threshold voting)
var allDirectionalSignals = signalOnCandles
.Where(s => s.Direction != TradeDirection.None && s.SignalType != SignalType.Context).ToList();
// Signal analysis - evaluate entry signals
var signalDirection = EvaluateSignalDirection(signalStrategies, config);
if (!allDirectionalSignals.Any())
{
return null; // No directional signals available
}
// Determine final direction and confidence
var (finalDirection, confidence) =
DetermineFinalSignal(signalDirection, trendDirection, signalStrategies, trendStrategies, config);
// Require 100% agreement - all signals must have the same direction
var lastSignalDirection = allDirectionalSignals.Last().Direction;
if (!allDirectionalSignals.All(s => s.Direction == lastSignalDirection))
{
return null; // Signals are not in complete agreement
}
if (finalDirection == TradeDirection.None || confidence < config.MinimumConfidence)
var finalDirection = lastSignalDirection;
// Calculate confidence based on the average confidence of all signals
var averageConfidence = CalculateAverageConfidence(allDirectionalSignals);
if (finalDirection == TradeDirection.None || averageConfidence < config.MinimumConfidence)
{
return null; // No valid signal or below minimum confidence
}
// Create composite signal
var lastSignal = signalStrategies.LastOrDefault() ??
trendStrategies.LastOrDefault() ?? contextStrategies.LastOrDefault();
var lastSignal = signals.LastOrDefault() ??
trendSignals.LastOrDefault() ?? contextSignals.LastOrDefault();
return new Signal(
ticker,
finalDirection,
confidence,
averageConfidence,
lastSignal?.Candle,
lastSignal?.Date ?? DateTime.UtcNow,
lastSignal?.Exchange ?? config.DefaultExchange,
IndicatorType.Composite,
SignalType.Signal);
SignalType.Signal, "Aggregated");
}
/// <summary>
/// Calculates the average confidence level from a list of signals
/// </summary>
private static Confidence CalculateAverageConfidence(List<Signal> signals)
{
if (!signals.Any())
{
return Confidence.None;
}
// Convert confidence enum to numeric values for averaging
var confidenceValues = signals.Select(s => (int)s.Confidence).ToList();
var averageValue = confidenceValues.Average();
// Round to nearest confidence level
var roundedValue = Math.Round(averageValue);
// Ensure the value is within valid confidence enum range
roundedValue = Math.Max(0, Math.Min(3, roundedValue));
return (Confidence)(int)roundedValue;
}
/// <summary>