Fix ROI
This commit is contained in:
@@ -99,9 +99,6 @@ public class TradingBotBase : ITradingBot
|
||||
// Notify AgentGrain about bot startup
|
||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.BotStarted,
|
||||
$"Bot: {Config.Name}, Ticker: {Config.Ticker}");
|
||||
|
||||
// Notify platform summary about active strategy count change
|
||||
await NotifyPlatformSummaryAboutStrategyCount();
|
||||
break;
|
||||
|
||||
case BotStatus.Running:
|
||||
@@ -390,11 +387,9 @@ public class TradingBotBase : ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
brokerPositions = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(_scopeFactory,
|
||||
async exchangeService =>
|
||||
{
|
||||
return [.. await exchangeService.GetBrokerPositions(Account)];
|
||||
});
|
||||
brokerPositions = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(
|
||||
_scopeFactory,
|
||||
async exchangeService => { return [.. await exchangeService.GetBrokerPositions(Account)]; });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -421,7 +416,7 @@ public class TradingBotBase : ITradingBot
|
||||
if (!internalPosition.Status.Equals(PositionStatus.New))
|
||||
{
|
||||
internalPosition.Status = PositionStatus.Filled;
|
||||
|
||||
|
||||
// Update Open trade status when position becomes Filled
|
||||
if (internalPosition.Open != null)
|
||||
{
|
||||
@@ -434,8 +429,11 @@ public class TradingBotBase : ITradingBot
|
||||
if (internalPosition.Status == PositionStatus.New)
|
||||
{
|
||||
var orders = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Trade>>(_scopeFactory,
|
||||
async exchangeService => { return [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)]; });
|
||||
|
||||
async exchangeService =>
|
||||
{
|
||||
return [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)];
|
||||
});
|
||||
|
||||
if (orders.Any())
|
||||
{
|
||||
var ordersCount = orders.Count();
|
||||
@@ -480,25 +478,27 @@ public class TradingBotBase : ITradingBot
|
||||
// Check if position is already open on broker with 2 orders
|
||||
await LogInformation(
|
||||
$"🔍 **Checking Broker Position**\nPosition has exactly `{orders.Count()}` open orders\nChecking if position is already open on broker...");
|
||||
|
||||
|
||||
Position brokerPosition = null;
|
||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
||||
{
|
||||
var brokerPositions = await exchangeService.GetBrokerPositions(Account);
|
||||
brokerPosition = brokerPositions.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
||||
});
|
||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||
async exchangeService =>
|
||||
{
|
||||
var brokerPositions = await exchangeService.GetBrokerPositions(Account);
|
||||
brokerPosition = brokerPositions.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
||||
});
|
||||
|
||||
if (brokerPosition != null)
|
||||
{
|
||||
await LogInformation(
|
||||
$"✅ **Position Found on Broker**\nPosition is already open on broker\nUpdating position status to Filled");
|
||||
|
||||
|
||||
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Filled);
|
||||
|
||||
|
||||
// Notify platform summary about the executed trade
|
||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
||||
$"Position found on broker with 2 orders: {internalPosition.Identifier}", internalPosition);
|
||||
$"Position found on broker with 2 orders: {internalPosition.Identifier}",
|
||||
internalPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -519,11 +519,13 @@ public class TradingBotBase : ITradingBot
|
||||
await HandleClosedPosition(positionForSignal);
|
||||
}
|
||||
}
|
||||
else if (internalPosition.Status == PositionStatus.Finished || internalPosition.Status == PositionStatus.Flipped)
|
||||
else if (internalPosition.Status == PositionStatus.Finished ||
|
||||
internalPosition.Status == PositionStatus.Flipped)
|
||||
{
|
||||
await HandleClosedPosition(positionForSignal);
|
||||
}
|
||||
else if (internalPosition.Status == PositionStatus.Filled || internalPosition.Status == PositionStatus.PartiallyFilled)
|
||||
else if (internalPosition.Status == PositionStatus.Filled ||
|
||||
internalPosition.Status == PositionStatus.PartiallyFilled)
|
||||
{
|
||||
Candle lastCandle = null;
|
||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
||||
@@ -619,7 +621,8 @@ public class TradingBotBase : ITradingBot
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (internalPosition.Status == PositionStatus.Rejected || internalPosition.Status == PositionStatus.Canceled)
|
||||
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)
|
||||
@@ -664,18 +667,18 @@ public class TradingBotBase : ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
if (!Config.IsForBacktest){
|
||||
// Update position in database with broker data
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
// Update position in database with broker data
|
||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
||||
{
|
||||
// Update the internal position with broker data
|
||||
internalPosition.Status = PositionStatus.Filled;
|
||||
internalPosition.ProfitAndLoss = internalPosition.ProfitAndLoss;
|
||||
|
||||
|
||||
// Save updated position to database
|
||||
await tradingService.UpdatePositionAsync(internalPosition);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -807,9 +810,6 @@ public class TradingBotBase : ITradingBot
|
||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
||||
$"Signal: {signal.Identifier}", position);
|
||||
|
||||
// Publish TradeExecutedEvent for the opening trade (this handles both position opening and trade execution)
|
||||
await PublishTradeExecutedEventAsync(position.Open, position, signal.Identifier, 0);
|
||||
|
||||
Logger.LogInformation($"Position requested");
|
||||
return position; // Return the created position without adding to list
|
||||
}
|
||||
@@ -828,10 +828,11 @@ public class TradingBotBase : ITradingBot
|
||||
// Handle insufficient funds errors with user-friendly messaging
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
await LogWarning(ex.UserMessage);
|
||||
|
||||
|
||||
// Log the technical details for debugging
|
||||
Logger.LogError(ex, "Insufficient funds error for signal {SignalId}: {ErrorMessage}", signal.Identifier, ex.Message);
|
||||
|
||||
Logger.LogError(ex, "Insufficient funds error for signal {SignalId}: {ErrorMessage}", signal.Identifier,
|
||||
ex.Message);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1066,7 +1067,7 @@ public class TradingBotBase : ITradingBot
|
||||
if (currentCandle != null)
|
||||
{
|
||||
List<Candle> recentCandles = null;
|
||||
|
||||
|
||||
if (Config.IsForBacktest)
|
||||
{
|
||||
recentCandles = LastCandle != null ? new List<Candle>() { LastCandle } : new List<Candle>();
|
||||
@@ -1076,16 +1077,18 @@ public class TradingBotBase : ITradingBot
|
||||
// Use CandleStoreGrain to get recent candles instead of calling exchange service directly
|
||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
||||
{
|
||||
var grainKey = CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
|
||||
var grainKey =
|
||||
CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
|
||||
var grain = grainFactory.GetGrain<ICandleStoreGrain>(grainKey);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
recentCandles = await grain.GetLastCandle(5);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error retrieving recent candles from CandleStoreGrain for {GrainKey}", grainKey);
|
||||
Logger.LogError(ex, "Error retrieving recent candles from CandleStoreGrain for {GrainKey}",
|
||||
grainKey);
|
||||
recentCandles = new List<Candle>();
|
||||
}
|
||||
});
|
||||
@@ -1218,10 +1221,8 @@ public class TradingBotBase : ITradingBot
|
||||
// Update position in database with all trade changes
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
||||
{
|
||||
await tradingService.UpdatePositionAsync(position);
|
||||
});
|
||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory,
|
||||
async tradingService => { await tradingService.UpdatePositionAsync(position); });
|
||||
}
|
||||
|
||||
// Update the last position closing time for cooldown period tracking
|
||||
@@ -1235,7 +1236,8 @@ public class TradingBotBase : ITradingBot
|
||||
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
|
||||
Logger.LogInformation(
|
||||
string.Format("💰 **Balance Updated**\nNew bot trading balance: `${0:F2}`", Config.BotTradingBalance));
|
||||
string.Format("💰 **Balance Updated**\nNew bot trading balance: `${0:F2}`",
|
||||
Config.BotTradingBalance));
|
||||
}
|
||||
|
||||
// Notify AgentGrain about position closing
|
||||
@@ -1244,13 +1246,6 @@ public class TradingBotBase : ITradingBot
|
||||
: "PnL: Unknown";
|
||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionClosed,
|
||||
string.Format("Signal: {0}, {1}", position.SignalIdentifier, pnlInfo), position);
|
||||
|
||||
// Publish TradeExecutedEvent for the closing trade
|
||||
var closingTrade = GetClosingTrade(position);
|
||||
if (closingTrade != null)
|
||||
{
|
||||
await PublishTradeExecutedEventAsync(closingTrade, position, position.SignalIdentifier, position.ProfitAndLoss?.Realized ?? 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1260,8 +1255,11 @@ public class TradingBotBase : ITradingBot
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
||||
messengerService => { messengerService.SendClosingPosition(position);
|
||||
return Task.CompletedTask; });
|
||||
messengerService =>
|
||||
{
|
||||
messengerService.SendClosingPosition(position);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
await CancelAllOrders();
|
||||
@@ -1330,7 +1328,7 @@ public class TradingBotBase : ITradingBot
|
||||
Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||
await LogInformation(
|
||||
$"📊 **Position Status Change**\nPosition: `{signalIdentifier}`\nStatus: `{position.Status}` → `{positionStatus}`");
|
||||
|
||||
|
||||
// Update Open trade status when position becomes Filled
|
||||
if (positionStatus == PositionStatus.Filled && position.Open != null)
|
||||
{
|
||||
@@ -1444,7 +1442,7 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
// Network Fee: $0.50 for opening position only
|
||||
// Closing is handled by oracle, so no network fee for closing
|
||||
var networkFeeForOpening = 0.50m;
|
||||
var networkFeeForOpening = 0.15m;
|
||||
fees += networkFeeForOpening;
|
||||
|
||||
return fees;
|
||||
@@ -1456,9 +1454,6 @@ public class TradingBotBase : ITradingBot
|
||||
Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
|
||||
await LogInformation(
|
||||
$"🔄 **Watch Mode Toggle**\nBot: `{Config.Name}`\nWatch Only: `{(Config.IsForWatchingOnly ? "ON" : "OFF")}`");
|
||||
|
||||
// Notify platform summary about strategy count change
|
||||
await NotifyPlatformSummaryAboutStrategyCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1467,9 +1462,6 @@ public class TradingBotBase : ITradingBot
|
||||
public async Task StopBot()
|
||||
{
|
||||
await LogInformation($"🛑 **Bot Stopped**\nBot: `{Config.Name}`\nTicker: `{Config.Ticker}`");
|
||||
|
||||
// Notify platform summary about strategy count change
|
||||
await NotifyPlatformSummaryAboutStrategyCount();
|
||||
}
|
||||
|
||||
public async Task LogInformation(string message)
|
||||
@@ -1953,46 +1945,6 @@ public class TradingBotBase : ITradingBot
|
||||
return isInCooldown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a TradeExecutedEvent to the platform summary grain
|
||||
/// </summary>
|
||||
/// <param name="trade">The trade that was executed</param>
|
||||
/// <param name="position">The position this trade belongs to</param>
|
||||
/// <param name="signalIdentifier">The signal identifier</param>
|
||||
/// <param name="pnl">The PnL for this trade</param>
|
||||
private async Task PublishTradeExecutedEventAsync(Trade trade, Position position, string signalIdentifier, decimal pnl)
|
||||
{
|
||||
if (Config.IsForBacktest)
|
||||
{
|
||||
return; // Skip notifications for backtest
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
||||
{
|
||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||
var tradeExecutedEvent = new TradeExecutedEvent
|
||||
{
|
||||
TradeId = Guid.NewGuid(), // Generate new ID for the event
|
||||
PositionId = position.Identifier,
|
||||
StrategyId = position.InitiatorIdentifier,
|
||||
Ticker = position.Ticker,
|
||||
Volume = trade.Price * trade.Quantity * trade.Leverage,
|
||||
PnL = pnl,
|
||||
Fee = trade.Fee,
|
||||
Direction = trade.Direction
|
||||
};
|
||||
await platformGrain.OnTradeExecutedAsync(tradeExecutedEvent);
|
||||
Logger.LogDebug("Published TradeExecutedEvent for trade {TradeId} in position {PositionId}", tradeExecutedEvent.TradeId, position.Identifier);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to publish TradeExecutedEvent for position {PositionId}", position.Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the trade that was used to close the position
|
||||
/// </summary>
|
||||
@@ -2013,12 +1965,14 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
return position.TakeProfit2;
|
||||
}
|
||||
|
||||
|
||||
// If no specific closing trade is found, create a synthetic one based on the position
|
||||
// This handles cases where the position was closed manually or by the exchange
|
||||
if (position.ProfitAndLoss?.Realized != null)
|
||||
{
|
||||
var closeDirection = position.OriginDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long;
|
||||
var closeDirection = position.OriginDirection == TradeDirection.Long
|
||||
? TradeDirection.Short
|
||||
: TradeDirection.Long;
|
||||
return new Trade(
|
||||
DateTime.UtcNow,
|
||||
closeDirection,
|
||||
@@ -2032,49 +1986,18 @@ public class TradingBotBase : ITradingBot
|
||||
"Position closed"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the platform summary grain about active strategy count changes
|
||||
/// </summary>
|
||||
private async Task NotifyPlatformSummaryAboutStrategyCount()
|
||||
{
|
||||
if (Config.IsForBacktest)
|
||||
{
|
||||
return; // Skip notifications for backtest
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
||||
{
|
||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||
|
||||
// Get current active strategy count from the platform
|
||||
var currentSummary = await platformGrain.GetPlatformSummaryAsync();
|
||||
var currentActiveCount = currentSummary.TotalActiveStrategies;
|
||||
|
||||
// Update the count (this will trigger a refresh if needed)
|
||||
await platformGrain.UpdateActiveStrategyCountAsync(currentActiveCount);
|
||||
|
||||
Logger.LogDebug("Notified platform summary about strategy count: {Count}", currentActiveCount);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to notify platform summary about strategy count");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies both AgentGrain and PlatformSummaryGrain about bot events
|
||||
/// </summary>
|
||||
/// <param name="eventType">The type of event (e.g., PositionOpened, PositionClosed)</param>
|
||||
/// <param name="additionalData">Optional additional context data</param>
|
||||
/// <param name="position">Optional position data for platform summary events</param>
|
||||
private async Task NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType eventType, string additionalData = null, Position position = null)
|
||||
private async Task NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType eventType, string additionalData = null,
|
||||
Position position = null)
|
||||
{
|
||||
if (Config.IsForBacktest)
|
||||
{
|
||||
@@ -2092,7 +2015,6 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
var updateEvent = new AgentSummaryUpdateEvent
|
||||
{
|
||||
UserId = Account.User.Id,
|
||||
BotId = Identifier,
|
||||
EventType = eventType,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
@@ -2105,24 +2027,35 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
// Notify PlatformSummaryGrain (platform-wide metrics)
|
||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case AgentSummaryEventType.PositionOpened when position != null:
|
||||
// Position opening is now handled by TradeExecutedEvent in PublishTradeExecutedEventAsync
|
||||
Logger.LogDebug("Position opened notification sent via TradeExecutedEvent for position {PositionId}", position.Identifier);
|
||||
Logger.LogDebug(
|
||||
"Position opened notification sent via TradeExecutedEvent for position {PositionId}",
|
||||
position.Identifier);
|
||||
var positionOpenEvent = new PositionOpenEvent
|
||||
{
|
||||
PositionIdentifier = position.Identifier,
|
||||
Ticker = position.Ticker,
|
||||
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
|
||||
Fee = position.Open.Fee
|
||||
};
|
||||
await platformGrain.OnPositionOpenAsync(positionOpenEvent);
|
||||
break;
|
||||
|
||||
case AgentSummaryEventType.PositionClosed when position != null:
|
||||
var positionClosedEvent = new PositionClosedEvent
|
||||
{
|
||||
PositionId = position.Identifier,
|
||||
PositionIdentifier = position.Identifier,
|
||||
Ticker = position.Ticker,
|
||||
RealizedPnL = position.ProfitAndLoss?.Realized ?? 0,
|
||||
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
|
||||
};
|
||||
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||
Logger.LogDebug("Sent platform position closed notification for position {PositionId}", position.Identifier);
|
||||
Logger.LogDebug("Sent platform position closed notification for position {PositionId}",
|
||||
position.Identifier);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -2132,5 +2065,4 @@ public class TradingBotBase : ITradingBot
|
||||
Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user