This commit is contained in:
2025-06-05 01:17:15 +07:00
parent 973a8c7c61
commit 6441687df3
12 changed files with 281 additions and 198 deletions

View File

@@ -471,51 +471,74 @@ public class TradingBot : Bot, ITradingBot
// Check time-based position management (only if MaxPositionTimeHours is set)
if (Config.MaxPositionTimeHours.HasValue)
{
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
var isAtBreakeven = Math.Abs(lastCandle.Close - positionForSignal.Open.Price) < 0.01m; // Small tolerance for breakeven
var hasExceededTimeLimit = HasPositionExceededTimeLimit(positionForSignal, currentTime);
// Calculate current unrealized PNL for logging
var currentPnl = CalculateUnrealizedPnl(positionForSignal, lastCandle.Close);
var pnlPercentage = Math.Round((currentPnl / (positionForSignal.Open.Price * positionForSignal.Open.Quantity)) * 100, 2);
var pnlPercentage =
Math.Round(
(currentPnl / (positionForSignal.Open.Price * positionForSignal.Open.Quantity)) * 100, 2);
// Early closure logic when CloseEarlyWhenProfitable is enabled
if (Config.CloseEarlyWhenProfitable && (isPositionInProfit || isAtBreakeven))
if (Config.CloseEarlyWhenProfitable)
{
await LogInformation(
$"Closing position early due to profitability - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}. Position is {(isPositionInProfit ? "in profit" : "at breakeven")} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is enabled.");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return;
}
// Time limit exceeded logic
if (hasExceededTimeLimit)
{
if (Config.CloseEarlyWhenProfitable || isPositionInProfit || isAtBreakeven)
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
var isAtBreakeven =
Math.Abs(lastCandle.Close - positionForSignal.Open.Price) <
0.01m; // Small tolerance for breakeven
if (isPositionInProfit || isAtBreakeven)
{
// Close when time limit is reached if:
// 1. CloseEarlyWhenProfitable is enabled (safety net), OR
// 2. Position is in profit/breakeven (when CloseEarlyWhenProfitable is disabled)
await LogInformation(
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
$"Position is {(isPositionInProfit ? "in profit" : isAtBreakeven ? "at breakeven" : "at a loss")} " +
$"Closing position early due to profitability - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}. Position is {(isPositionInProfit ? "in profit" : "at breakeven")} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%)");
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is enabled.");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return;
}
else
// Time limit exceeded logic when CloseEarlyWhenProfitable is enabled
if (hasExceededTimeLimit && (isPositionInProfit || isAtBreakeven))
{
await LogInformation(
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
$"Position is {(isPositionInProfit ? "in profit" : "at breakeven")} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is enabled.");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return;
}
else if (hasExceededTimeLimit)
{
await LogInformation(
$"Position has exceeded time limit ({Config.MaxPositionTimeHours} hours) but is at a loss " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is disabled - waiting for profit or breakeven before closing.");
$"CloseEarlyWhenProfitable is enabled - waiting for profit or breakeven before closing.");
}
}
else
{
// Time limit exceeded logic when CloseEarlyWhenProfitable is disabled
if (hasExceededTimeLimit)
{
var isPositionInProfit = await IsPositionInProfit(positionForSignal, lastCandle.Close);
var profitStatus = isPositionInProfit ? "in profit" :
Math.Abs(lastCandle.Close - positionForSignal.Open.Price) < 0.01m ? "at breakeven" : "at a loss";
await LogInformation(
$"Closing position due to time limit - Position opened at {positionForSignal.Open.Date}, " +
$"current time {currentTime}, max time limit {Config.MaxPositionTimeHours} hours. " +
$"Position is {profitStatus} " +
$"(entry: {positionForSignal.Open.Price}, current: {lastCandle.Close}). " +
$"Current PNL: ${currentPnl:F2} ({pnlPercentage:F2}%). " +
$"CloseEarlyWhenProfitable is disabled - closing regardless of profit status.");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
return;
}
}
}
@@ -665,7 +688,6 @@ public class TradingBot : Bot, ITradingBot
$"Position {previousSignal.Identifier} is not in profit (entry: {openedPosition.Open.Price}, current: {lastPrice}). " +
$"Signal {signal.Identifier} will wait for position to become profitable before flipping.");
// Keep signal in waiting status to check again on next execution
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return;
}
@@ -1046,7 +1068,7 @@ public class TradingBot : Bot, ITradingBot
private void SetSignalStatus(string signalIdentifier, SignalStatus signalStatus)
{
if (Signals.Any(s => s.Identifier == signalIdentifier))
if (Signals.Any(s => s.Identifier == signalIdentifier && s.Status != signalStatus))
{
Signals.First(s => s.Identifier == signalIdentifier).Status = signalStatus;
Logger.LogInformation($"Signal {signalIdentifier} is now {signalStatus}");
@@ -1294,7 +1316,7 @@ public class TradingBot : Bot, ITradingBot
var timeOpen = currentTime - position.Open.Date;
var maxTimeAllowed = TimeSpan.FromHours((double)Config.MaxPositionTimeHours.Value);
return timeOpen >= maxTimeAllowed;
}
@@ -1360,11 +1382,11 @@ public class TradingBot : Bot, ITradingBot
// Log the configuration update
await LogInformation($"Updating bot configuration. Previous config: " +
$"Balance: {Config.BotTradingBalance}, " +
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
$"Cooldown: {Config.CooldownPeriod}, " +
$"MaxLoss: {Config.MaxLossStreak}");
$"Balance: {Config.BotTradingBalance}, " +
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
$"Cooldown: {Config.CooldownPeriod}, " +
$"MaxLoss: {Config.MaxLossStreak}");
// Update the configuration
Config = newConfig;
@@ -1388,11 +1410,11 @@ public class TradingBot : Bot, ITradingBot
}
await LogInformation($"Bot configuration updated successfully. New config: " +
$"Balance: {Config.BotTradingBalance}, " +
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
$"Cooldown: {Config.CooldownPeriod}, " +
$"MaxLoss: {Config.MaxLossStreak}");
$"Balance: {Config.BotTradingBalance}, " +
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
$"Cooldown: {Config.CooldownPeriod}, " +
$"MaxLoss: {Config.MaxLossStreak}");
// Save the updated configuration as backup
if (!Config.IsForBacktest)