Fix backtests and indicators

This commit is contained in:
2025-10-16 20:06:47 +07:00
parent f49f75ede0
commit d6122aeb27
8 changed files with 60 additions and 60 deletions

View File

@@ -723,13 +723,13 @@ fi
SCRIPT_SIZE=$(wc -l < "$MIGRATION_SCRIPT") SCRIPT_SIZE=$(wc -l < "$MIGRATION_SCRIPT")
echo "📄 Migration script contains $SCRIPT_SIZE lines" echo "📄 Migration script contains $SCRIPT_SIZE lines"
# Show first 20 lines as preview # Show last 20 lines as preview
echo "" echo ""
echo "📋 PREVIEW (first 20 lines):" echo "📋 PREVIEW (last 20 lines):"
echo "----------------------------------------" echo "----------------------------------------"
head -20 "$MIGRATION_SCRIPT" | sed 's/^/ /' tail -20 "$MIGRATION_SCRIPT" | sed 's/^/ /'
if [ "$SCRIPT_SIZE" -gt 20 ]; then 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 fi
echo "----------------------------------------" echo "----------------------------------------"
echo "" echo ""

View File

@@ -701,13 +701,9 @@ public class TradingBotBase : ITradingBot
{ {
if (positionForSignal.StopLoss.Price >= lastCandle.Low) 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.SetDate(lastCandle.Date);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
// Cancel TP trades when SL is hit
if (positionForSignal.TakeProfit1 != null) if (positionForSignal.TakeProfit1 != null)
{ {
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled);
@@ -719,16 +715,13 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
executionPrice, true); positionForSignal.StopLoss.Price, true);
} }
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High && else if (positionForSignal.TakeProfit1.Price <= lastCandle.High &&
positionForSignal.TakeProfit1.Status != TradeStatus.Filled) 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.SetDate(lastCandle.Date);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
@@ -739,15 +732,12 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
executionPrice, positionForSignal.TakeProfit2 == null); positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
} }
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High) 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.SetDate(lastCandle.Date);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
@@ -758,18 +748,15 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
executionPrice, true); positionForSignal.TakeProfit2.Price, true);
} }
} }
else if (positionForSignal.OriginDirection == TradeDirection.Short) else if (positionForSignal.OriginDirection == TradeDirection.Short)
{ {
if (positionForSignal.StopLoss.Price <= lastCandle.High) 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.SetDate(lastCandle.Date);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
@@ -785,16 +772,14 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
executionPrice, true); positionForSignal.StopLoss.Price, true);
} }
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low && else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low &&
positionForSignal.TakeProfit1.Status != TradeStatus.Filled) positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
{ {
// Use actual execution price (lastCandle.Low for TP hit on SHORT) // 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.SetDate(lastCandle.Date);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
@@ -805,15 +790,13 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
executionPrice, positionForSignal.TakeProfit2 == null); positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
} }
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low) else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
{ {
// Use actual execution price (lastCandle.Low for TP hit on SHORT) // 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.SetDate(lastCandle.Date);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
@@ -824,9 +807,9 @@ public class TradingBotBase : ITradingBot
} }
await LogInformation( 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, await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
executionPrice, true); positionForSignal.TakeProfit2.Price, true);
} }
} }
} }
@@ -1530,11 +1513,15 @@ public class TradingBotBase : ITradingBot
closingPrice = Config.IsForBacktest closingPrice = Config.IsForBacktest
? currentCandle.Close ? currentCandle.Close
: 0; : 0;
if (!Config.IsForBacktest)
{
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
async exchangeService => async exchangeService =>
{ {
closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker); closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
}); });
}
bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long
? closingPrice > position.Open.Price ? closingPrice > position.Open.Price

View File

@@ -451,7 +451,8 @@ public class TradingService : ITradingService
{ {
try try
{ {
indicatorsValues[indicator.Type] = indicator.GetIndicatorValues(candles); var buildedIndicator = ScenarioHelpers.BuildIndicator(ScenarioHelpers.BaseToLight(indicator));
indicatorsValues[indicator.Type] = buildedIndicator.GetIndicatorValues(candles);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -42,26 +42,6 @@ public class LightIndicator
[Id(11)] public int? CyclePeriods { get; set; } [Id(11)] public int? CyclePeriods { get; set; }
/// <summary>
/// Converts a full Indicator to a LightIndicator
/// </summary>
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
};
}
/// <summary> /// <summary>
/// Converts a LightIndicator back to a full Indicator /// Converts a LightIndicator back to a full Indicator
/// </summary> /// </summary>

View File

@@ -30,7 +30,7 @@ public class LightScenario
{ {
var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod) var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod)
{ {
Indicators = scenario.Indicators?.Select(LightIndicator.BaseToLight).ToList() ?? Indicators = scenario.Indicators?.Select(ScenarioHelpers.BaseToLight).ToList() ??
new List<LightIndicator>() new List<LightIndicator>()
}; };
return lightScenario; return lightScenario;

View File

@@ -103,6 +103,26 @@ public static class ScenarioHelpers
return result; return result;
} }
/// <summary>
/// Converts a full Indicator to a LightIndicator
/// </summary>
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( public static IIndicator BuildIndicator(
IndicatorType type, IndicatorType type,
string name, string name,

View File

@@ -4070,6 +4070,8 @@ export interface Backtest {
requestId?: string; requestId?: string;
metadata?: any | null; metadata?: any | null;
scoreMessage?: string; scoreMessage?: string;
initialBalance: number;
netPnl: number;
} }
export interface TradingBotConfig { export interface TradingBotConfig {
@@ -4355,6 +4357,8 @@ export interface LightBacktestResponse {
sharpeRatio: number; sharpeRatio: number;
score: number; score: number;
scoreMessage: string; scoreMessage: string;
initialBalance: number;
netPnl: number;
} }
export enum BacktestSortableColumn { export enum BacktestSortableColumn {
@@ -4389,6 +4393,8 @@ export interface LightBacktest {
scoreMessage?: string | null; scoreMessage?: string | null;
metadata?: any | null; metadata?: any | null;
ticker?: string | null; ticker?: string | null;
initialBalance?: number;
netPnl?: number;
} }
export interface RunBacktestRequest { export interface RunBacktestRequest {

View File

@@ -245,6 +245,8 @@ export interface Backtest {
requestId?: string; requestId?: string;
metadata?: any | null; metadata?: any | null;
scoreMessage?: string; scoreMessage?: string;
initialBalance: number;
netPnl: number;
} }
export interface TradingBotConfig { export interface TradingBotConfig {
@@ -530,6 +532,8 @@ export interface LightBacktestResponse {
sharpeRatio: number; sharpeRatio: number;
score: number; score: number;
scoreMessage: string; scoreMessage: string;
initialBalance: number;
netPnl: number;
} }
export enum BacktestSortableColumn { export enum BacktestSortableColumn {
@@ -564,6 +568,8 @@ export interface LightBacktest {
scoreMessage?: string | null; scoreMessage?: string | null;
metadata?: any | null; metadata?: any | null;
ticker?: string | null; ticker?: string | null;
initialBalance?: number;
netPnl?: number;
} }
export interface RunBacktestRequest { export interface RunBacktestRequest {