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

@@ -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<IExchangeService>(_scopeFactory,
async exchangeService =>
{
closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
});
if (!Config.IsForBacktest)
{
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
async exchangeService =>
{
closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
});
}
bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long
? closingPrice > position.Open.Price

View File

@@ -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)
{

View File

@@ -42,26 +42,6 @@ public class LightIndicator
[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>
/// Converts a LightIndicator back to a full Indicator
/// </summary>

View File

@@ -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<LightIndicator>()
};
return lightScenario;

View File

@@ -103,6 +103,26 @@ public static class ScenarioHelpers
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(
IndicatorType type,
string name,

View File

@@ -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 {

View File

@@ -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 {