From d6122aeb273da7a91d3a08c90b65651e3334e90b Mon Sep 17 00:00:00 2001 From: cryptooda Date: Thu, 16 Oct 2025 20:06:47 +0700 Subject: [PATCH] Fix backtests and indicators --- scripts/safe-migrate.sh | 8 +-- .../Bots/TradingBotBase.cs | 55 +++++++------------ .../Trading/TradingService.cs | 3 +- .../Indicators/LightIndicator.cs | 20 ------- .../Scenarios/LightScenario.cs | 2 +- .../Scenarios/ScenarioHelpers.cs | 20 +++++++ .../src/generated/ManagingApi.ts | 6 ++ .../src/generated/ManagingApiTypes.ts | 6 ++ 8 files changed, 60 insertions(+), 60 deletions(-) diff --git a/scripts/safe-migrate.sh b/scripts/safe-migrate.sh index 95fa6c96..fb25b23c 100755 --- a/scripts/safe-migrate.sh +++ b/scripts/safe-migrate.sh @@ -723,13 +723,13 @@ fi SCRIPT_SIZE=$(wc -l < "$MIGRATION_SCRIPT") echo "📄 Migration script contains $SCRIPT_SIZE lines" - # Show first 20 lines as preview + # Show last 20 lines as preview echo "" - echo "📋 PREVIEW (first 20 lines):" + echo "📋 PREVIEW (last 20 lines):" echo "----------------------------------------" - head -20 "$MIGRATION_SCRIPT" | sed 's/^/ /' + tail -20 "$MIGRATION_SCRIPT" | sed 's/^/ /' if [ "$SCRIPT_SIZE" -gt 20 ]; then - echo " ... (showing first 20 lines of $SCRIPT_SIZE total)" + echo " ... (showing last 20 lines of $SCRIPT_SIZE total)" fi echo "----------------------------------------" echo "" diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index dddb9e2a..215d6596 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -701,13 +701,9 @@ public class TradingBotBase : ITradingBot { if (positionForSignal.StopLoss.Price >= lastCandle.Low) { - // Use actual execution price (lastCandle.Low for SL hit) - var executionPrice = lastCandle.Low; - positionForSignal.StopLoss.SetPrice(executionPrice, 2); positionForSignal.StopLoss.SetDate(lastCandle.Date); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); - // Cancel TP trades when SL is hit if (positionForSignal.TakeProfit1 != null) { positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled); @@ -719,16 +715,13 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🛑 Stop Loss Hit\nClosing LONG position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.StopLoss.Price:F2}`)"); + $"🛑 Stop Loss Hit\nClosing LONG position\nPrice: `${positionForSignal.StopLoss.Price:F2}`"); await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, - executionPrice, true); + positionForSignal.StopLoss.Price, true); } else if (positionForSignal.TakeProfit1.Price <= lastCandle.High && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) { - // Use actual execution price (lastCandle.High for TP hit) - var executionPrice = lastCandle.High; - positionForSignal.TakeProfit1.SetPrice(executionPrice, 2); positionForSignal.TakeProfit1.SetDate(lastCandle.Date); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); @@ -739,15 +732,12 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🎯 Take Profit 1 Hit\nClosing LONG position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit1.Price:F2}`)"); + $"🎯 Take Profit 1 Hit\nClosing LONG position\nPrice: `${positionForSignal.TakeProfit1.Price:F2}`"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, - executionPrice, positionForSignal.TakeProfit2 == null); + positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); } else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High) { - // Use actual execution price (lastCandle.High for TP hit) - var executionPrice = lastCandle.High; - positionForSignal.TakeProfit2.SetPrice(executionPrice, 2); positionForSignal.TakeProfit2.SetDate(lastCandle.Date); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); @@ -758,18 +748,15 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🎯 Take Profit 2 Hit\nClosing LONG position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit2.Price:F2}`)"); + $"🎯 Take Profit 2 Hit\nClosing LONG position\nPrice: `${positionForSignal.TakeProfit2.Price:F2}`"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, - executionPrice, true); + positionForSignal.TakeProfit2.Price, true); } } else if (positionForSignal.OriginDirection == TradeDirection.Short) { if (positionForSignal.StopLoss.Price <= lastCandle.High) { - // Use actual execution price (lastCandle.High for SL hit on SHORT) - var executionPrice = lastCandle.High; - positionForSignal.StopLoss.SetPrice(executionPrice, 2); positionForSignal.StopLoss.SetDate(lastCandle.Date); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); @@ -785,16 +772,14 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🛑 Stop Loss Hit\nClosing SHORT position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.StopLoss.Price:F2}`)"); + $"🛑 Stop Loss Hit\nClosing SHORT position\nPrice: `${positionForSignal.StopLoss.Price:F2}`"); await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, - executionPrice, true); + positionForSignal.StopLoss.Price, true); } else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) { // Use actual execution price (lastCandle.Low for TP hit on SHORT) - var executionPrice = lastCandle.Low; - positionForSignal.TakeProfit1.SetPrice(executionPrice, 2); positionForSignal.TakeProfit1.SetDate(lastCandle.Date); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); @@ -805,15 +790,13 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🎯 Take Profit 1 Hit\nClosing SHORT position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit1.Price:F2}`)"); + $"🎯 Take Profit 1 Hit\nClosing SHORT position\nPrice: `${positionForSignal.TakeProfit1.Price:F2}` (was `${positionForSignal.TakeProfit1.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, - executionPrice, positionForSignal.TakeProfit2 == null); + positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); } else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low) { // Use actual execution price (lastCandle.Low for TP hit on SHORT) - var executionPrice = lastCandle.Low; - positionForSignal.TakeProfit2.SetPrice(executionPrice, 2); positionForSignal.TakeProfit2.SetDate(lastCandle.Date); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); @@ -824,9 +807,9 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"🎯 Take Profit 2 Hit\nClosing SHORT position\nPrice: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit2.Price:F2}`)"); + $"🎯 Take Profit 2 Hit\nClosing SHORT position\nPrice: `${positionForSignal.TakeProfit2.Price:F2}` (was `${positionForSignal.TakeProfit2.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, - executionPrice, true); + positionForSignal.TakeProfit2.Price, true); } } } @@ -1530,11 +1513,15 @@ public class TradingBotBase : ITradingBot closingPrice = Config.IsForBacktest ? currentCandle.Close : 0; - await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async exchangeService => - { - closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker); - }); + + if (!Config.IsForBacktest) + { + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker); + }); + } bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long ? closingPrice > position.Open.Price diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 5927f748..33ac8d6b 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -451,7 +451,8 @@ public class TradingService : ITradingService { try { - indicatorsValues[indicator.Type] = indicator.GetIndicatorValues(candles); + var buildedIndicator = ScenarioHelpers.BuildIndicator(ScenarioHelpers.BaseToLight(indicator)); + indicatorsValues[indicator.Type] = buildedIndicator.GetIndicatorValues(candles); } catch (Exception ex) { diff --git a/src/Managing.Domain/Indicators/LightIndicator.cs b/src/Managing.Domain/Indicators/LightIndicator.cs index 458bf57c..639b6738 100644 --- a/src/Managing.Domain/Indicators/LightIndicator.cs +++ b/src/Managing.Domain/Indicators/LightIndicator.cs @@ -42,26 +42,6 @@ public class LightIndicator [Id(11)] public int? CyclePeriods { get; set; } - /// - /// Converts a full Indicator to a LightIndicator - /// - public static LightIndicator BaseToLight(IndicatorBase indicatorBase) - { - return new LightIndicator(indicatorBase.Name, indicatorBase.Type) - { - SignalType = indicatorBase.SignalType, - MinimumHistory = indicatorBase.MinimumHistory, - Period = indicatorBase.Period, - FastPeriods = indicatorBase.FastPeriods, - SlowPeriods = indicatorBase.SlowPeriods, - SignalPeriods = indicatorBase.SignalPeriods, - Multiplier = indicatorBase.Multiplier, - SmoothPeriods = indicatorBase.SmoothPeriods, - StochPeriods = indicatorBase.StochPeriods, - CyclePeriods = indicatorBase.CyclePeriods - }; - } - /// /// Converts a LightIndicator back to a full Indicator /// diff --git a/src/Managing.Domain/Scenarios/LightScenario.cs b/src/Managing.Domain/Scenarios/LightScenario.cs index 1ba1a3b9..692526cc 100644 --- a/src/Managing.Domain/Scenarios/LightScenario.cs +++ b/src/Managing.Domain/Scenarios/LightScenario.cs @@ -30,7 +30,7 @@ public class LightScenario { var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod) { - Indicators = scenario.Indicators?.Select(LightIndicator.BaseToLight).ToList() ?? + Indicators = scenario.Indicators?.Select(ScenarioHelpers.BaseToLight).ToList() ?? new List() }; return lightScenario; diff --git a/src/Managing.Domain/Scenarios/ScenarioHelpers.cs b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs index 4a78792a..aadc2bee 100644 --- a/src/Managing.Domain/Scenarios/ScenarioHelpers.cs +++ b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs @@ -103,6 +103,26 @@ public static class ScenarioHelpers return result; } + /// + /// Converts a full Indicator to a LightIndicator + /// + public static LightIndicator BaseToLight(IndicatorBase indicatorBase) + { + return new LightIndicator(indicatorBase.Name, indicatorBase.Type) + { + SignalType = indicatorBase.SignalType, + MinimumHistory = indicatorBase.MinimumHistory, + Period = indicatorBase.Period, + FastPeriods = indicatorBase.FastPeriods, + SlowPeriods = indicatorBase.SlowPeriods, + SignalPeriods = indicatorBase.SignalPeriods, + Multiplier = indicatorBase.Multiplier, + SmoothPeriods = indicatorBase.SmoothPeriods, + StochPeriods = indicatorBase.StochPeriods, + CyclePeriods = indicatorBase.CyclePeriods + }; + } + public static IIndicator BuildIndicator( IndicatorType type, string name, diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index f4b62763..38a6002b 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -4070,6 +4070,8 @@ export interface Backtest { requestId?: string; metadata?: any | null; scoreMessage?: string; + initialBalance: number; + netPnl: number; } export interface TradingBotConfig { @@ -4355,6 +4357,8 @@ export interface LightBacktestResponse { sharpeRatio: number; score: number; scoreMessage: string; + initialBalance: number; + netPnl: number; } export enum BacktestSortableColumn { @@ -4389,6 +4393,8 @@ export interface LightBacktest { scoreMessage?: string | null; metadata?: any | null; ticker?: string | null; + initialBalance?: number; + netPnl?: number; } export interface RunBacktestRequest { diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index f4fdcbe0..790cef43 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -245,6 +245,8 @@ export interface Backtest { requestId?: string; metadata?: any | null; scoreMessage?: string; + initialBalance: number; + netPnl: number; } export interface TradingBotConfig { @@ -530,6 +532,8 @@ export interface LightBacktestResponse { sharpeRatio: number; score: number; scoreMessage: string; + initialBalance: number; + netPnl: number; } export enum BacktestSortableColumn { @@ -564,6 +568,8 @@ export interface LightBacktest { scoreMessage?: string | null; metadata?: any | null; ticker?: string | null; + initialBalance?: number; + netPnl?: number; } export interface RunBacktestRequest {