diff --git a/src/Managing.Application.Abstractions/Services/IMessengerService.cs b/src/Managing.Application.Abstractions/Services/IMessengerService.cs index 78df21ee..904f7bc5 100644 --- a/src/Managing.Application.Abstractions/Services/IMessengerService.cs +++ b/src/Managing.Application.Abstractions/Services/IMessengerService.cs @@ -27,4 +27,5 @@ public interface IMessengerService Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate); Task SendBacktestNotification(Backtest backtest); Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness, object? bestChromosome); + Task SendClosedPosition(Position position, User user); } \ No newline at end of file diff --git a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs index 7823934c..28bb2222 100644 --- a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs +++ b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs @@ -859,18 +859,18 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable try { var message = isSuccess - ? $"๐Ÿ”„ **Automatic Swap Successful**\n\n" + - $"๐ŸŽฏ **Bot:** {_tradingBot?.Identifier}\n" + - $"๐Ÿ’ฐ **Amount:** {amount} USDC โ†’ ETH\n" + - $"โœ… **Status:** Success\n" + - $"๐Ÿ”— **Transaction:** {transactionHash}\n" + - $"โฐ **Time:** {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC" - : $"โŒ **Automatic Swap Failed**\n\n" + - $"๐ŸŽฏ **Bot:** {_tradingBot?.Identifier}\n" + - $"๐Ÿ’ฐ **Amount:** {amount} USDC โ†’ ETH\n" + - $"โŒ **Status:** Failed\n" + - $"โš ๏ธ **Error:** {errorMessage}\n" + - $"โฐ **Time:** {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC"; + ? $"๐Ÿ”„ Automatic Swap Successful\n\n" + + $"๐ŸŽฏ Bot: {_tradingBot?.Identifier}\n" + + $"๐Ÿ’ฐ Amount: {amount} USDC โ†’ ETH\n" + + $"โœ… Status: Success\n" + + $"๐Ÿ”— Transaction: {transactionHash}\n" + + $"โฐ Time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC" + : $"โŒ Automatic Swap Failed\n\n" + + $"๐ŸŽฏ Bot: {_tradingBot?.Identifier}\n" + + $"๐Ÿ’ฐ Amount: {amount} USDC โ†’ ETH\n" + + $"โŒ Status: Failed\n" + + $"โš ๏ธ Error: {errorMessage}\n" + + $"โฐ Time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC"; // Send notification via webhook service await ServiceScopeHelpers.WithScopedService(_scopeFactory, diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index 88f57704..b0cd15eb 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -82,16 +82,16 @@ public class TradingBotBase : ITradingBot { case BotStatus.Saved: var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList(); - var startupMessage = $"๐Ÿš€ **Bot Started Successfully!**\n\n" + - $"๐Ÿ“Š **Trading Setup:**\n" + + var startupMessage = $"๐Ÿš€ Bot Started Successfully\n\n" + + $"๐Ÿ“Š Trading Setup:\n" + $"๐ŸŽฏ Ticker: `{Config.Ticker}`\n" + $"โฐ Timeframe: `{Config.Timeframe}`\n" + $"๐ŸŽฎ Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" + $"๐Ÿ’ฐ Balance: `${Config.BotTradingBalance:F2}`\n" + $"๐Ÿ‘€ Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" + - $"๐Ÿ“ˆ **Active Indicators:** `{string.Join(", ", indicatorNames)}`\n\n" + - $"โœ… Ready to monitor signals and execute trades!\n" + - $"๐Ÿ“ข I'll notify you when signals are triggered."; + $"๐Ÿ“ˆ Active Indicators: `{string.Join(", ", indicatorNames)}`\n\n" + + $"โœ… Ready to monitor signals and execute trades\n" + + $"๐Ÿ“ข Notifications will be sent when positions are triggered"; await LogInformation(startupMessage); break; @@ -101,7 +101,7 @@ public class TradingBotBase : ITradingBot case BotStatus.Stopped: // If status was Stopped we log a message to inform the user that the bot is restarting - await LogInformation($"๐Ÿ”„ **Bot Restarted**\n" + + await LogInformation($"๐Ÿ”„ Bot Restarted\n" + $"๐Ÿ“Š Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" + $"โœ… Ready to continue trading"); break; @@ -264,7 +264,7 @@ public class TradingBotBase : ITradingBot Signals.Add(recreatedSignal.Identifier, recreatedSignal); await LogInformation( - $"๐Ÿ” **Signal Recovery Success**\nRecreated signal: `{recreatedSignal.Identifier}`\nFor position: `{position.Identifier}`"); + $"๐Ÿ” Signal Recovery Success\nRecreated signal: `{recreatedSignal.Identifier}`\nFor position: `{position.Identifier}`"); return recreatedSignal; } catch (Exception ex) @@ -283,7 +283,7 @@ public class TradingBotBase : ITradingBot if (signalForPosition == null) { await LogInformation( - $"๐Ÿ” **Signal Recovery**\nSignal not found for position `{position.Identifier}`\nRecreating signal from position data..."); + $"๐Ÿ” Signal Recovery\nSignal not found for position `{position.Identifier}`\nRecreating signal from position data..."); // Recreate the signal based on position information signalForPosition = await RecreateSignalFromPosition(position); @@ -299,7 +299,7 @@ public class TradingBotBase : ITradingBot if (signalForPosition.Status != SignalStatus.PositionOpen && position.Status != PositionStatus.Finished) { await LogInformation( - $"๐Ÿ”„ **Signal Status Update**\nSignal: `{signalForPosition.Identifier}`\nStatus: `{signalForPosition.Status}` โ†’ `PositionOpen`"); + $"๐Ÿ”„ Signal Status Update\nSignal: `{signalForPosition.Identifier}`\nStatus: `{signalForPosition.Status}` โ†’ `PositionOpen`"); SetSignalStatus(signalForPosition.Identifier, SignalStatus.PositionOpen); } @@ -315,7 +315,7 @@ public class TradingBotBase : ITradingBot if (LastCandle != null && signal.Date < LastCandle.Date) { await LogWarning( - $"โŒ **Signal Expired**\nSignal `{signal.Identifier}` is older than last candle `{LastCandle.Date}`\nStatus: `Expired`"); + $"โŒ Signal Expired\nSignal `{signal.Identifier}` is older than last candle `{LastCandle.Date}`\nStatus: `Expired`"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); continue; } @@ -327,7 +327,7 @@ public class TradingBotBase : ITradingBot { // Position already exists for this signal, update signal status await LogInformation( - $"๐Ÿ”„ **Signal Status Update**\nSignal: `{signal.Identifier}`\nStatus: `{signal.Status}` โ†’ `PositionOpen`\nPosition already exists: `{existingPosition.Identifier}`"); + $"๐Ÿ”„ Signal Status Update\nSignal: `{signal.Identifier}`\nStatus: `{signal.Status}` โ†’ `PositionOpen`\nPosition already exists: `{existingPosition.Identifier}`"); SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); continue; } @@ -342,7 +342,7 @@ public class TradingBotBase : ITradingBot else { await LogWarning( - $"โš ๏ธ **Position Creation Failed**\nSignal: `{signal.Identifier}`\nPosition creation returned null"); + $"โš ๏ธ Position Creation Failed\nSignal: `{signal.Identifier}`\nPosition creation returned null"); } } } @@ -486,7 +486,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..."); + $"โš ๏ธ Order Cleanup\nToo many open orders: `{orders.Count()}`\nPosition: `{positionForSignal.Identifier}`\nTime elapsed: `{waitTimeMinutes}min`\nCanceling all orders..."); try { await ServiceScopeHelpers.WithScopedService(_scopeFactory, @@ -495,7 +495,7 @@ public class TradingBotBase : ITradingBot await exchangeService.CancelOrder(Account, Config.Ticker); }); await LogInformation( - $"โœ… **Orders Canceled**\nSuccessfully canceled all orders for: `{Config.Ticker}`"); + $"โœ… Orders Canceled\nSuccessfully canceled all orders for: `{Config.Ticker}`"); } catch (Exception ex) { @@ -510,7 +510,7 @@ public class TradingBotBase : ITradingBot { var remainingMinutes = waitTimeMinutes - timeSinceRequest.TotalMinutes; await LogInformation( - $"โณ **Waiting for Orders**\nPosition has `{orders.Count()}` open orders\nElapsed: `{timeSinceRequest.TotalMinutes:F1}min`\nWaiting `{remainingMinutes:F1}min` more before canceling"); + $"โณ Waiting for Orders\nPosition has `{orders.Count()}` open orders\nElapsed: `{timeSinceRequest.TotalMinutes:F1}min`\nWaiting `{remainingMinutes:F1}min` more before canceling"); } } else if (ordersCount == 2) @@ -518,7 +518,7 @@ public class TradingBotBase : ITradingBot // TODO: This should never happen, but just in case // 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..."); + $"๐Ÿ” Checking Broker Position\nPosition has exactly `{orders.Count()}` open orders\nChecking if position is already open on broker..."); Position brokerPosition = null; await ServiceScopeHelpers.WithScopedService(_scopeFactory, @@ -531,7 +531,7 @@ public class TradingBotBase : ITradingBot if (brokerPosition != null) { await LogInformation( - $"โœ… **Position Found on Broker**\nPosition is already open on broker\nUpdating position status to Filled"); + $"โœ… Position Found on Broker\nPosition is already open on broker\nUpdating position status to Filled"); // Calculate net PnL after fees for broker position var brokerNetPnL = brokerPosition.GetNetPnL(); @@ -564,19 +564,19 @@ public class TradingBotBase : ITradingBot else { await LogInformation( - $"โธ๏ธ **Position Pending**\nPosition still waiting to open\n`{orders.Count()}` open orders remaining"); + $"โธ๏ธ Position Pending\nPosition still waiting to open\n`{orders.Count()}` open orders remaining"); } } else { await LogInformation( - $"โธ๏ธ **Position Pending**\nPosition still waiting to open\n`{orders.Count()}` open orders remaining"); + $"โธ๏ธ Position Pending\nPosition still waiting to open\n`{orders.Count()}` open orders remaining"); } } else { 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 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 @@ -622,7 +622,7 @@ public class TradingBotBase : ITradingBot var profitStatus = isPositionInProfit ? "in profit" : "at a loss"; await LogInformation( - $"โฐ **Time Limit Close**\nClosing position due to time limit: `{Config.MaxPositionTimeHours}h` exceeded\n๐Ÿ“ˆ Position Status: **{profitStatus}**\n๐Ÿ’ฐ Entry: `${positionForSignal.Open.Price}` โ†’ Current: `${lastCandle.Close}`\n๐Ÿ“Š Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)"); + $"โฐ Time Limit Close\nClosing position due to time limit: `{Config.MaxPositionTimeHours}h` exceeded\n๐Ÿ“ˆ Position Status: {profitStatus}\n๐Ÿ’ฐ Entry: `${positionForSignal.Open.Price}` โ†’ Current: `${lastCandle.Close}`\n๐Ÿ“Š Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)"); await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true); return; } @@ -651,7 +651,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.StopLoss.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, executionPrice, true); } @@ -671,7 +671,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit1.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, executionPrice, positionForSignal.TakeProfit2 == null); } @@ -690,7 +690,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit2.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, executionPrice, true); } @@ -717,7 +717,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.StopLoss.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, executionPrice, true); } @@ -737,7 +737,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit1.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, executionPrice, positionForSignal.TakeProfit2 == null); } @@ -756,7 +756,7 @@ 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: `${executionPrice:F2}` (was `${positionForSignal.TakeProfit2.Price:F2}`)"); await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, executionPrice, true); } @@ -845,7 +845,7 @@ public class TradingBotBase : ITradingBot if (openedPosition.OriginDirection == signal.Direction) { await LogInformation( - $"๐Ÿ“ **Same Direction Signal**\nSignal `{signal.Identifier}` tried to open position\nBut `{previousSignal.Identifier}` already open for same direction"); + $"๐Ÿ“ Same Direction Signal\nSignal `{signal.Identifier}` tried to open position\nBut `{previousSignal.Identifier}` already open for same direction"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); return null; } @@ -863,19 +863,19 @@ public class TradingBotBase : ITradingBot : "FlipOnlyWhenInProfit is disabled"; await LogInformation( - $"๐Ÿ”„ **Position Flip Initiated**\nFlipping position due to opposite signal\nReason: {flipReason}"); + $"๐Ÿ”„ Position Flip Initiated\nFlipping position due to opposite signal\nReason: {flipReason}"); await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped); var newPosition = await OpenPosition(signal); await LogInformation( - $"โœ… **Position Flipped**\nPosition: `{previousSignal.Identifier}` โ†’ `{signal.Identifier}`\nPrice: `${lastPrice}`"); + $"โœ… Position Flipped\nPosition: `{previousSignal.Identifier}` โ†’ `{signal.Identifier}`\nPrice: `${lastPrice}`"); return newPosition; } else { var currentPnl = openedPosition.ProfitAndLoss?.Realized ?? 0; await LogInformation( - $"๐Ÿ’ธ **Flip Blocked - Not Profitable**\nPosition `{previousSignal.Identifier}` PnL: `${currentPnl:F2}`\nSignal `{signal.Identifier}` will wait for profitability"); + $"๐Ÿ’ธ Flip Blocked - Not Profitable\nPosition `{previousSignal.Identifier}` PnL: `${currentPnl:F2}`\nSignal `{signal.Identifier}` will wait for profitability"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); return null; @@ -884,7 +884,7 @@ public class TradingBotBase : ITradingBot else { await LogInformation( - $"๐Ÿšซ **Flip Disabled**\nPosition already open for: `{previousSignal.Identifier}`\nFlipping disabled, new signal expired"); + $"๐Ÿšซ Flip Disabled\nPosition already open for: `{previousSignal.Identifier}`\nFlipping disabled, new signal expired"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); return null; } @@ -899,9 +899,6 @@ public class TradingBotBase : ITradingBot return null; } - await LogInformation( - $"๐Ÿš€ **Opening Position**\nTime: `{signal.Date:HH:mm:ss}`\nSignal: `{signal.Identifier}`"); - try { var command = new OpenPositionRequest( @@ -936,7 +933,7 @@ public class TradingBotBase : ITradingBot if (!Config.IsForBacktest) { await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async messengerService => { await messengerService.SendPosition(position); }); + async messengerService => { await messengerService.SendClosedPosition(position, Account.User); }); } Logger.LogInformation($"Position requested"); @@ -979,7 +976,7 @@ public class TradingBotBase : ITradingBot // TODO : check if its a startup cycle if (!Config.IsForBacktest && ExecutionCount == 0) { - await LogInformation("โณ **Bot Not Ready**\nCannot open position\nBot hasn't executed first cycle yet"); + await LogInformation("โณ Bot Not Ready\nCannot open position\nBot hasn't executed first cycle yet"); return false; } @@ -1059,7 +1056,7 @@ public class TradingBotBase : ITradingBot if (lastPosition.OriginDirection == signal.Direction) { await LogWarning( - $"๐Ÿ”ฅ **Loss Streak Limit**\nCannot open position\nMax loss streak: `{Config.MaxLossStreak}` reached\n๐Ÿ“‰ Last `{recentPositions.Count}` trades were losses\n๐ŸŽฏ Last position: `{lastPosition.OriginDirection}`\nWaiting for opposite direction signal"); + $"๐Ÿ”ฅ Loss Streak Limit\nCannot open position\nMax loss streak: `{Config.MaxLossStreak}` reached\n๐Ÿ“‰ Last `{recentPositions.Count}` trades were losses\n๐ŸŽฏ Last position: `{lastPosition.OriginDirection}`\nWaiting for opposite direction signal"); return false; } @@ -1104,7 +1101,7 @@ public class TradingBotBase : ITradingBot } catch (Exception ex) { - await LogWarning($"โŒ **Broker Position Check Failed**\nError checking broker positions\n{ex.Message}"); + await LogWarning($"โŒ Broker Position Check Failed\nError checking broker positions\n{ex.Message}"); return false; } } @@ -1120,7 +1117,7 @@ public class TradingBotBase : ITradingBot } await LogInformation( - $"๐Ÿ”ง **Closing Trade**\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n๐Ÿ“‹ Type: `{tradeToClose.TradeType}`\n๐Ÿ“Š Quantity: `{tradeToClose.Quantity}`\n๐ŸŽฏ Closing Position: `{(tradeClosingPosition ? "Yes" : "No")}`"); + $"๐Ÿ”ง Closing Trade\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n๐Ÿ“‹ Type: `{tradeToClose.TradeType}`\n๐Ÿ“Š Quantity: `{tradeToClose.Quantity:F5}`"); decimal quantity = 0; @@ -1202,7 +1199,7 @@ public class TradingBotBase : ITradingBot try { Logger.LogInformation( - $"๐Ÿ” **Fetching Position History from GMX**\nPosition: `{position.Identifier}`\nTicker: `{Config.Ticker}`"); + $"๐Ÿ” Fetching Position History from GMX\nPosition: `{position.Identifier}`\nTicker: `{Config.Ticker}`"); List positionHistory = null; await ServiceScopeHelpers.WithScopedService(_scopeFactory, @@ -1225,7 +1222,7 @@ public class TradingBotBase : ITradingBot if (gmxPosition != null && gmxPosition.ProfitAndLoss != null) { Logger.LogInformation( - $"โœ… **GMX Position History Found**\n" + + $"โœ… GMX Position History Found\n" + $"Position: `{position.Identifier}`\n" + $"GMX Realized PnL (after fees): `${gmxPosition.ProfitAndLoss.Realized:F2}`\n" + $"Bot's UI Fees: `${position.UiFees:F2}`\n" + @@ -1289,7 +1286,7 @@ public class TradingBotBase : ITradingBot } Logger.LogInformation( - $"๐Ÿ“Š **Position Reconciliation Complete**\n" + + $"๐Ÿ“Š Position Reconciliation Complete\n" + $"Position: `{position.Identifier}`\n" + $"Closing Price: `${closingPrice:F2}`\n" + $"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" + @@ -1302,7 +1299,7 @@ public class TradingBotBase : ITradingBot } Logger.LogWarning( - $"โš ๏ธ **No GMX Position History Found**\nPosition: `{position.Identifier}`\nFalling back to candle-based calculation"); + $"โš ๏ธ No GMX Position History Found\nPosition: `{position.Identifier}`\nFalling back to candle-based calculation"); } catch (Exception ex) { @@ -1406,7 +1403,7 @@ public class TradingBotBase : ITradingBot } Logger.LogInformation( - $"๐Ÿ›‘ **Stop Loss Execution Confirmed**\n" + + $"๐Ÿ›‘ Stop Loss Execution Confirmed\n" + $"Position: `{position.Identifier}`\n" + $"SL Price: `${closingPrice:F2}` was hit (was `${position.StopLoss.Price:F2}`)\n" + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`"); @@ -1429,7 +1426,7 @@ public class TradingBotBase : ITradingBot } Logger.LogInformation( - $"๐ŸŽฏ **Take Profit Execution Confirmed**\n" + + $"๐ŸŽฏ Take Profit Execution Confirmed\n" + $"Position: `{position.Identifier}`\n" + $"TP Price: `${closingPrice:F2}` was hit (was `${position.TakeProfit1.Price:F2}`)\n" + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`"); @@ -1480,7 +1477,7 @@ public class TradingBotBase : ITradingBot } Logger.LogInformation( - $"โœ‹ **Manual/Exchange Close Detected**\n" + + $"โœ‹ Manual/Exchange Close Detected\n" + $"Position: `{position.Identifier}`\n" + $"SL: `${position.StopLoss.Price:F2}` | TP: `${position.TakeProfit1.Price:F2}`\n" + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`\n" + @@ -1549,21 +1546,21 @@ public class TradingBotBase : ITradingBot if (position.Open?.Status == TradeStatus.Filled) { Logger.LogInformation( - $"โœ… **Position Closed Successfully**\nPosition: `{position.SignalIdentifier}`\nPnL: `${position.ProfitAndLoss?.Realized:F2}`"); + $"โœ… Position Closed Successfully\nPosition: `{position.SignalIdentifier}`\nPnL: `${position.ProfitAndLoss?.Realized:F2}`"); if (position.ProfitAndLoss != null) { Config.BotTradingBalance += position.ProfitAndLoss.Realized; Logger.LogInformation( - string.Format("๐Ÿ’ฐ **Balance Updated**\nNew bot trading balance: `${0:F2}`", + string.Format("๐Ÿ’ฐ Balance Updated\nNew bot trading balance: `${0:F2}`", Config.BotTradingBalance)); } } else { Logger.LogInformation( - $"โœ… **Position Cleanup**\nPosition: `{position.SignalIdentifier}` was never filled - no balance or PnL changes"); + $"โœ… Position Cleanup\nPosition: `{position.SignalIdentifier}` was never filled - no balance or PnL changes"); } } else @@ -1574,10 +1571,9 @@ public class TradingBotBase : ITradingBot if (!Config.IsForBacktest) { await ServiceScopeHelpers.WithScopedService(_scopeFactory, - messengerService => + async messengerService => { - messengerService.SendClosingPosition(position); - return Task.CompletedTask; + await messengerService.SendClosedPosition(position, Account.User); }); } @@ -1646,7 +1642,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}`"); + $"๐Ÿ“Š Position Status Change\nPosition: `{position.Ticker}`\nDirection: `{position.OriginDirection}`\nNew Status: `{positionStatus}`"); // Update Open trade status when position becomes Filled if (positionStatus == PositionStatus.Filled && position.Open != null) @@ -1735,7 +1731,7 @@ public class TradingBotBase : ITradingBot { Config.IsForWatchingOnly = !Config.IsForWatchingOnly; await LogInformation( - $"๐Ÿ”„ **Watch Mode Toggle**\nBot: `{Config.Name}`\nWatch Only: `{(Config.IsForWatchingOnly ? "ON" : "OFF")}`"); + $"๐Ÿ”„ Watch Mode Toggle\nBot: `{Config.Name}`\nWatch Only: `{(Config.IsForWatchingOnly ? "ON" : "OFF")}`"); } /// @@ -1743,7 +1739,7 @@ public class TradingBotBase : ITradingBot /// public async Task StopBot() { - await LogInformation($"๐Ÿ›‘ **Bot Stopped**\nBot: `{Config.Name}`\nTicker: `{Config.Ticker}`"); + await LogInformation($"๐Ÿ›‘ Bot Stopped\nBot: `{Config.Name}`\nTicker: `{Config.Ticker}`"); } public async Task LogInformation(string message) @@ -1783,7 +1779,7 @@ public class TradingBotBase : ITradingBot if (!Config.IsForBacktest) { var user = Account.User; - var messageWithBotName = $"๐Ÿค– **{user.AgentName} - {Config.Name}**\n{message}"; + var messageWithBotName = $"๐Ÿค– {user.AgentName} - {Config.Name}\n{message}"; await ServiceScopeHelpers.WithScopedService(_scopeFactory, async messengerService => { @@ -1831,7 +1827,7 @@ public class TradingBotBase : ITradingBot Positions[position.Identifier] = position; Logger.LogInformation( - $"๐Ÿ‘ค **Manual Position Opened**\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`\nAdded to positions list"); + $"๐Ÿ‘ค Manual Position Opened\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`\nAdded to positions list"); return position; } @@ -1846,8 +1842,8 @@ public class TradingBotBase : ITradingBot } var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList(); - var signalText = $"๐ŸŽฏ **New Trading Signal**\n\n" + - $"๐Ÿ“Š **Signal Details:**\n" + + var signalText = $"๐ŸŽฏ New Trading Signal\n\n" + + $"๐Ÿ“Š Signal Details:\n" + $"๐Ÿ“ˆ Action: `{signal.Direction}` {Config.Ticker}\n" + $"โฐ Timeframe: `{Config.Timeframe}`\n" + $"๐ŸŽฏ Confidence: `{signal.Confidence}`\n" + @@ -2144,13 +2140,13 @@ public class TradingBotBase : ITradingBot // Only log if there are actual changes if (changes.Any()) { - var changeMessage = "โš™๏ธ **Configuration Updated**\n" + string.Join("\n", changes); + var changeMessage = "โš™๏ธ Configuration Updated\n" + string.Join("\n", changes); await LogInformation(changeMessage); } else { await LogInformation( - "โš™๏ธ **Configuration Update**\nโœ… No changes detected - configuration already up to date"); + "โš™๏ธ Configuration Update\nโœ… No changes detected - configuration already up to date"); } return true; @@ -2227,7 +2223,7 @@ public class TradingBotBase : ITradingBot var remainingTime = cooldownEndTime - LastCandle.Date; Logger.LogWarning( - $"โณ **Cooldown Period Active**\n" + + $"โณ Cooldown Period Active\n" + $"Cannot open new positions\n" + $"Last position closed: `{LastPositionClosingTime:HH:mm:ss}`\n" + $"Cooldown period: `{Config.CooldownPeriod}` candles\n" + diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 815b6797..74cd937e 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -76,11 +76,11 @@ namespace Managing.Application.ManageBot await _botRepository.DeleteBot(identifier); await grain.DeleteAsync(); - var deleteMessage = $"๐Ÿ—‘๏ธ **Bot Deleted**\n\n" + - $"๐ŸŽฏ **Agent:** {account.User.AgentName}\n" + - $"๐Ÿค– **Bot Name:** {config.Name}\n" + - $"โฐ **Deleted At:** {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + - $"โš ๏ธ **Bot has been permanently deleted and all data removed.**"; + var deleteMessage = $"๐Ÿ—‘๏ธ Bot Deleted\n\n" + + $"๐ŸŽฏ Agent: {account.User.AgentName}\n" + + $"๐Ÿค– Bot Name: {config.Name}\n" + + $"โฐ Deleted At: {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + + $"โš ๏ธ Bot has been permanently deleted and all data removed"; await _messengerService.SendTradeMessage(deleteMessage, false, account.User); return true; @@ -139,12 +139,12 @@ namespace Managing.Application.ManageBot { // First time startup await botGrain.StartAsync(); - var startupMessage = $"๐Ÿš€ **Bot Started**\n\n" + - $"๐ŸŽฏ **Agent:** {account.User.AgentName}\n" + - $"๐Ÿค– **Bot Name:** {grainState.Config.Name}\n" + - $"โฐ **Started At:** {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n" + - $"๐Ÿ• **Startup Time:** {grainState.StartupTime:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + - $"โœ… **Bot has been successfully started and is now active.**"; + var startupMessage = $"๐Ÿš€ Bot Started\n\n" + + $"๐ŸŽฏ Agent: {account.User.AgentName}\n" + + $"๐Ÿค– Bot Name: {grainState.Config.Name}\n" + + $"โฐ Started At: {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n" + + $"๐Ÿ• Startup Time: {grainState.StartupTime:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + + $"โœ… Bot has been successfully started and is now active"; await _messengerService.SendTradeMessage(startupMessage, false, account.User); } @@ -152,12 +152,12 @@ namespace Managing.Application.ManageBot { // Restart (bot was previously down) await botGrain.RestartAsync(); - var restartMessage = $"๐Ÿ”„ **Bot Restarted**\n\n" + - $"๐ŸŽฏ **Agent:** {account.User.AgentName}\n" + - $"๐Ÿค– **Bot Name:** {grainState.Config.Name}\n" + - $"โฐ **Restarted At:** {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n" + - $"๐Ÿ• **New Startup Time:** {grainState.StartupTime:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + - $"๐Ÿš€ **Bot has been successfully restarted and is now active.**"; + var restartMessage = $"๐Ÿ”„ Bot Restarted\n\n" + + $"๐ŸŽฏ Agent: {account.User.AgentName}\n" + + $"๐Ÿค– Bot Name: {grainState.Config.Name}\n" + + $"โฐ Restarted At: {DateTime.UtcNow:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n" + + $"๐Ÿ• New Startup Time: {grainState.StartupTime:MMM dd, yyyy โ€ข HH:mm:ss} UTC\n\n" + + $"๐Ÿš€ Bot has been successfully restarted and is now active"; await _messengerService.SendTradeMessage(restartMessage, false, account.User); } diff --git a/src/Managing.Application/Shared/MessengerService.cs b/src/Managing.Application/Shared/MessengerService.cs index c3d988fc..92249582 100644 --- a/src/Managing.Application/Shared/MessengerService.cs +++ b/src/Managing.Application/Shared/MessengerService.cs @@ -81,6 +81,22 @@ public class MessengerService : IMessengerService } } + public async Task SendClosedPosition(Position position, User user) + { + var message = BuildClosePositionMessage(position); + await _discordService.SendMessage(message); + await _webhookService.SendMessage(message, user.TelegramChannel); + } + + private string BuildClosePositionMessage(Position position) + { + return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" + + $"Open Price : {position.Open.Price} \n" + + $"Closing Price : {position.Open.Price} \n" + + $"Quantity :{position.Open.Quantity} \n" + + $"PNL : {position.ProfitAndLoss.Realized} $"; + } + public async Task SendMessage(string message, string telegramChannel) { await _webhookService.SendMessage(message, telegramChannel); @@ -91,27 +107,28 @@ public class MessengerService : IMessengerService var direction = position.OriginDirection.ToString(); var status = position.Status.ToString(); - var message = $"๐ŸŽฏ Position {status}\n" + + var message = $"๐ŸŽฏ {status} Position \n" + $"Symbol: {position.Ticker}\n" + $"Direction: {direction}\n" + - $"Identifier: {position.Identifier}\n" + - $"Initiator: {position.Initiator}\n" + $"Date: {position.Date:yyyy-MM-dd HH:mm:ss}"; if (position.Open != null) { - message += $"\nOpen Trade: {position.Open.Quantity} @ {position.Open.Price:F4}"; + message += $"\nOpen Trade: {position.Open.Quantity:F5} @ {position.Open.Price:F5}"; + } + + if (position.StopLoss != null){ + message += $"\nStop Loss: {position.StopLoss.Quantity:F5} @ {position.StopLoss.Price:F5}"; + } + + if (position.TakeProfit1 != null){ + message += $"\nTake Profit 1: {position.TakeProfit1.Quantity:F5} @ {position.TakeProfit1.Price:F5}"; } if (position.ProfitAndLoss != null) { var pnlEmoji = position.ProfitAndLoss.Realized >= 0 ? "โœ…" : "โŒ"; - message += $"\nPnL: {pnlEmoji} ${position.ProfitAndLoss.Realized:F2}"; - } - - if (!string.IsNullOrEmpty(position.SignalIdentifier)) - { - message += $"\nSignal: {position.SignalIdentifier}"; + message += $"\nPnL: {pnlEmoji} ${position.ProfitAndLoss.Realized:F5}"; } return message; @@ -194,47 +211,6 @@ public class MessengerService : IMessengerService } } - private string BuildBacktestConfigMessage(Backtest backtest) - { - var config = backtest.Config; - - // Get indicators list as comma-separated string - var indicators = config.Scenario?.Indicators != null && config.Scenario.Indicators.Any() - ? string.Join(", ", config.Scenario.Indicators.Select(i => i.Type.ToString())) - : "N/A"; - - // MoneyManagement summary - var mmSl = config.MoneyManagement != null ? (config.MoneyManagement.StopLoss * 100).ToString("F2") : "N/A"; - var mmTp = config.MoneyManagement != null ? (config.MoneyManagement.TakeProfit * 100).ToString("F2") : "N/A"; - var mmLev = config.MoneyManagement != null ? config.MoneyManagement.Leverage.ToString("F2") : "N/A"; - - return $"๐Ÿš€ Excellent Backtest Results! ๐Ÿš€\n\n" + - $"๐Ÿ”น {config.Ticker} | โฑ๏ธ {config.Timeframe}\n" + - $"๐Ÿ’ฐ {config.BotTradingBalance:C}\n" + - $"๐Ÿ›ก๏ธ SL: {mmSl}% | ๐ŸŽฏ TP: {mmTp}% | ๐Ÿ“ˆ Lev: {mmLev}x\n" + - $"๐Ÿงฉ {indicators}\n" + - $"๐Ÿ“… {backtest.StartDate:yyyy-MM-dd} to {backtest.EndDate:yyyy-MM-dd}"; - } - - private string BuildBacktestResultsMessage(Backtest backtest) - { - var config = backtest.Config; - var score = backtest.Score; - var winRate = backtest.WinRate; - var tradeCount = backtest.Positions?.Count ?? 0; - var finalPnl = backtest.FinalPnl; - var growthPercentage = backtest.GrowthPercentage; - var maxDrawdown = backtest.Statistics?.MaxDrawdownPc ?? 0; - var sharpeRatio = (backtest.Statistics?.SharpeRatio * 100) ?? 0; - - return $"๐Ÿ“ˆ Performance Metrics:\n" + - $"โญ Score: {score:F1}/100 | ๐Ÿ† Win Rate: {winRate:F1}%\n" + - $"๐Ÿ“Š Trades: {tradeCount} | ๐Ÿ’ฐ PnL: ${finalPnl:F2}\n" + - $"๐Ÿ“ˆ Growth: {growthPercentage:F1}% | ๐Ÿ“‰ DD: {maxDrawdown:F1}%\n" + - $"๐Ÿ“Š Sharpe: {sharpeRatio:F2}\n\n" + - $"๐Ÿ†” ID: {backtest.Id}"; - } - private string BuildBacktestMessage(Backtest backtest) { var config = backtest.Config; diff --git a/src/Managing.Application/Synth/SynthApiClient.cs b/src/Managing.Application/Synth/SynthApiClient.cs index c08ca702..648854c0 100644 --- a/src/Managing.Application/Synth/SynthApiClient.cs +++ b/src/Managing.Application/Synth/SynthApiClient.cs @@ -65,7 +65,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable { try { - _logger.LogInformation("๐Ÿ” **Synth API** - Fetching leaderboard"); + _logger.LogInformation("๐Ÿ” Synth API - Fetching leaderboard"); var response = await _httpClient.GetAsync("/leaderboard/latest"); @@ -79,7 +79,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var jsonContent = await response.Content.ReadAsStringAsync(); var miners = JsonSerializer.Deserialize>(jsonContent, _jsonOptions); - _logger.LogInformation($"๐Ÿ“Š **Synth API** - Retrieved {miners?.Count ?? 0} miners from leaderboard"); + _logger.LogInformation($"๐Ÿ“Š Synth API - Retrieved {miners?.Count ?? 0} miners from leaderboard"); return miners ?? new List(); } @@ -118,7 +118,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var url = $"/leaderboard/historical?start_time={startTimeStr}&end_time={endTimeStr}"; - _logger.LogInformation($"๐Ÿ” **Synth API** - Fetching historical leaderboard from {startTime:yyyy-MM-dd HH:mm} to {endTime:yyyy-MM-dd HH:mm}"); + _logger.LogInformation($"๐Ÿ” Synth API - Fetching historical leaderboard from {startTime:yyyy-MM-dd HH:mm} to {endTime:yyyy-MM-dd HH:mm}"); var response = await _httpClient.GetAsync(url); @@ -132,7 +132,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var jsonContent = await response.Content.ReadAsStringAsync(); var miners = JsonSerializer.Deserialize>(jsonContent, _jsonOptions); - _logger.LogInformation($"๐Ÿ“Š **Synth API** - Retrieved {miners?.Count ?? 0} miners from historical leaderboard"); + _logger.LogInformation($"๐Ÿ“Š Synth API - Retrieved {miners?.Count ?? 0} miners from historical leaderboard"); return miners ?? new List(); } @@ -193,7 +193,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var url = $"/prediction/latest?{string.Join("&", queryParams)}"; _logger.LogInformation( - $"๐Ÿ”ฎ **Synth API** - Fetching predictions for {minerUids.Count} miners, asset: {asset}, time: {timeLength}s"); + $"๐Ÿ”ฎ Synth API - Fetching predictions for {minerUids.Count} miners, asset: {asset}, time: {timeLength}s"); var response = await _httpClient.GetAsync(url); @@ -209,7 +209,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var totalPaths = predictions?.Sum(p => p.NumSimulations) ?? 0; _logger.LogInformation( - $"๐Ÿ“ˆ **Synth API** - Retrieved {predictions?.Count ?? 0} predictions with {totalPaths} total simulation paths"); + $"๐Ÿ“ˆ Synth API - Retrieved {predictions?.Count ?? 0} predictions with {totalPaths} total simulation paths"); return predictions ?? new List(); } @@ -275,7 +275,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var url = $"/prediction/historical?{string.Join("&", queryParams)}"; _logger.LogInformation( - $"๐Ÿ”ฎ **Synth API** - Fetching historical predictions for {minerUids.Count} miners, asset: {asset}, time: {startTime:yyyy-MM-dd HH:mm}, duration: {timeLength}s"); + $"๐Ÿ”ฎ Synth API - Fetching historical predictions for {minerUids.Count} miners, asset: {asset}, time: {startTime:yyyy-MM-dd HH:mm}, duration: {timeLength}s"); var response = await _httpClient.GetAsync(url); @@ -291,7 +291,7 @@ public class SynthApiClient : ISynthApiClient, IDisposable var totalPaths = predictions?.Sum(p => p.NumSimulations) ?? 0; _logger.LogInformation( - $"๐Ÿ“ˆ **Synth API** - Retrieved {predictions?.Count ?? 0} historical predictions with {totalPaths} total simulation paths"); + $"๐Ÿ“ˆ Synth API - Retrieved {predictions?.Count ?? 0} historical predictions with {totalPaths} total simulation paths"); return predictions ?? new List(); } diff --git a/src/Managing.Application/Synth/SynthPredictionService.cs b/src/Managing.Application/Synth/SynthPredictionService.cs index f73ad9a1..b54eacce 100644 --- a/src/Managing.Application/Synth/SynthPredictionService.cs +++ b/src/Managing.Application/Synth/SynthPredictionService.cs @@ -123,14 +123,14 @@ public class SynthPredictionService : ISynthPredictionService await GetCachedPredictionsAsync(asset, timeHorizonSeconds, config, false, DateTime.UtcNow); if (!predictions.Any()) { - _logger.LogWarning($"๐Ÿšซ **Synth Probability** - No predictions available for {asset}"); + _logger.LogWarning($"๐Ÿšซ Synth Probability - No predictions available for {asset}"); return 0m; } var probability = CalculateProbabilityFromPaths(predictions, currentPrice, targetPrice, timeHorizonSeconds, isLongPosition); - _logger.LogInformation($"๐ŸŽฏ **Synth Probability** - {asset}\n" + + _logger.LogInformation($"๐ŸŽฏ Synth Probability - {asset}\n" + $"๐Ÿ“Š Target: ${targetPrice:F2} | Current: ${currentPrice:F2}\n" + $"โฑ๏ธ Horizon: {timeHorizonSeconds / 3600.0:F1}h | Position: {(isLongPosition ? "LONG" : "SHORT")}\n" + $"๐ŸŽฒ Probability: {probability:P2}"); @@ -170,7 +170,7 @@ public class SynthPredictionService : ISynthPredictionService await GetCachedPredictionsAsync(asset, timeHorizonSeconds, config, isBacktest, signalDate); if (!predictions.Any()) { - _logger.LogWarning($"๐Ÿšซ **Synth Multi-Threshold** - No predictions available for {asset}"); + _logger.LogWarning($"๐Ÿšซ Synth Multi-Threshold - No predictions available for {asset}"); return results; } @@ -181,7 +181,7 @@ public class SynthPredictionService : ISynthPredictionService results[threshold.Key] = probability; } - _logger.LogInformation($"๐ŸŽฏ **Synth Multi-Threshold** - {asset}\n" + + _logger.LogInformation($"๐ŸŽฏ Synth Multi-Threshold - {asset}\n" + $"๐Ÿ“Š Calculated {results.Count} threshold probabilities\n" + $"โฑ๏ธ Horizon: {timeHorizonSeconds / 3600.0:F1}h | Position: {(isLongPosition ? "LONG" : "SHORT")} {(isBacktest ? "(HISTORICAL)" : "(LIVE)")}"); @@ -200,7 +200,7 @@ public class SynthPredictionService : ISynthPredictionService public async Task ClearCacheAsync() { await _synthRepository.CleanupOldDataAsync(0); // Remove all data - _logger.LogInformation("๐Ÿงน **Synth Cache** - Cleared all cached individual predictions from MongoDB"); + _logger.LogInformation("๐Ÿงน Synth Cache - Cleared all cached individual predictions from MongoDB"); } /// @@ -262,7 +262,7 @@ public class SynthPredictionService : ISynthPredictionService isBacktest, signal.Date); // Debug logging to understand LONG vs SHORT differences - _logger.LogInformation($"๐Ÿ” **Debug Signal Direction Analysis** - {signal.Direction} {signal.Ticker}\n" + + _logger.LogInformation($"๐Ÿ” Debug Signal Direction Analysis - {signal.Direction} {signal.Ticker}\n" + $"๐Ÿ’ฐ Current Price: ${currentPrice:F2}\n" + $"๐Ÿ“Š Price Targets Requested:\n" + $" {string.Join("\n ", priceThresholds.Select(kvp => $"{kvp.Key}: ${kvp.Value:F2}"))}\n" + @@ -355,7 +355,7 @@ public class SynthPredictionService : ISynthPredictionService $"Confidence: {finalConfidence}, Risk: {result.GetUtilityRiskAssessment()}"; _logger.LogInformation( - $"๐ŸŽฏ **Synth Signal Analysis** - {signal.Direction} {signal.Ticker} {(isBacktest ? "(BACKTEST)" : "(LIVE)")}\n" + + $"๐ŸŽฏ Synth Signal Analysis - {signal.Direction} {signal.Ticker} {(isBacktest ? "(BACKTEST)" : "(LIVE)")}\n" + $"๐Ÿ“Š Signal: `{signal.Identifier}`\n" + $"๐Ÿ“Š SL Risk: {slProbability:P2} | TP Probability: {tpProbability:P2}\n" + $"๐ŸŽฒ Ratio: {tpSlRatio:F2}x (TP/SL) | Win/Loss: {result.WinLossRatio:F2}:1\n" + @@ -412,7 +412,7 @@ public class SynthPredictionService : ISynthPredictionService Math.Min(constrainedScore * 0.75m, configLowThreshold + 0.05m); // Small buffer above LOW threshold _logger.LogDebug( - $"๐Ÿšจ **Conditional Pass-Through** - Signal exceeded {result.AdverseProbabilityThreshold:P1} threshold " + + $"๐Ÿšจ Conditional Pass-Through - Signal exceeded {result.AdverseProbabilityThreshold:P1} threshold " + $"(SL: {result.StopLossProbability:P2}) but has redeeming qualities. " + $"Constrained score: {constrainedScore:F4} โ†’ Penalized: {penalizedScore:F4} โ†’ LOW confidence"); @@ -442,7 +442,7 @@ public class SynthPredictionService : ISynthPredictionService }; // Debug logging to understand scoring breakdown - _logger.LogDebug($"๐Ÿงฎ **Config-Aware Confidence** [{riskConfig.RiskTolerance}] - " + + _logger.LogDebug($"๐Ÿงฎ Config-Aware Confidence [{riskConfig.RiskTolerance}] - " + $"Composite: {compositeScore:F4} = " + $"Config({configScore:F4})*0.5 + " + $"Threshold({thresholdAlignmentScore:F4})*0.3 + " + @@ -527,7 +527,7 @@ public class SynthPredictionService : ISynthPredictionService var redeemingPercentage = (decimal)redeemingFactors / totalFactors; _logger.LogDebug( - $"๐Ÿ” **Redeeming Qualities Check** - {redeemingFactors}/{totalFactors} factors positive ({redeemingPercentage:P0}). " + + $"๐Ÿ” Redeeming Qualities Check - {redeemingFactors}/{totalFactors} factors positive ({redeemingPercentage:P0}). " + $"Kelly: {result.KellyFraction:P2} (>2x{riskConfig.KellyMinimumThreshold:P2}), " + $"EMV: ${result.ExpectedMonetaryValue:F2} (>$100), " + $"TP/SL: {result.TpSlRatio:F2}x (>2.0), " + @@ -842,7 +842,7 @@ public class SynthPredictionService : ISynthPredictionService if (liquidationProbability > config.MaxLiquidationProbability) { _logger.LogWarning( - $"๐Ÿšซ **Synth Risk Block**\n" + + $"๐Ÿšซ Synth Risk Block\n" + $"Liquidation probability too high: {liquidationProbability:P2}\n" + $"๐Ÿ“Š Max allowed: {config.MaxLiquidationProbability:P2}\n" + $"๐Ÿ’ฐ Est. liquidation: ${estimatedLiquidationPrice:F2}\n" + @@ -851,7 +851,7 @@ public class SynthPredictionService : ISynthPredictionService } _logger.LogInformation( - $"โœ… **Synth Risk Check**\n" + + $"โœ… Synth Risk Check\n" + $"Liquidation probability acceptable: {liquidationProbability:P2}\n" + $"๐Ÿ“Š Max allowed: {config.MaxLiquidationProbability:P2}"); @@ -893,7 +893,7 @@ public class SynthPredictionService : ISynthPredictionService { result.ShouldWarn = true; result.WarningMessage = - $"โš ๏ธ **High Liquidation Risk**\n" + + $"โš ๏ธ High Liquidation Risk\n" + $"Position: `{positionIdentifier}`\n" + $"๐ŸŽฒ Liquidation probability: {liquidationProbability:P2}\n" + $"๐Ÿ’ฐ Stop loss: ${liquidationPrice:F2}\n" + @@ -906,7 +906,7 @@ public class SynthPredictionService : ISynthPredictionService { result.ShouldAutoClose = true; result.EmergencyMessage = - $"๐Ÿšจ **Emergency Close**\n" + + $"๐Ÿšจ Emergency Close\n" + $"Extremely high liquidation risk: {liquidationProbability:P2}\n" + $"Auto-closing position for protection"; } @@ -974,7 +974,7 @@ public class SynthPredictionService : ISynthPredictionService if (cachedLeaderboard != null) { leaderboard = cachedLeaderboard.Miners; - _logger.LogDebug($"๐Ÿ“ฆ **Synth Cache** - Using cached leaderboard for {asset}"); + _logger.LogDebug($"๐Ÿ“ฆ Synth Cache - Using cached leaderboard for {asset}"); } else { @@ -1036,7 +1036,7 @@ public class SynthPredictionService : ISynthPredictionService if (cachedIndividualPredictions.Count == topMinerUids.Count) { _logger.LogDebug( - $"๐Ÿ“ฆ **Synth Individual Cache** - Using all cached individual predictions for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}"); + $"๐Ÿ“ฆ Synth Individual Cache - Using all cached individual predictions for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}"); return cachedIndividualPredictions.Select(p => p.Prediction).ToList(); } @@ -1046,7 +1046,7 @@ public class SynthPredictionService : ISynthPredictionService var missingMinerUids = topMinerUids.Where(uid => !cachedMinerUids.Contains(uid)).ToList(); _logger.LogInformation( - $"๐Ÿ”„ **Synth Individual Cache** - Partial cache hit for {asset}: {cachedIndividualPredictions.Count}/{topMinerUids.Count} cached, fetching {missingMinerUids.Count} fresh predictions {(isBacktest ? "HISTORICAL" : "LIVE")}"); + $"๐Ÿ”„ Synth Individual Cache - Partial cache hit for {asset}: {cachedIndividualPredictions.Count}/{topMinerUids.Count} cached, fetching {missingMinerUids.Count} fresh predictions {(isBacktest ? "HISTORICAL" : "LIVE")}"); // Fetch missing predictions from API List freshPredictions; @@ -1112,7 +1112,7 @@ public class SynthPredictionService : ISynthPredictionService } _logger.LogInformation( - $"๐Ÿ“ˆ **Synth Individual Cache** - Total predictions assembled: {allPredictions.Count} for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}"); + $"๐Ÿ“ˆ Synth Individual Cache - Total predictions assembled: {allPredictions.Count} for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}"); return allPredictions; } @@ -1186,7 +1186,7 @@ public class SynthPredictionService : ISynthPredictionService var probability = totalPaths > 0 ? (decimal)pathsCrossingThreshold / totalPaths : 0m; _logger.LogDebug( - $"๐Ÿงฎ **Probability Calculation** - {pathsCrossingThreshold}/{totalPaths} paths crossed threshold = {probability:P2}"); + $"๐Ÿงฎ Probability Calculation - {pathsCrossingThreshold}/{totalPaths} paths crossed threshold = {probability:P2}"); return probability; } diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs index 60acac2e..efe7d8d0 100644 --- a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs @@ -291,7 +291,7 @@ namespace Managing.Infrastructure.Messengers.Discord { return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" + $"Open Price : {position.Open.Price} \n" + - $"Closing Price : {position.Open.Price} \n" + + $"Closing Price : {position.Open.Price:F5} \n" + $"Quantity :{position.Open.Quantity} \n" + $"PNL : {position.ProfitAndLoss.Realized} $"; }