Fix config update + remove messages + Summary fix for not open position

This commit is contained in:
2025-10-08 02:52:11 +07:00
parent ff7e4ed3d3
commit 67065469a6
17 changed files with 209 additions and 159 deletions

View File

@@ -339,11 +339,6 @@ public class TradingBotBase : ITradingBot
{
Positions[newlyCreatedPosition.Identifier] = newlyCreatedPosition;
}
else
{
await LogWarning(
$"⚠️ Position Creation Failed\nSignal: `{signal.Identifier}`\nPosition creation returned null");
}
}
}
@@ -369,7 +364,7 @@ public class TradingBotBase : ITradingBot
try
{
// Skip processing if position is already canceled or rejected (never filled)
if (positionForSignal.Status == PositionStatus.Canceled ||
if (positionForSignal.Status == PositionStatus.Canceled ||
positionForSignal.Status == PositionStatus.Rejected)
{
Logger.LogDebug(
@@ -486,7 +481,7 @@ public class TradingBotBase : ITradingBot
if (timeSinceRequest.TotalMinutes >= waitTimeMinutes)
{
await LogWarning(
$"⚠️ Order Cleanup\nToo many open orders: `{orders.Count()}`\nPosition: `{positionForSignal.Identifier}`\nTime elapsed: `{waitTimeMinutes}min`\nCanceling all orders...");
$"⚠️ Orders Cleanup\nTime elapsed: {waitTimeMinutes}min\nCanceling all orders...");
try
{
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
@@ -495,7 +490,7 @@ public class TradingBotBase : ITradingBot
await exchangeService.CancelOrder(Account, Config.Ticker);
});
await LogInformation(
$"✅ Orders Canceled\nSuccessfully canceled all orders for: `{Config.Ticker}`");
$"✅ Orders for {internalPosition.OriginDirection} {Config.Ticker} successfully canceled");
}
catch (Exception ex)
{
@@ -504,6 +499,13 @@ public class TradingBotBase : ITradingBot
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
positionForSignal.Status = PositionStatus.Canceled;
positionForSignal.Open.SetStatus(TradeStatus.Cancelled);
positionForSignal.StopLoss.SetStatus(TradeStatus.Cancelled);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled);
await UpdatePositionDatabase(positionForSignal);
return;
}
else
@@ -577,7 +579,7 @@ public class TradingBotBase : ITradingBot
{
await LogWarning(
$"❌ Position Never Filled\nNo position on exchange and no orders\nSignal: `{signal.Identifier}`\nPosition was never filled and will be marked as canceled.");
// Position was never filled (still in New status), so just mark it as canceled
// Don't call HandleClosedPosition as that would incorrectly add volume/PnL
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
@@ -762,16 +764,16 @@ public class TradingBotBase : ITradingBot
}
}
}
else if (internalPosition.Status == PositionStatus.Rejected ||
internalPosition.Status == PositionStatus.Canceled)
{
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
if (signal.Status == SignalStatus.PositionOpen)
{
Logger.LogInformation($"Try to re-open position");
await OpenPosition(signal);
}
}
// else if (internalPosition.Status == PositionStatus.Rejected ||
// internalPosition.Status == PositionStatus.Canceled)
// {
// await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
// if (signal.Status == SignalStatus.PositionOpen)
// {
// Logger.LogInformation($"Try to re-open position");
// await OpenPosition(signal);
// }
// }
if (Config.UseSynthApi && !Config.IsForBacktest &&
positionForSignal.Status == PositionStatus.Filled)
@@ -926,24 +928,26 @@ public class TradingBotBase : ITradingBot
if (position != null)
{
if (position.Open.Status != TradeStatus.Cancelled)
if (position.Open.Status != TradeStatus.Cancelled && position.Status != PositionStatus.Rejected)
{
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
if (!Config.IsForBacktest)
{
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
async messengerService => { await messengerService.SendClosedPosition(position, Account.User); });
async messengerService => { await messengerService.SendPosition(position); });
}
Logger.LogInformation($"Position requested");
return position; // Return the created position without adding to list
return position;
}
else
{
await SetPositionStatus(signal.Identifier, PositionStatus.Rejected);
position.Status = PositionStatus.Rejected;
await UpdatePositionDatabase(position);
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return null;
return position;
}
}
@@ -964,7 +968,7 @@ public class TradingBotBase : ITradingBot
catch (Exception ex)
{
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
SentrySdk.CaptureException(ex);
return null;
}
}
@@ -1247,7 +1251,7 @@ public class TradingBotBase : ITradingBot
// We use this for reconciliation with the bot's own calculations
var totalBotFees = position.GasFees + position.UiFees;
var gmxNetPnl = gmxPosition.ProfitAndLoss.Realized; // This is already after GMX fees
position.ProfitAndLoss = new ProfitAndLoss
{
// GMX's realized PnL is already after their fees
@@ -1530,7 +1534,7 @@ public class TradingBotBase : ITradingBot
// No need to subtract fees from PnL as they're tracked separately
}
SkipCandleBasedCalculation:
SkipCandleBasedCalculation:
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
// Update position in database with all trade changes
@@ -1538,13 +1542,13 @@ public class TradingBotBase : ITradingBot
{
position.Status = PositionStatus.Finished;
await UpdatePositionDatabase(position);
// Only send PositionClosed notification if the position was actually filled
// Check if Open trade was filled (means position was opened on the broker)
if (position.Open?.Status == TradeStatus.Filled)
{
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionClosed, position);
// Update the last position closing time for cooldown period tracking
// Only update if position was actually filled
LastPositionClosingTime = Config.IsForBacktest ? currentCandle.Date : DateTime.UtcNow;
@@ -1586,10 +1590,7 @@ public class TradingBotBase : ITradingBot
if (!Config.IsForBacktest)
{
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
async messengerService =>
{
await messengerService.SendClosedPosition(position, Account.User);
});
async messengerService => { await messengerService.SendClosedPosition(position, Account.User); });
}
await CancelAllOrders();
@@ -1810,7 +1811,7 @@ public class TradingBotBase : ITradingBot
/// <param name="direction">The direction of the trade (Long/Short).</param>
/// <returns>The created Position object.</returns>
/// <exception cref="Exception">Throws if no candles are available or position opening fails.</exception>
public async Task<Position> OpenPositionManually(TradeDirection direction)
public async Task<LightSignal> CreateManualSignal(TradeDirection direction)
{
if (LastCandle == null)
{
@@ -1828,22 +1829,7 @@ public class TradingBotBase : ITradingBot
// Add the signal to our collection
await AddSignal(signal);
// Open the position using the generated signal (SL/TP handled by MoneyManagement)
var position = await OpenPosition(signal);
if (position == null)
{
// Clean up the signal if position creation failed
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
throw new Exception("Failed to open position");
}
// Add the position to the list after successful creation
Positions[position.Identifier] = position;
Logger.LogInformation(
$"👤 Manual Position Opened\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`\nAdded to positions list");
return position;
return signal;
}
public async Task AddSignal(LightSignal signal)
@@ -1866,7 +1852,7 @@ public class TradingBotBase : ITradingBot
$"🆔 Signal ID: `{signal.Identifier}`";
// Apply Synth-based signal filtering if enabled
if ((Config.UseSynthApi || !Config.IsForBacktest) && ExecutionCount > 0)
if (Config.UseSynthApi && !Config.IsForBacktest && ExecutionCount > 0)
{
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
async (tradingService, exchangeService) =>
@@ -2052,11 +2038,26 @@ public class TradingBotBase : ITradingBot
changes.Add($"👀 Watch Only: {oldWatch} → {newWatch}");
}
if (Config.MoneyManagement?.GetType().Name != newConfig.MoneyManagement?.GetType().Name)
// Check for changes in individual MoneyManagement properties
if (Config.MoneyManagement?.StopLoss != newConfig.MoneyManagement?.StopLoss)
{
var oldMM = Config.MoneyManagement?.GetType().Name ?? "None";
var newMM = newConfig.MoneyManagement?.GetType().Name ?? "None";
changes.Add($"💰 Money Management: {oldMM} → {newMM}");
var oldStopLoss = Config.MoneyManagement?.StopLoss.ToString("P2") ?? "None";
var newStopLoss = newConfig.MoneyManagement?.StopLoss.ToString("P2") ?? "None";
changes.Add($"🛑 Stop Loss: {oldStopLoss} → {newStopLoss}");
}
if (Config.MoneyManagement?.TakeProfit != newConfig.MoneyManagement?.TakeProfit)
{
var oldTakeProfit = Config.MoneyManagement?.TakeProfit.ToString("P2") ?? "None";
var newTakeProfit = newConfig.MoneyManagement?.TakeProfit.ToString("P2") ?? "None";
changes.Add($"🎯 Take Profit: {oldTakeProfit} → {newTakeProfit}");
}
if (Config.MoneyManagement?.Leverage != newConfig.MoneyManagement?.Leverage)
{
var oldLeverage = Config.MoneyManagement?.Leverage.ToString("F1") + "x" ?? "None";
var newLeverage = newConfig.MoneyManagement?.Leverage.ToString("F1") + "x" ?? "None";
changes.Add($"⚡ Leverage: {oldLeverage} → {newLeverage}");
}
if (Config.RiskManagement != newConfig.RiskManagement)
@@ -2260,13 +2261,13 @@ public class TradingBotBase : ITradingBot
{
var grainKey = CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
var grain = grainFactory.GetGrain<ICandleStoreGrain>(grainKey);
var lastCandles = await grain.GetLastCandle(1);
LastCandle = lastCandles.FirstOrDefault();
if (LastCandle != null)
{
Logger.LogDebug("Successfully refreshed last candle for {Ticker} at {Date}",
Logger.LogDebug("Successfully refreshed last candle for {Ticker} at {Date}",
Config.Ticker, LastCandle.Date);
}
else