Add endpoint for indicator refiner
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Shared;
|
||||
@@ -340,4 +341,54 @@ public class TradingController : BaseController
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
|
||||
/// </summary>
|
||||
/// <param name="request">The request containing ticker, timeframe, date range, and indicators configuration.</param>
|
||||
/// <returns>A response containing calculated indicator values and generated signals.</returns>
|
||||
[HttpPost("RefineIndicators")]
|
||||
public async Task<ActionResult<RefineIndicatorsResponse>> RefineIndicators(
|
||||
[FromBody] RefineIndicatorsRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate request
|
||||
if (request == null)
|
||||
{
|
||||
return BadRequest("Request cannot be null.");
|
||||
}
|
||||
|
||||
if (request.Indicators == null || request.Indicators.Count == 0)
|
||||
{
|
||||
return BadRequest("At least one indicator must be provided.");
|
||||
}
|
||||
|
||||
if (request.StartDate >= request.EndDate)
|
||||
{
|
||||
return BadRequest("Start date must be before end date.");
|
||||
}
|
||||
|
||||
// Call service - request.Indicators is already List<IndicatorRequest>
|
||||
var result = await _tradingService.RefineIndicatorsAsync(
|
||||
request.Ticker,
|
||||
request.Timeframe,
|
||||
request.StartDate,
|
||||
request.EndDate,
|
||||
request.Indicators);
|
||||
|
||||
// Map service result to API response
|
||||
return Ok(new RefineIndicatorsResponse
|
||||
{
|
||||
IndicatorsValues = result.IndicatorsValues,
|
||||
Signals = result.Signals
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error refining indicators for ticker {Ticker}, timeframe {Timeframe}",
|
||||
request?.Ticker, request?.Timeframe);
|
||||
return StatusCode(500, $"Error refining indicators: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Managing.Api/Models/Requests/RefineIndicatorsRequest.cs
Normal file
42
src/Managing.Api/Models/Requests/RefineIndicatorsRequest.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.Backtests;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for refining indicators and generating signals.
|
||||
/// </summary>
|
||||
public class RefineIndicatorsRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The ticker symbol (e.g., "BTC", "ETH").
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Ticker Ticker { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeframe for the candles (e.g., "FifteenMinutes", "OneHour", "OneDay").
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Timeframe Timeframe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date for the date range (ISO 8601 format).
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end date for the date range (ISO 8601 format).
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of selected indicators with their parameters.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public List<IndicatorRequest> Indicators { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Response model for refined indicators and signals.
|
||||
/// </summary>
|
||||
public class RefineIndicatorsResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of indicator types to their calculated values over time.
|
||||
/// </summary>
|
||||
public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; } =
|
||||
new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Array of signals generated for the date range.
|
||||
/// </summary>
|
||||
public List<LightSignal> Signals { get; set; } = new List<LightSignal>();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Scenarios;
|
||||
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;
|
||||
@@ -57,4 +59,24 @@ public interface ITradingService
|
||||
Task<IEnumerable<Position>> GetPositionByUserIdAsync(int userId);
|
||||
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
|
||||
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
|
||||
/// </summary>
|
||||
Task<RefineIndicatorsResult> RefineIndicatorsAsync(
|
||||
Ticker ticker,
|
||||
Timeframe timeframe,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
List<IndicatorRequest> indicators);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of refining indicators operation.
|
||||
/// </summary>
|
||||
public class RefineIndicatorsResult
|
||||
{
|
||||
public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; } =
|
||||
new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
public List<LightSignal> Signals { get; set; } = new List<LightSignal>();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user