diff --git a/src/Managing.Api/Controllers/TradingController.cs b/src/Managing.Api/Controllers/TradingController.cs
index fc215a96..bd053d18 100644
--- a/src/Managing.Api/Controllers/TradingController.cs
+++ b/src/Managing.Api/Controllers/TradingController.cs
@@ -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
});
}
}
+
+ ///
+ /// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
+ ///
+ /// The request containing ticker, timeframe, date range, and indicators configuration.
+ /// A response containing calculated indicator values and generated signals.
+ [HttpPost("RefineIndicators")]
+ public async Task> 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
+ 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}");
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Managing.Api/Models/Requests/RefineIndicatorsRequest.cs b/src/Managing.Api/Models/Requests/RefineIndicatorsRequest.cs
new file mode 100644
index 00000000..afa62561
--- /dev/null
+++ b/src/Managing.Api/Models/Requests/RefineIndicatorsRequest.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel.DataAnnotations;
+using Managing.Domain.Backtests;
+using static Managing.Common.Enums;
+
+namespace Managing.Api.Models.Requests;
+
+///
+/// Request model for refining indicators and generating signals.
+///
+public class RefineIndicatorsRequest
+{
+ ///
+ /// The ticker symbol (e.g., "BTC", "ETH").
+ ///
+ [Required]
+ public Ticker Ticker { get; set; }
+
+ ///
+ /// The timeframe for the candles (e.g., "FifteenMinutes", "OneHour", "OneDay").
+ ///
+ [Required]
+ public Timeframe Timeframe { get; set; }
+
+ ///
+ /// The start date for the date range (ISO 8601 format).
+ ///
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ ///
+ /// The end date for the date range (ISO 8601 format).
+ ///
+ [Required]
+ public DateTime EndDate { get; set; }
+
+ ///
+ /// Array of selected indicators with their parameters.
+ ///
+ [Required]
+ public List Indicators { get; set; } = new();
+}
+
diff --git a/src/Managing.Api/Models/Responses/RefineIndicatorsResponse.cs b/src/Managing.Api/Models/Responses/RefineIndicatorsResponse.cs
new file mode 100644
index 00000000..d20b568a
--- /dev/null
+++ b/src/Managing.Api/Models/Responses/RefineIndicatorsResponse.cs
@@ -0,0 +1,23 @@
+using Managing.Domain.Indicators;
+using Managing.Domain.Strategies.Base;
+using static Managing.Common.Enums;
+
+namespace Managing.Api.Models.Responses;
+
+///
+/// Response model for refined indicators and signals.
+///
+public class RefineIndicatorsResponse
+{
+ ///
+ /// Dictionary of indicator types to their calculated values over time.
+ ///
+ public Dictionary IndicatorsValues { get; set; } =
+ new Dictionary();
+
+ ///
+ /// Array of signals generated for the date range.
+ ///
+ public List Signals { get; set; } = new List();
+}
+
diff --git a/src/Managing.Application.Abstractions/Services/ITradingService.cs b/src/Managing.Application.Abstractions/Services/ITradingService.cs
index 17fd79e9..2cc3ccd1 100644
--- a/src/Managing.Application.Abstractions/Services/ITradingService.cs
+++ b/src/Managing.Application.Abstractions/Services/ITradingService.cs
@@ -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> GetPositionByUserIdAsync(int userId);
Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
+
+ ///
+ /// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
+ ///
+ Task RefineIndicatorsAsync(
+ Ticker ticker,
+ Timeframe timeframe,
+ DateTime startDate,
+ DateTime endDate,
+ List indicators);
+}
+
+///
+/// Result of refining indicators operation.
+///
+public class RefineIndicatorsResult
+{
+ public Dictionary IndicatorsValues { get; set; } =
+ new Dictionary();
+ public List Signals { get; set; } = new List();
}
\ No newline at end of file
diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs
index ef529744..fdf1d49e 100644
--- a/src/Managing.Application/Trading/TradingService.cs
+++ b/src/Managing.Application/Trading/TradingService.cs
@@ -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);
}
}
+
+ ///
+ /// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.
+ ///
+ public async Task RefineIndicatorsAsync(
+ Ticker ticker,
+ Timeframe timeframe,
+ DateTime startDate,
+ DateTime endDate,
+ List 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(),
+ Signals = new List()
+ };
+ }
+
+ // 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
+ };
+ }
+
+ ///
+ /// Maps IndicatorRequest list to a domain Scenario object.
+ ///
+ private Scenario MapRefineIndicatorsToScenario(List 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;
+ }
+
+ ///
+ /// Generates signals for a date range using a rolling window approach.
+ ///
+ private List GenerateSignalsForDateRange(
+ List candles,
+ Scenario scenario,
+ Dictionary preCalculatedIndicatorValues)
+ {
+ var allSignals = new List();
+ var previousSignals = new Dictionary();
+ var lightScenario = LightScenario.FromScenario(scenario);
+
+ // Use rolling window approach similar to backtests
+ const int RollingWindowSize = 600;
+ var rollingWindowCandles = new List(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();
+ }
}
\ No newline at end of file