Improve perf for backtests
This commit is contained in:
@@ -346,7 +346,7 @@ public class DataController : ControllerBase
|
|||||||
{
|
{
|
||||||
// Map ScenarioRequest to domain Scenario object
|
// Map ScenarioRequest to domain Scenario object
|
||||||
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
|
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
|
||||||
indicatorsValues = _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
|
indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new CandlesWithIndicatorsResponse
|
return Ok(new CandlesWithIndicatorsResponse
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public interface ITradingService
|
|||||||
/// <param name="scenario">The scenario containing indicators.</param>
|
/// <param name="scenario">The scenario containing indicators.</param>
|
||||||
/// <param name="candles">The candles to calculate indicators for.</param>
|
/// <param name="candles">The candles to calculate indicators for.</param>
|
||||||
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
|
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
|
||||||
Scenario scenario,
|
Scenario scenario,
|
||||||
HashSet<Candle> candles);
|
HashSet<Candle> candles);
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ using Managing.Application.Abstractions.Repositories;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Bots;
|
using Managing.Application.Bots;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Backtests;
|
namespace Managing.Application.Backtests;
|
||||||
|
|
||||||
@@ -76,7 +79,7 @@ public class BacktestExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a fresh TradingBotBase instance for this backtest
|
// Create a fresh TradingBotBase instance for this backtest
|
||||||
var tradingBot = await CreateTradingBotInstance(config);
|
var tradingBot = CreateTradingBotInstance(config);
|
||||||
tradingBot.Account = user.Accounts.First();
|
tradingBot.Account = user.Accounts.First();
|
||||||
|
|
||||||
var totalCandles = candles.Count;
|
var totalCandles = candles.Count;
|
||||||
@@ -86,6 +89,41 @@ public class BacktestExecutor
|
|||||||
_logger.LogInformation("Backtest requested by {UserId} with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
_logger.LogInformation("Backtest requested by {UserId} with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
||||||
user.Id, totalCandles, config.Ticker, config.Timeframe);
|
user.Id, totalCandles, config.Ticker, config.Timeframe);
|
||||||
|
|
||||||
|
// Pre-calculate indicator values once for all candles to optimize performance
|
||||||
|
// This avoids recalculating indicators for every candle iteration
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues = null;
|
||||||
|
if (config.Scenario != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Pre-calculating indicator values for {IndicatorCount} indicators",
|
||||||
|
config.Scenario.Indicators?.Count ?? 0);
|
||||||
|
|
||||||
|
// Convert LightScenario to Scenario for CalculateIndicatorsValuesAsync
|
||||||
|
var scenario = config.Scenario.ToScenario();
|
||||||
|
|
||||||
|
// Calculate all indicator values once with all candles
|
||||||
|
preCalculatedIndicatorValues = await ServiceScopeHelpers.WithScopedService<ITradingService, Dictionary<IndicatorType, IndicatorsResultBase>>(
|
||||||
|
_scopeFactory,
|
||||||
|
async tradingService =>
|
||||||
|
{
|
||||||
|
return await tradingService.CalculateIndicatorsValuesAsync(scenario, candles);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store pre-calculated values in trading bot for use during signal generation
|
||||||
|
tradingBot.PreCalculatedIndicatorValues = preCalculatedIndicatorValues;
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully pre-calculated indicator values for {IndicatorCount} indicator types",
|
||||||
|
preCalculatedIndicatorValues?.Count ?? 0);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to pre-calculate indicator values, will calculate on-the-fly. Error: {ErrorMessage}", ex.Message);
|
||||||
|
// Continue with normal calculation if pre-calculation fails
|
||||||
|
preCalculatedIndicatorValues = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize wallet balance with first candle
|
// Initialize wallet balance with first candle
|
||||||
tradingBot.WalletBalances.Clear();
|
tradingBot.WalletBalances.Clear();
|
||||||
tradingBot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
|
tradingBot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
|
||||||
@@ -239,7 +277,7 @@ public class BacktestExecutor
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a TradingBotBase instance for backtesting
|
/// Creates a TradingBotBase instance for backtesting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<TradingBotBase> CreateTradingBotInstance(TradingBotConfig config)
|
private TradingBotBase CreateTradingBotInstance(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Validate configuration for backtesting
|
// Validate configuration for backtesting
|
||||||
if (config == null)
|
if (config == null)
|
||||||
|
|||||||
@@ -139,41 +139,6 @@ namespace Managing.Application.Backtests
|
|||||||
return await RunTradingBotBacktest(config, startDate, endDate, user, false, withCandles, requestId, metadata);
|
return await RunTradingBotBacktest(config, startDate, endDate, user, false, withCandles, requestId, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed RunBacktestWithCandles - backtests now run via compute workers
|
|
||||||
// This method is kept for backward compatibility but should not be called directly
|
|
||||||
|
|
||||||
private async Task<HashSet<Candle>> GetCandles(Ticker ticker, Timeframe timeframe,
|
|
||||||
DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
var candles = await _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
|
||||||
startDate, timeframe, endDate);
|
|
||||||
|
|
||||||
if (candles == null || candles.Count == 0)
|
|
||||||
throw new Exception(
|
|
||||||
$"No candles for {ticker} on {timeframe} timeframe for start {startDate} to end {endDate}");
|
|
||||||
|
|
||||||
return candles;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Removed CreateCleanConfigForOrleans - no longer needed with job queue approach
|
|
||||||
|
|
||||||
private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (backtest.Score > 60)
|
|
||||||
{
|
|
||||||
await _messengerService.SendBacktestNotification(backtest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to send backtest notification for backtest {Id}", backtest.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> DeleteBacktestAsync(string id)
|
public async Task<bool> DeleteBacktestAsync(string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -243,7 +208,6 @@ namespace Managing.Application.Backtests
|
|||||||
return (backtests, totalCount);
|
return (backtests, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
|
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
|
||||||
{
|
{
|
||||||
var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);
|
var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);
|
||||||
@@ -605,7 +569,5 @@ namespace Managing.Application.Backtests
|
|||||||
if (string.IsNullOrWhiteSpace(requestId) || response == null) return;
|
if (string.IsNullOrWhiteSpace(requestId) || response == null) return;
|
||||||
await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", response);
|
await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed TriggerBundleBacktestGrain methods - bundle backtests now use job queue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ using Managing.Domain.Indicators;
|
|||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Synth.Models;
|
using Managing.Domain.Synth.Models;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -28,7 +29,9 @@ public class TradingBotBase : ITradingBot
|
|||||||
public readonly ILogger<TradingBotBase> Logger;
|
public readonly ILogger<TradingBotBase> Logger;
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
private const int NEW_POSITION_GRACE_SECONDS = 45; // grace window before evaluating missing orders
|
private const int NEW_POSITION_GRACE_SECONDS = 45; // grace window before evaluating missing orders
|
||||||
private const int CLOSE_POSITION_GRACE_MS = 20000; // grace window before closing position to allow broker processing (20 seconds)
|
|
||||||
|
private const int
|
||||||
|
CLOSE_POSITION_GRACE_MS = 20000; // grace window before closing position to allow broker processing (20 seconds)
|
||||||
|
|
||||||
public TradingBotConfig Config { get; set; }
|
public TradingBotConfig Config { get; set; }
|
||||||
public Account Account { get; set; }
|
public Account Account { get; set; }
|
||||||
@@ -42,6 +45,12 @@ public class TradingBotBase : ITradingBot
|
|||||||
public Candle LastCandle { get; set; }
|
public Candle LastCandle { get; set; }
|
||||||
public DateTime? LastPositionClosingTime { get; set; }
|
public DateTime? LastPositionClosingTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-calculated indicator values for backtesting optimization.
|
||||||
|
/// Key is IndicatorType, Value is the calculated indicator result.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<IndicatorType, IndicatorsResultBase> PreCalculatedIndicatorValues { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public TradingBotBase(
|
public TradingBotBase(
|
||||||
ILogger<TradingBotBase> logger,
|
ILogger<TradingBotBase> logger,
|
||||||
@@ -56,6 +65,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
Positions = new Dictionary<Guid, Position>();
|
Positions = new Dictionary<Guid, Position>();
|
||||||
WalletBalances = new Dictionary<DateTime, decimal>();
|
WalletBalances = new Dictionary<DateTime, decimal>();
|
||||||
PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe);
|
PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe);
|
||||||
|
PreCalculatedIndicatorValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start(BotStatus previousStatus)
|
public async Task Start(BotStatus previousStatus)
|
||||||
@@ -269,7 +279,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (Config.IsForBacktest && candles != null)
|
if (Config.IsForBacktest && candles != null)
|
||||||
{
|
{
|
||||||
var backtestSignal =
|
var backtestSignal =
|
||||||
TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod);
|
TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod,
|
||||||
|
PreCalculatedIndicatorValues);
|
||||||
if (backtestSignal == null) return;
|
if (backtestSignal == null) return;
|
||||||
await AddSignal(backtestSignal);
|
await AddSignal(backtestSignal);
|
||||||
}
|
}
|
||||||
@@ -768,7 +779,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"⏰ Time Limit Close\nClosing position due to time limit: `{Config.MaxPositionTimeHours}h` exceeded\n📈 Position Status: {profitStatus}\n💰 Entry: `${positionForSignal.Open.Price}` → Current: `${lastCandle.Close}`\n📊 Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)");
|
$"⏰ Time Limit Close\nClosing position due to time limit: `{Config.MaxPositionTimeHours}h` exceeded\n📈 Position Status: {profitStatus}\n💰 Entry: `${positionForSignal.Open.Price}` → Current: `${lastCandle.Close}`\n📊 Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)");
|
||||||
// Force a market close: compute PnL based on current price instead of SL/TP
|
// Force a market close: compute PnL based on current price instead of SL/TP
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true, true);
|
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true,
|
||||||
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1355,7 +1367,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||||
}
|
}
|
||||||
|
|
||||||
await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
|
await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null,
|
||||||
|
forceMarketClose);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1371,13 +1384,15 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Trade close on exchange => Should close trade manually
|
// Trade close on exchange => Should close trade manually
|
||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||||
// Ensure trade dates are properly updated even for canceled/rejected positions
|
// Ensure trade dates are properly updated even for canceled/rejected positions
|
||||||
await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
|
await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null,
|
||||||
|
forceMarketClose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleClosedPosition(Position position, decimal? forcedClosingPrice = null, bool forceMarketClose = false)
|
private async Task HandleClosedPosition(Position position, decimal? forcedClosingPrice = null,
|
||||||
|
bool forceMarketClose = false)
|
||||||
{
|
{
|
||||||
if (Positions.ContainsKey(position.Identifier))
|
if (Positions.ContainsKey(position.Identifier))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -435,9 +435,12 @@ public class TradingService : ITradingService
|
|||||||
/// <param name="scenario">The scenario containing indicators.</param>
|
/// <param name="scenario">The scenario containing indicators.</param>
|
||||||
/// <param name="candles">The candles to calculate indicators for.</param>
|
/// <param name="candles">The candles to calculate indicators for.</param>
|
||||||
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
||||||
public Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
|
public async Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
|
||||||
Scenario scenario,
|
Scenario scenario,
|
||||||
HashSet<Candle> candles)
|
HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
// Offload CPU-bound indicator calculations to thread pool
|
||||||
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||||
|
|
||||||
@@ -462,6 +465,7 @@ public class TradingService : ITradingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return indicatorsValues;
|
return indicatorsValues;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user)
|
public async Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user)
|
||||||
|
|||||||
@@ -28,11 +28,71 @@ public class StDevContext : IndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stDev = candles.GetStdDev(Period.Value).ToList();
|
var stDev = candles.GetStdDev(Period.Value).ToList();
|
||||||
var stDevCandles = MapStDev(stDev, candles.TakeLast(Period.Value));
|
|
||||||
|
|
||||||
if (stDev.Count == 0)
|
if (stDev.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
ProcessStDevSignals(stDev, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated StdDev values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated StdDev values if available
|
||||||
|
List<StdDevResult> stDev = null;
|
||||||
|
if (preCalculatedValues?.StdDev != null && preCalculatedValues.StdDev.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated StdDev values to match the candles we're processing
|
||||||
|
stDev = preCalculatedValues.StdDev
|
||||||
|
.Where(s => candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (stDev == null || !stDev.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStDevSignals(stDev, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes StdDev context signals based on Z-score volatility analysis.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stDev">List of StdDev calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessStDevSignals(List<StdDevResult> stDev, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var stDevCandles = MapStDev(stDev, candles.TakeLast(Period.Value));
|
||||||
|
|
||||||
|
if (stDevCandles.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var lastCandle = stDevCandles.Last();
|
var lastCandle = stDevCandles.Last();
|
||||||
var zScore = lastCandle.ZScore ?? 0;
|
var zScore = lastCandle.ZScore ?? 0;
|
||||||
|
|
||||||
@@ -65,13 +125,6 @@ public class StDevContext : IndicatorBase
|
|||||||
// Context strategies always return TradeDirection.None
|
// Context strategies always return TradeDirection.None
|
||||||
// The confidence level indicates the quality of market conditions
|
// The confidence level indicates the quality of market conditions
|
||||||
AddSignal(lastCandle, TradeDirection.None, confidence);
|
AddSignal(lastCandle, TradeDirection.None, confidence);
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ namespace Managing.Domain.Strategies
|
|||||||
int? CyclePeriods { get; set; }
|
int? CyclePeriods { get; set; }
|
||||||
|
|
||||||
List<LightSignal> Run(HashSet<Candle> candles);
|
List<LightSignal> Run(HashSet<Candle> candles);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated indicator values for performance optimization.
|
||||||
|
/// If pre-calculated values are not available or not applicable, falls back to regular Run().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candles">The candles to process</param>
|
||||||
|
/// <param name="preCalculatedValues">Pre-calculated indicator values (optional)</param>
|
||||||
|
/// <returns>List of signals generated by the indicator</returns>
|
||||||
|
List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues);
|
||||||
|
|
||||||
IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles);
|
IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,17 @@ namespace Managing.Domain.Strategies
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated values if available, otherwise falls back to regular Run().
|
||||||
|
/// Default implementation falls back to regular Run() - override in derived classes to use pre-calculated values.
|
||||||
|
/// </summary>
|
||||||
|
public virtual List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
// Default implementation: ignore pre-calculated values and use regular Run()
|
||||||
|
// Derived classes should override this to use pre-calculated values for performance
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public virtual IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GetSignals(ChandelierType.Long, candles);
|
ProcessChandelierSignals(candles);
|
||||||
GetSignals(ChandelierType.Short, candles);
|
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
}
|
}
|
||||||
@@ -41,6 +40,77 @@ public class ChandelierExitIndicatorBase : IndicatorBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated Chandelier values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= MinimumHistory)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated Chandelier values if available
|
||||||
|
List<ChandelierResult> chandelierLong = null;
|
||||||
|
List<ChandelierResult> chandelierShort = null;
|
||||||
|
if (preCalculatedValues?.ChandelierLong != null && preCalculatedValues.ChandelierLong.Any() &&
|
||||||
|
preCalculatedValues?.ChandelierShort != null && preCalculatedValues.ChandelierShort.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated Chandelier values to match the candles we're processing
|
||||||
|
chandelierLong = preCalculatedValues.ChandelierLong
|
||||||
|
.Where(c => c.ChandelierExit.HasValue && candles.Any(candle => candle.Date == c.Date))
|
||||||
|
.OrderBy(c => c.Date)
|
||||||
|
.ToList();
|
||||||
|
chandelierShort = preCalculatedValues.ChandelierShort
|
||||||
|
.Where(c => c.ChandelierExit.HasValue && candles.Any(candle => candle.Date == c.Date))
|
||||||
|
.OrderBy(c => c.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (chandelierLong == null || !chandelierLong.Any() || chandelierShort == null || !chandelierShort.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessChandelierSignalsWithPreCalculated(chandelierLong, chandelierShort, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes Chandelier signals for both Long and Short types.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessChandelierSignals(HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
GetSignals(ChandelierType.Long, candles);
|
||||||
|
GetSignals(ChandelierType.Short, candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes Chandelier signals using pre-calculated values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chandelierLong">Pre-calculated Long Chandelier values</param>
|
||||||
|
/// <param name="chandelierShort">Pre-calculated Short Chandelier values</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessChandelierSignalsWithPreCalculated(
|
||||||
|
List<ChandelierResult> chandelierLong,
|
||||||
|
List<ChandelierResult> chandelierShort,
|
||||||
|
HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
GetSignalsWithPreCalculated(ChandelierType.Long, chandelierLong, candles);
|
||||||
|
GetSignalsWithPreCalculated(ChandelierType.Short, chandelierShort, candles);
|
||||||
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
return new IndicatorsResultBase()
|
return new IndicatorsResultBase()
|
||||||
@@ -54,9 +124,28 @@ public class ChandelierExitIndicatorBase : IndicatorBase
|
|||||||
{
|
{
|
||||||
var chandelier = candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType)
|
var chandelier = candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType)
|
||||||
.Where(s => s.ChandelierExit.HasValue).ToList();
|
.Where(s => s.ChandelierExit.HasValue).ToList();
|
||||||
var chandelierCandle = MapChandelierToCandle(chandelier, candles.TakeLast(MinimumHistory));
|
ProcessChandelierSignalsForType(chandelier, chandelierType, candles);
|
||||||
var previousCandle = chandelierCandle[0];
|
}
|
||||||
|
|
||||||
|
private void GetSignalsWithPreCalculated(ChandelierType chandelierType, List<ChandelierResult> chandelier, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
ProcessChandelierSignalsForType(chandelier, chandelierType, candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes Chandelier signals for a specific type (Long or Short).
|
||||||
|
/// This method is shared between regular and optimized signal processing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chandelier">Chandelier calculation results</param>
|
||||||
|
/// <param name="chandelierType">Type of Chandelier (Long or Short)</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessChandelierSignalsForType(List<ChandelierResult> chandelier, ChandelierType chandelierType, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var chandelierCandle = MapChandelierToCandle(chandelier, candles.TakeLast(MinimumHistory));
|
||||||
|
if (chandelierCandle.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var previousCandle = chandelierCandle[0];
|
||||||
foreach (var currentCandle in chandelierCandle.Skip(1))
|
foreach (var currentCandle in chandelierCandle.Skip(1))
|
||||||
{
|
{
|
||||||
// Short
|
// Short
|
||||||
|
|||||||
@@ -42,10 +42,77 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
|
|||||||
var fastEma = candles.GetEma(FastPeriods.Value).ToList();
|
var fastEma = candles.GetEma(FastPeriods.Value).ToList();
|
||||||
var slowEma = candles.GetEma(SlowPeriods.Value).ToList();
|
var slowEma = candles.GetEma(SlowPeriods.Value).ToList();
|
||||||
|
|
||||||
|
if (fastEma.Count == 0 || slowEma.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ProcessDualEmaCrossSignals(fastEma, slowEma, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated EMA values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= MinimumHistory)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated EMA values if available
|
||||||
|
List<EmaResult> fastEma = null;
|
||||||
|
List<EmaResult> slowEma = null;
|
||||||
|
if (preCalculatedValues?.FastEma != null && preCalculatedValues.FastEma.Any() &&
|
||||||
|
preCalculatedValues?.SlowEma != null && preCalculatedValues.SlowEma.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated EMA values to match the candles we're processing
|
||||||
|
fastEma = preCalculatedValues.FastEma
|
||||||
|
.Where(e => candles.Any(c => c.Date == e.Date))
|
||||||
|
.OrderBy(e => e.Date)
|
||||||
|
.ToList();
|
||||||
|
slowEma = preCalculatedValues.SlowEma
|
||||||
|
.Where(e => candles.Any(c => c.Date == e.Date))
|
||||||
|
.OrderBy(e => e.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (fastEma == null || !fastEma.Any() || slowEma == null || !slowEma.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessDualEmaCrossSignals(fastEma, slowEma, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes dual EMA cross signals based on Fast EMA crossing Slow EMA.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fastEma">List of Fast EMA calculation results</param>
|
||||||
|
/// <param name="slowEma">List of Slow EMA calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessDualEmaCrossSignals(List<EmaResult> fastEma, List<EmaResult> slowEma, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
var dualEmaCandles = MapDualEmaToCandle(fastEma, slowEma, candles.TakeLast(MinimumHistory));
|
var dualEmaCandles = MapDualEmaToCandle(fastEma, slowEma, candles.TakeLast(MinimumHistory));
|
||||||
|
|
||||||
if (dualEmaCandles.Count < 2)
|
if (dualEmaCandles.Count < 2)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
var previousCandle = dualEmaCandles[0];
|
var previousCandle = dualEmaCandles[0];
|
||||||
foreach (var currentCandle in dualEmaCandles.Skip(1))
|
foreach (var currentCandle in dualEmaCandles.Skip(1))
|
||||||
@@ -66,13 +133,6 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CandleDualEma> MapDualEmaToCandle(List<EmaResult> fastEma, List<EmaResult> slowEma,
|
private List<CandleDualEma> MapDualEmaToCandle(List<EmaResult> fastEma, List<EmaResult> slowEma,
|
||||||
|
|||||||
@@ -36,11 +36,71 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ema = candles.GetEma(Period.Value).ToList();
|
var ema = candles.GetEma(Period.Value).ToList();
|
||||||
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
|
|
||||||
|
|
||||||
if (ema.Count == 0)
|
if (ema.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
ProcessEmaCrossSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated EMA values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated EMA values if available
|
||||||
|
List<EmaResult> ema = null;
|
||||||
|
if (preCalculatedValues?.Ema != null && preCalculatedValues.Ema.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated EMA values to match the candles we're processing
|
||||||
|
ema = preCalculatedValues.Ema
|
||||||
|
.Where(e => candles.Any(c => c.Date == e.Date))
|
||||||
|
.OrderBy(e => e.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (ema == null || !ema.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessEmaCrossSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes EMA cross signals based on price crossing the EMA line.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ema">List of EMA calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessEmaCrossSignals(List<EmaResult> ema, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
|
||||||
|
|
||||||
|
if (emaCandles.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var previousCandle = emaCandles[0];
|
var previousCandle = emaCandles[0];
|
||||||
foreach (var currentCandle in emaCandles.Skip(1))
|
foreach (var currentCandle in emaCandles.Skip(1))
|
||||||
{
|
{
|
||||||
@@ -58,13 +118,6 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
||||||
|
|||||||
@@ -36,11 +36,71 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ema = candles.GetEma(Period.Value).ToList();
|
var ema = candles.GetEma(Period.Value).ToList();
|
||||||
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value).ToHashSet());
|
|
||||||
|
|
||||||
if (ema.Count == 0)
|
if (ema.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
ProcessEmaCrossSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated EMA values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated EMA values if available
|
||||||
|
List<EmaResult> ema = null;
|
||||||
|
if (preCalculatedValues?.Ema != null && preCalculatedValues.Ema.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated EMA values to match the candles we're processing
|
||||||
|
ema = preCalculatedValues.Ema
|
||||||
|
.Where(e => candles.Any(c => c.Date == e.Date))
|
||||||
|
.OrderBy(e => e.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (ema == null || !ema.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessEmaCrossSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes EMA cross signals based on price crossing the EMA line.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ema">List of EMA calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessEmaCrossSignals(List<EmaResult> ema, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value).ToHashSet());
|
||||||
|
|
||||||
|
if (emaCandles.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var previousCandle = emaCandles[0];
|
var previousCandle = emaCandles[0];
|
||||||
foreach (var currentCandle in emaCandles.Skip(1))
|
foreach (var currentCandle in emaCandles.Skip(1))
|
||||||
{
|
{
|
||||||
@@ -58,13 +118,6 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
||||||
|
|||||||
@@ -38,10 +38,70 @@ public class LaggingSTC : IndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
||||||
|
if (stc.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ProcessLaggingStcSignals(stc, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated STC values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated STC values if available
|
||||||
|
List<StcResult> stc = null;
|
||||||
|
if (preCalculatedValues?.Stc != null && preCalculatedValues.Stc.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated STC values to match the candles we're processing
|
||||||
|
stc = preCalculatedValues.Stc
|
||||||
|
.Where(s => candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (stc == null || !stc.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessLaggingStcSignals(stc, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes Lagging STC signals based on STC values crossing thresholds with volatility confirmation.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stc">List of STC calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessLaggingStcSignals(List<StcResult> stc, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value * 3));
|
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value * 3));
|
||||||
|
|
||||||
if (stcCandles.Count == 0)
|
if (stcCandles.Count == 0)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
for (int i = 1; i < stcCandles.Count; i++)
|
for (int i = 1; i < stcCandles.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -81,13 +141,6 @@ public class LaggingSTC : IndicatorBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -31,20 +31,74 @@ public class MacdCrossIndicatorBase : IndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList();
|
var macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList();
|
||||||
var macdCandle = MapMacdToCandle(macd, candles.TakeLast(SignalPeriods.Value));
|
|
||||||
|
|
||||||
if (macd.Count == 0)
|
if (macd.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
ProcessMacdSignals(macd, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated MACD values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 2 * (SlowPeriods + SignalPeriods))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated MACD values if available
|
||||||
|
List<MacdResult> macd = null;
|
||||||
|
if (preCalculatedValues?.Macd != null && preCalculatedValues.Macd.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated MACD values to match the candles we're processing
|
||||||
|
macd = preCalculatedValues.Macd
|
||||||
|
.Where(m => candles.Any(c => c.Date == m.Date))
|
||||||
|
.OrderBy(m => m.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (macd == null || !macd.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessMacdSignals(macd, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes MACD signals based on MACD line crossing the Signal line.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="macd">List of MACD calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessMacdSignals(List<MacdResult> macd, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var macdCandle = MapMacdToCandle(macd, candles.TakeLast(SignalPeriods.Value));
|
||||||
|
|
||||||
|
if (macdCandle.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var previousCandle = macdCandle[0];
|
var previousCandle = macdCandle[0];
|
||||||
foreach (var currentCandle in macdCandle.Skip(1))
|
foreach (var currentCandle in macdCandle.Skip(1))
|
||||||
{
|
{
|
||||||
// // Only trigger signals when Signal line is outside -100 to 100 range (extreme conditions)
|
|
||||||
// if (currentCandle.Signal < -200 || currentCandle.Signal > 200)
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check for MACD line crossing below Signal line (bearish cross)
|
// Check for MACD line crossing below Signal line (bearish cross)
|
||||||
if (previousCandle.Macd > previousCandle.Signal && currentCandle.Macd < currentCandle.Signal)
|
if (previousCandle.Macd > previousCandle.Signal && currentCandle.Macd < currentCandle.Signal)
|
||||||
{
|
{
|
||||||
@@ -59,13 +113,6 @@ public class MacdCrossIndicatorBase : IndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -29,18 +29,13 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticker = candles.First().Ticker;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
|
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
|
||||||
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
|
if (rsiResult.Count == 0)
|
||||||
|
|
||||||
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
GetLongSignals(candlesRsi, candles);
|
ProcessRsiDivergenceConfirmSignals(rsiResult, candles);
|
||||||
GetShortSignals(candlesRsi, candles);
|
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
}
|
}
|
||||||
@@ -50,6 +45,63 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated RSI values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated RSI values if available
|
||||||
|
List<RsiResult> rsiResult = null;
|
||||||
|
if (preCalculatedValues?.Rsi != null && preCalculatedValues.Rsi.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated RSI values to match the candles we're processing
|
||||||
|
var relevantCandles = candles.TakeLast(10 * Period.Value);
|
||||||
|
rsiResult = preCalculatedValues.Rsi
|
||||||
|
.Where(r => relevantCandles.Any(c => c.Date == r.Date))
|
||||||
|
.OrderBy(r => r.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (rsiResult == null || !rsiResult.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessRsiDivergenceConfirmSignals(rsiResult, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes RSI divergence confirmation signals based on price and RSI divergence patterns.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rsiResult">List of RSI calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessRsiDivergenceConfirmSignals(List<RsiResult> rsiResult, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
|
||||||
|
|
||||||
|
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GetLongSignals(candlesRsi, candles);
|
||||||
|
GetShortSignals(candlesRsi, candles);
|
||||||
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
return new IndicatorsResultBase()
|
return new IndicatorsResultBase()
|
||||||
|
|||||||
@@ -32,18 +32,13 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticker = candles.First().Ticker;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
|
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
|
||||||
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
|
if (rsiResult.Count == 0)
|
||||||
|
|
||||||
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
GetLongSignals(candlesRsi, candles);
|
ProcessRsiDivergenceSignals(rsiResult, candles);
|
||||||
GetShortSignals(candlesRsi, candles);
|
|
||||||
|
|
||||||
return Signals;
|
return Signals;
|
||||||
}
|
}
|
||||||
@@ -53,6 +48,63 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated RSI values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (!Period.HasValue || candles.Count <= Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated RSI values if available
|
||||||
|
List<RsiResult> rsiResult = null;
|
||||||
|
if (preCalculatedValues?.Rsi != null && preCalculatedValues.Rsi.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated RSI values to match the candles we're processing
|
||||||
|
var relevantCandles = candles.TakeLast(10 * Period.Value);
|
||||||
|
rsiResult = preCalculatedValues.Rsi
|
||||||
|
.Where(r => relevantCandles.Any(c => c.Date == r.Date))
|
||||||
|
.OrderBy(r => r.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (rsiResult == null || !rsiResult.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessRsiDivergenceSignals(rsiResult, candles);
|
||||||
|
|
||||||
|
return Signals;
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes RSI divergence signals based on price and RSI divergence patterns.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rsiResult">List of RSI calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessRsiDivergenceSignals(List<RsiResult> rsiResult, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
|
||||||
|
|
||||||
|
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GetLongSignals(candlesRsi, candles);
|
||||||
|
GetShortSignals(candlesRsi, candles);
|
||||||
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
return new IndicatorsResultBase()
|
return new IndicatorsResultBase()
|
||||||
|
|||||||
@@ -33,31 +33,51 @@ public class StcIndicatorBase : IndicatorBase
|
|||||||
if (FastPeriods != null)
|
if (FastPeriods != null)
|
||||||
{
|
{
|
||||||
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
||||||
if (CyclePeriods != null)
|
|
||||||
{
|
|
||||||
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value));
|
|
||||||
|
|
||||||
if (stc.Count == 0)
|
if (stc.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var previousCandle = stcCandles[0];
|
ProcessStcSignals(stc, candles);
|
||||||
foreach (var currentCandle in stcCandles.Skip(1))
|
|
||||||
{
|
|
||||||
if (previousCandle.Stc > 75 && currentCandle.Stc <= 75)
|
|
||||||
{
|
|
||||||
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousCandle.Stc < 25 && currentCandle.Stc >= 25)
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
{
|
{
|
||||||
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousCandle = currentCandle;
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated STC values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated STC values if available
|
||||||
|
List<StcResult> stc = null;
|
||||||
|
if (preCalculatedValues?.Stc != null && preCalculatedValues.Stc.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated STC values to match the candles we're processing
|
||||||
|
stc = preCalculatedValues.Stc
|
||||||
|
.Where(s => candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (stc == null || !stc.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessStcSignals(stc, candles);
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
}
|
}
|
||||||
catch (RuleException)
|
catch (RuleException)
|
||||||
@@ -80,6 +100,39 @@ public class StcIndicatorBase : IndicatorBase
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes STC signals based on STC values crossing thresholds (25 and 75).
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stc">List of STC calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessStcSignals(List<StcResult> stc, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
if (CyclePeriods == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value));
|
||||||
|
|
||||||
|
if (stcCandles.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var previousCandle = stcCandles[0];
|
||||||
|
foreach (var currentCandle in stcCandles.Skip(1))
|
||||||
|
{
|
||||||
|
if (previousCandle.Stc > 75 && currentCandle.Stc <= 75)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousCandle.Stc < 25 && currentCandle.Stc >= 25)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousCandle = currentCandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<CandleSct> MapStcToCandle(List<StcResult> stc, IEnumerable<Candle> candles)
|
private List<CandleSct> MapStcToCandle(List<StcResult> stc, IEnumerable<Candle> candles)
|
||||||
{
|
{
|
||||||
var sctList = new List<CandleSct>();
|
var sctList = new List<CandleSct>();
|
||||||
|
|||||||
@@ -48,10 +48,124 @@ public class SuperTrendCrossEma : IndicatorBase
|
|||||||
.Where(a => a.Adx.HasValue && a.Pdi.HasValue && a.Mdi.HasValue) // Ensure all values exist
|
.Where(a => a.Adx.HasValue && a.Pdi.HasValue && a.Mdi.HasValue) // Ensure all values exist
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
ProcessSuperTrendCrossEmaSignals(superTrend, ema50, adxResults, candles, minimumRequiredHistory, adxThreshold);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None)
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CandleSuperTrend> MapSuperTrendToCandle(List<SuperTrendResult> superTrend, IEnumerable<Candle> candles)
|
||||||
|
{
|
||||||
|
var superTrends = new List<CandleSuperTrend>();
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
var currentSuperTrend = superTrend.Find(candle.Date);
|
||||||
|
if (currentSuperTrend != null)
|
||||||
|
{
|
||||||
|
superTrends.Add(new CandleSuperTrend()
|
||||||
|
{
|
||||||
|
Close = candle.Close,
|
||||||
|
Open = candle.Open,
|
||||||
|
Date = candle.Date,
|
||||||
|
Ticker = candle.Ticker,
|
||||||
|
Exchange = candle.Exchange,
|
||||||
|
SuperTrend = currentSuperTrend.SuperTrend.Value,
|
||||||
|
LowerBand = currentSuperTrend.LowerBand,
|
||||||
|
UpperBand = currentSuperTrend.UpperBand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return superTrends;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated SuperTrend values for performance optimization.
|
||||||
|
/// Note: EMA50 and ADX are still calculated on-the-fly as they're not part of the standard indicator values.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
// Validate sufficient historical data for all indicators
|
||||||
|
const int emaPeriod = 50;
|
||||||
|
const int adxPeriod = 14; // Standard ADX period
|
||||||
|
const int adxThreshold = 25; // Minimum ADX level to confirm a trend
|
||||||
|
|
||||||
|
int minimumRequiredHistory = Math.Max(Math.Max(emaPeriod, adxPeriod), Period.Value * 2); // Ensure enough data
|
||||||
|
if (candles.Count < minimumRequiredHistory)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated SuperTrend values if available
|
||||||
|
List<SuperTrendResult> superTrend = null;
|
||||||
|
if (preCalculatedValues?.SuperTrend != null && preCalculatedValues.SuperTrend.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated SuperTrend values to match the candles we're processing
|
||||||
|
superTrend = preCalculatedValues.SuperTrend
|
||||||
|
.Where(s => s.SuperTrend.HasValue && candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated SuperTrend values, calculate them
|
||||||
|
if (superTrend == null || !superTrend.Any())
|
||||||
|
{
|
||||||
|
superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value)
|
||||||
|
.Where(s => s.SuperTrend.HasValue)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// EMA50 and ADX are still calculated on-the-fly (not part of standard pre-calculation)
|
||||||
|
var ema50 = candles.GetEma(emaPeriod)
|
||||||
|
.Where(e => e.Ema.HasValue)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var adxResults = candles.GetAdx(adxPeriod)
|
||||||
|
.Where(a => a.Adx.HasValue && a.Pdi.HasValue && a.Mdi.HasValue)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
ProcessSuperTrendCrossEmaSignals(superTrend, ema50, adxResults, candles, minimumRequiredHistory, adxThreshold);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None)
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes SuperTrendCrossEma signals based on SuperTrend crossing EMA50 with ADX confirmation.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="superTrend">List of SuperTrend calculation results</param>
|
||||||
|
/// <param name="ema50">List of EMA50 calculation results</param>
|
||||||
|
/// <param name="adxResults">List of ADX calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
/// <param name="minimumRequiredHistory">Minimum history required</param>
|
||||||
|
/// <param name="adxThreshold">ADX threshold for trend confirmation</param>
|
||||||
|
private void ProcessSuperTrendCrossEmaSignals(
|
||||||
|
List<SuperTrendResult> superTrend,
|
||||||
|
List<EmaResult> ema50,
|
||||||
|
List<AdxResult> adxResults,
|
||||||
|
HashSet<Candle> candles,
|
||||||
|
int minimumRequiredHistory,
|
||||||
|
int adxThreshold)
|
||||||
|
{
|
||||||
// 2. Create merged dataset with price + indicators
|
// 2. Create merged dataset with price + indicators
|
||||||
var superTrendCandles = MapSuperTrendToCandle(superTrend, candles.TakeLast(minimumRequiredHistory));
|
var superTrendCandles = MapSuperTrendToCandle(superTrend, candles.TakeLast(minimumRequiredHistory));
|
||||||
if (superTrendCandles.Count == 0)
|
if (superTrendCandles.Count == 0)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
// 3. Add EMA50 and ADX values to the CandleSuperTrend objects
|
// 3. Add EMA50 and ADX values to the CandleSuperTrend objects
|
||||||
foreach (var candle in superTrendCandles)
|
foreach (var candle in superTrendCandles)
|
||||||
@@ -122,40 +236,6 @@ public class SuperTrendCrossEma : IndicatorBase
|
|||||||
AddSignal(current, TradeDirection.Short, Confidence.Medium);
|
AddSignal(current, TradeDirection.Short, Confidence.Medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None)
|
|
||||||
.OrderBy(s => s.Date)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CandleSuperTrend> MapSuperTrendToCandle(List<SuperTrendResult> superTrend, IEnumerable<Candle> candles)
|
|
||||||
{
|
|
||||||
var superTrends = new List<CandleSuperTrend>();
|
|
||||||
foreach (var candle in candles)
|
|
||||||
{
|
|
||||||
var currentSuperTrend = superTrend.Find(candle.Date);
|
|
||||||
if (currentSuperTrend != null)
|
|
||||||
{
|
|
||||||
superTrends.Add(new CandleSuperTrend()
|
|
||||||
{
|
|
||||||
Close = candle.Close,
|
|
||||||
Open = candle.Open,
|
|
||||||
Date = candle.Date,
|
|
||||||
Ticker = candle.Ticker,
|
|
||||||
Exchange = candle.Exchange,
|
|
||||||
SuperTrend = currentSuperTrend.SuperTrend.Value,
|
|
||||||
LowerBand = currentSuperTrend.LowerBand,
|
|
||||||
UpperBand = currentSuperTrend.UpperBand,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return superTrends;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -29,27 +29,77 @@ public class SuperTrendIndicatorBase : IndicatorBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue);
|
var superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value)
|
||||||
|
.Where(s => s.SuperTrend.HasValue)
|
||||||
|
.ToList();
|
||||||
|
if (superTrend.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ProcessSuperTrendSignals(superTrend, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated SuperTrend values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= MinimumHistory)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated SuperTrend values if available
|
||||||
|
List<SuperTrendResult> superTrend = null;
|
||||||
|
if (preCalculatedValues?.SuperTrend != null && preCalculatedValues.SuperTrend.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated SuperTrend values to match the candles we're processing
|
||||||
|
superTrend = preCalculatedValues.SuperTrend
|
||||||
|
.Where(s => s.SuperTrend.HasValue && candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (superTrend == null || !superTrend.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessSuperTrendSignals(superTrend, candles);
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes SuperTrend signals based on price position relative to SuperTrend line.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="superTrend">List of SuperTrend calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessSuperTrendSignals(List<SuperTrendResult> superTrend, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
var superTrendCandle = MapSuperTrendToCandle(superTrend, candles.TakeLast(MinimumHistory));
|
var superTrendCandle = MapSuperTrendToCandle(superTrend, candles.TakeLast(MinimumHistory));
|
||||||
|
|
||||||
if (superTrendCandle.Count == 0)
|
if (superTrendCandle.Count == 0)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
var previousCandle = superTrendCandle[0];
|
var previousCandle = superTrendCandle[0];
|
||||||
foreach (var currentCandle in superTrendCandle.Skip(1))
|
foreach (var currentCandle in superTrendCandle.Skip(1))
|
||||||
{
|
{
|
||||||
// // Short
|
|
||||||
// if (currentCandle.Close < previousCandle.SuperTrend && previousCandle.Close > previousCandle.SuperTrend)
|
|
||||||
// {
|
|
||||||
// AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Long
|
|
||||||
// if (currentCandle.Close > previousCandle.SuperTrend && previousCandle.Close < previousCandle.SuperTrend)
|
|
||||||
// {
|
|
||||||
// AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentCandle.SuperTrend < currentCandle.Close)
|
if (currentCandle.SuperTrend < currentCandle.Close)
|
||||||
{
|
{
|
||||||
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
||||||
@@ -61,13 +111,6 @@ public class SuperTrendIndicatorBase : IndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -27,6 +27,35 @@ namespace Managing.Domain.Strategies.Signals
|
|||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
ProcessThreeWhiteSoldiersSignals(candles);
|
||||||
|
|
||||||
|
return signals;
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated values.
|
||||||
|
/// Note: ThreeWhiteSoldiers is a pattern-based indicator that doesn't use traditional indicator calculations,
|
||||||
|
/// so pre-calculated values are not applicable. This method falls back to regular Run().
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
// ThreeWhiteSoldiers doesn't use traditional indicators, so pre-calculated values don't apply
|
||||||
|
// Fall back to regular calculation
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes ThreeWhiteSoldiers pattern signals based on candle patterns.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessThreeWhiteSoldiersSignals(HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
var lastFourCandles = candles.TakeLast(4);
|
var lastFourCandles = candles.TakeLast(4);
|
||||||
Candle previousCandles = null;
|
Candle previousCandles = null;
|
||||||
@@ -44,13 +73,6 @@ namespace Managing.Domain.Strategies.Signals
|
|||||||
|
|
||||||
previousCandles = currentCandle;
|
previousCandles = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return signals;
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -28,11 +28,71 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ema = candles.GetEma(Period.Value).ToList();
|
var ema = candles.GetEma(Period.Value).ToList();
|
||||||
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
|
|
||||||
|
|
||||||
if (ema.Count == 0)
|
if (ema.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
ProcessEmaTrendSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated EMA values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 2 * Period)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated EMA values if available
|
||||||
|
List<EmaResult> ema = null;
|
||||||
|
if (preCalculatedValues?.Ema != null && preCalculatedValues.Ema.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated EMA values to match the candles we're processing
|
||||||
|
ema = preCalculatedValues.Ema
|
||||||
|
.Where(e => candles.Any(c => c.Date == e.Date))
|
||||||
|
.OrderBy(e => e.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (ema == null || !ema.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessEmaTrendSignals(ema, candles);
|
||||||
|
|
||||||
|
return Signals.OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes EMA trend signals based on price position relative to EMA line.
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ema">List of EMA calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessEmaTrendSignals(List<EmaResult> ema, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
|
||||||
|
|
||||||
|
if (emaCandles.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var previousCandle = emaCandles[0];
|
var previousCandle = emaCandles[0];
|
||||||
foreach (var currentCandle in emaCandles.Skip(1))
|
foreach (var currentCandle in emaCandles.Skip(1))
|
||||||
{
|
{
|
||||||
@@ -47,13 +107,6 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -37,11 +37,72 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
|
|||||||
{
|
{
|
||||||
var stochRsi = candles
|
var stochRsi = candles
|
||||||
.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value)
|
.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value)
|
||||||
.RemoveWarmupPeriods();
|
.RemoveWarmupPeriods()
|
||||||
|
.ToList();
|
||||||
|
if (stochRsi.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ProcessStochRsiTrendSignals(stochRsi, candles);
|
||||||
|
|
||||||
|
return Signals.OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the indicator using pre-calculated StochRsi values for performance optimization.
|
||||||
|
/// </summary>
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 10 * Period + 50)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated StochRsi values if available
|
||||||
|
List<StochRsiResult> stochRsi = null;
|
||||||
|
if (preCalculatedValues?.StochRsi != null && preCalculatedValues.StochRsi.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated StochRsi values to match the candles we're processing
|
||||||
|
stochRsi = preCalculatedValues.StochRsi
|
||||||
|
.Where(s => s.Signal.HasValue && candles.Any(c => c.Date == s.Date))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||||
|
if (stochRsi == null || !stochRsi.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStochRsiTrendSignals(stochRsi, candles);
|
||||||
|
|
||||||
|
return Signals.OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes StochRsi trend signals based on Signal line thresholds (20 and 80).
|
||||||
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stochRsi">List of StochRsi calculation results</param>
|
||||||
|
/// <param name="candles">Candles to process</param>
|
||||||
|
private void ProcessStochRsiTrendSignals(List<StochRsiResult> stochRsi, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
var stochRsiCandles = MapStochRsiToCandle(stochRsi, candles.TakeLast(Period.Value));
|
var stochRsiCandles = MapStochRsiToCandle(stochRsi, candles.TakeLast(Period.Value));
|
||||||
|
|
||||||
if (stochRsi.Count() == 0)
|
if (stochRsiCandles.Count == 0)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
var previousCandle = stochRsiCandles[0];
|
var previousCandle = stochRsiCandles[0];
|
||||||
foreach (var currentCandle in stochRsiCandles.Skip(1))
|
foreach (var currentCandle in stochRsiCandles.Skip(1))
|
||||||
@@ -57,13 +118,6 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
|
|||||||
|
|
||||||
previousCandle = currentCandle;
|
previousCandle = currentCandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signals.OrderBy(s => s.Date).ToList();
|
|
||||||
}
|
|
||||||
catch (RuleException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Managing.Domain.Indicators;
|
|||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -53,11 +54,25 @@ public static class TradingBox
|
|||||||
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
|
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
|
||||||
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
|
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
|
||||||
{
|
{
|
||||||
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod);
|
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
|
||||||
|
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod,
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
|
||||||
|
{
|
||||||
|
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod, preCalculatedIndicatorValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
|
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
|
||||||
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
|
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
|
||||||
|
{
|
||||||
|
return GetSignal(newCandles, lightScenario, previousSignal, config, loopbackPeriod, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
|
||||||
|
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod,
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
|
||||||
{
|
{
|
||||||
var signalOnCandles = new List<LightSignal>();
|
var signalOnCandles = new List<LightSignal>();
|
||||||
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
|
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
|
||||||
@@ -65,7 +80,19 @@ public static class TradingBox
|
|||||||
foreach (var indicator in lightScenario.Indicators)
|
foreach (var indicator in lightScenario.Indicators)
|
||||||
{
|
{
|
||||||
IIndicator indicatorInstance = indicator.ToInterface();
|
IIndicator indicatorInstance = indicator.ToInterface();
|
||||||
var signals = indicatorInstance.Run(newCandles);
|
|
||||||
|
// Use pre-calculated indicator values if available (for backtest optimization)
|
||||||
|
List<LightSignal> signals;
|
||||||
|
if (preCalculatedIndicatorValues != null && preCalculatedIndicatorValues.ContainsKey(indicator.Type))
|
||||||
|
{
|
||||||
|
// Use pre-calculated values to avoid recalculating indicators
|
||||||
|
signals = indicatorInstance.Run(newCandles, preCalculatedIndicatorValues[indicator.Type]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal path: calculate indicators on the fly
|
||||||
|
signals = indicatorInstance.Run(newCandles);
|
||||||
|
}
|
||||||
|
|
||||||
if (signals == null || signals.Count() == 0)
|
if (signals == null || signals.Count() == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user