Add endpoint for indicator refiner

This commit is contained in:
2025-12-28 20:38:38 +07:00
parent d1924d9030
commit 4f3ec31501
5 changed files with 278 additions and 0 deletions

View File

@@ -1,12 +1,15 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Synth.Models;
using Managing.Domain.Trades;
using Managing.Domain.Users;
@@ -491,4 +494,141 @@ public class TradingService : ITradingService
throw new InvalidOperationException($"Failed to swap GMX tokens: {ex.Message}", ex);
}
}
/// <summary>
/// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
/// </summary>
public async Task<RefineIndicatorsResult> RefineIndicatorsAsync(
Ticker ticker,
Timeframe timeframe,
DateTime startDate,
DateTime endDate,
List<IndicatorRequest> indicators)
{
// Get candles for the specified period
var candles = await _exchangeService.GetCandlesInflux(
TradingExchanges.Evm,
ticker,
startDate,
timeframe,
endDate);
if (candles == null || candles.Count == 0)
{
return new RefineIndicatorsResult
{
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>(),
Signals = new List<LightSignal>()
};
}
// Convert to ordered List to preserve chronological order for indicators
var candlesList = candles.OrderBy(c => c.Date).ToList();
// Map request indicators to domain Scenario
var scenario = MapRefineIndicatorsToScenario(indicators);
// Calculate indicators values
var indicatorsValues = TradingBox.CalculateIndicatorsValues(scenario, candlesList);
// Generate signals for the date range using rolling window approach
var signals = GenerateSignalsForDateRange(candlesList, scenario, indicatorsValues);
return new RefineIndicatorsResult
{
IndicatorsValues = indicatorsValues,
Signals = signals
};
}
/// <summary>
/// Maps IndicatorRequest list to a domain Scenario object.
/// </summary>
private Scenario MapRefineIndicatorsToScenario(List<IndicatorRequest> indicators)
{
var scenario = new Scenario("RefineIndicators", 1);
foreach (var indicatorRequest in indicators)
{
var indicator = new IndicatorBase(indicatorRequest.Name, indicatorRequest.Type)
{
SignalType = indicatorRequest.SignalType,
MinimumHistory = indicatorRequest.MinimumHistory,
Period = indicatorRequest.Period,
FastPeriods = indicatorRequest.FastPeriods,
SlowPeriods = indicatorRequest.SlowPeriods,
SignalPeriods = indicatorRequest.SignalPeriods,
Multiplier = indicatorRequest.Multiplier,
StDev = indicatorRequest.StDev,
SmoothPeriods = indicatorRequest.SmoothPeriods,
StochPeriods = indicatorRequest.StochPeriods,
CyclePeriods = indicatorRequest.CyclePeriods,
KFactor = indicatorRequest.KFactor,
DFactor = indicatorRequest.DFactor,
TenkanPeriods = indicatorRequest.TenkanPeriods,
KijunPeriods = indicatorRequest.KijunPeriods,
SenkouBPeriods = indicatorRequest.SenkouBPeriods,
OffsetPeriods = indicatorRequest.OffsetPeriods,
SenkouOffset = indicatorRequest.SenkouOffset,
ChikouOffset = indicatorRequest.ChikouOffset
};
scenario.AddIndicator(indicator);
}
return scenario;
}
/// <summary>
/// Generates signals for a date range using a rolling window approach.
/// </summary>
private List<LightSignal> GenerateSignalsForDateRange(
List<Candle> candles,
Scenario scenario,
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
{
var allSignals = new List<LightSignal>();
var previousSignals = new Dictionary<string, LightSignal>();
var lightScenario = LightScenario.FromScenario(scenario);
// Use rolling window approach similar to backtests
const int RollingWindowSize = 600;
var rollingWindowCandles = new List<Candle>(RollingWindowSize);
foreach (var candle in candles)
{
// Maintain rolling window
if (rollingWindowCandles.Count >= RollingWindowSize)
{
rollingWindowCandles.RemoveAt(0);
}
rollingWindowCandles.Add(candle);
// Only process if we have enough candles for indicators
if (rollingWindowCandles.Count < 2)
{
continue;
}
// Generate signal for current position
var signal = TradingBox.GetSignal(
rollingWindowCandles,
lightScenario,
previousSignals,
scenario.LookbackPeriod,
preCalculatedIndicatorValues);
if (signal != null)
{
// Check if this is a new signal (not already in our collection)
if (!previousSignals.ContainsKey(signal.Identifier))
{
allSignals.Add(signal);
previousSignals[signal.Identifier] = signal;
}
}
}
return allSignals.OrderBy(s => s.Date).ToList();
}
}