Update messaging

This commit is contained in:
2025-10-06 01:34:13 +07:00
parent dab4807334
commit 347c78afc7
8 changed files with 144 additions and 171 deletions

View File

@@ -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<IWebhookService>(_scopeFactory,

View File

@@ -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<IExchangeService>(_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<IExchangeService>(_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<IMessengerService>(_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<Position> positionHistory = null;
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_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<IMessengerService>(_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")}`");
}
/// <summary>
@@ -1743,7 +1739,7 @@ public class TradingBotBase : ITradingBot
/// </summary>
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<IMessengerService>(_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" +

View File

@@ -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);
}

View File

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

View File

@@ -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<List<MinerInfo>>(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<MinerInfo>();
}
@@ -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<List<MinerInfo>>(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<MinerInfo>();
}
@@ -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<MinerPrediction>();
}
@@ -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<MinerPrediction>();
}

View File

@@ -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");
}
/// <summary>
@@ -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<MinerPrediction> 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;
}