Update messages

This commit is contained in:
2025-07-04 13:56:59 +07:00
parent 2d295fc860
commit f613020d9d

View File

@@ -83,7 +83,8 @@ public class TradingBot : Bot, ITradingBot
}
else
{
throw new ArgumentException("Scenario object must be provided in TradingBotConfig. ScenarioName alone is not sufficient.");
throw new ArgumentException(
"Scenario object must be provided in TradingBotConfig. ScenarioName alone is not sufficient.");
}
if (!Config.IsForBacktest)
@@ -105,23 +106,51 @@ public class TradingBot : Bot, ITradingBot
// This is just a safety check
if (Config.Scenario == null || !Indicators.Any())
{
throw new InvalidOperationException("Scenario or indicators not loaded properly in constructor. This indicates a configuration error.");
throw new InvalidOperationException(
"Scenario or indicators not loaded properly in constructor. This indicates a configuration error.");
}
PreloadCandles().GetAwaiter().GetResult();
CancelAllOrders().GetAwaiter().GetResult();
// Send startup message only for fresh starts (not reboots)
var isReboot = Signals.Any() || Positions.Any();
if (!isReboot)
{
try
{
// await MessengerService.SendMessage(
// $"Hey everyone! I'm about to start {Name}. 🚀\n" +
// $"I'll post an update here each time a signal is triggered by the following strategies: {string.Join(", ", Strategies.Select(s => s.Name))}."
// );
var indicatorNames = Indicators.Select(i => i.Type.ToString()).ToList();
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.";
LogInformation(startupMessage).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Message);
}
}
else
{
try
{
LogInformation($"🔄 **Bot Restarted**\n" +
$"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" +
$"✅ Ready to continue trading").GetAwaiter().GetResult();
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Message);
}
}
InitWorker(Run).GetAwaiter().GetResult();
}
@@ -144,7 +173,6 @@ public class TradingBot : Bot, ITradingBot
}
public void LoadScenario(Scenario scenario)
{
if (scenario == null)
@@ -1469,22 +1497,154 @@ public class TradingBot : Bot, ITradingBot
throw new ArgumentException("Scenario object must be provided in configuration");
}
// Track changes for logging
var changes = new List<string>();
// Check for changes and build change list
if (Config.BotTradingBalance != newConfig.BotTradingBalance)
{
changes.Add($"💰 Balance: ${Config.BotTradingBalance:F2} → ${newConfig.BotTradingBalance:F2}");
}
if (Config.MaxPositionTimeHours != newConfig.MaxPositionTimeHours)
{
var oldTime = Config.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled";
var newTime = newConfig.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled";
changes.Add($"⏱️ Max Time: {oldTime} → {newTime}");
}
if (Config.FlipOnlyWhenInProfit != newConfig.FlipOnlyWhenInProfit)
{
var oldFlip = Config.FlipOnlyWhenInProfit ? "✅" : "❌";
var newFlip = newConfig.FlipOnlyWhenInProfit ? "✅" : "❌";
changes.Add($"📈 Flip Only in Profit: {oldFlip} → {newFlip}");
}
if (Config.CooldownPeriod != newConfig.CooldownPeriod)
{
changes.Add($"⏳ Cooldown: {Config.CooldownPeriod} → {newConfig.CooldownPeriod} candles");
}
if (Config.MaxLossStreak != newConfig.MaxLossStreak)
{
changes.Add($"📉 Max Loss Streak: {Config.MaxLossStreak} → {newConfig.MaxLossStreak}");
}
if (Config.FlipPosition != newConfig.FlipPosition)
{
var oldFlipPos = Config.FlipPosition ? "✅" : "❌";
var newFlipPos = newConfig.FlipPosition ? "✅" : "❌";
changes.Add($"🔄 Flip Position: {oldFlipPos} → {newFlipPos}");
}
if (Config.CloseEarlyWhenProfitable != newConfig.CloseEarlyWhenProfitable)
{
var oldCloseEarly = Config.CloseEarlyWhenProfitable ? "✅" : "❌";
var newCloseEarly = newConfig.CloseEarlyWhenProfitable ? "✅" : "❌";
changes.Add($"⏰ Close Early When Profitable: {oldCloseEarly} → {newCloseEarly}");
}
if (Config.UseSynthApi != newConfig.UseSynthApi)
{
var oldSynth = Config.UseSynthApi ? "✅" : "❌";
var newSynth = newConfig.UseSynthApi ? "✅" : "❌";
changes.Add($"🔗 Use Synth API: {oldSynth} → {newSynth}");
}
if (Config.UseForPositionSizing != newConfig.UseForPositionSizing)
{
var oldPositionSizing = Config.UseForPositionSizing ? "✅" : "❌";
var newPositionSizing = newConfig.UseForPositionSizing ? "✅" : "❌";
changes.Add($"📏 Use Synth for Position Sizing: {oldPositionSizing} → {newPositionSizing}");
}
if (Config.UseForSignalFiltering != newConfig.UseForSignalFiltering)
{
var oldSignalFiltering = Config.UseForSignalFiltering ? "✅" : "❌";
var newSignalFiltering = newConfig.UseForSignalFiltering ? "✅" : "❌";
changes.Add($"🔍 Use Synth for Signal Filtering: {oldSignalFiltering} → {newSignalFiltering}");
}
if (Config.UseForDynamicStopLoss != newConfig.UseForDynamicStopLoss)
{
var oldDynamicStopLoss = Config.UseForDynamicStopLoss ? "✅" : "❌";
var newDynamicStopLoss = newConfig.UseForDynamicStopLoss ? "✅" : "❌";
changes.Add($"🎯 Use Synth for Dynamic Stop Loss: {oldDynamicStopLoss} → {newDynamicStopLoss}");
}
if (Config.IsForWatchingOnly != newConfig.IsForWatchingOnly)
{
var oldWatch = Config.IsForWatchingOnly ? "✅" : "❌";
var newWatch = newConfig.IsForWatchingOnly ? "✅" : "❌";
changes.Add($"👀 Watch Only: {oldWatch} → {newWatch}");
}
if (Config.MoneyManagement?.GetType().Name != newConfig.MoneyManagement?.GetType().Name)
{
var oldMM = Config.MoneyManagement?.GetType().Name ?? "None";
var newMM = newConfig.MoneyManagement?.GetType().Name ?? "None";
changes.Add($"💰 Money Management: {oldMM} → {newMM}");
}
if (Config.RiskManagement != newConfig.RiskManagement)
{
// Compare risk management by serializing (complex object comparison)
var oldRiskSerialized = JsonConvert.SerializeObject(Config.RiskManagement, Formatting.None);
var newRiskSerialized = JsonConvert.SerializeObject(newConfig.RiskManagement, Formatting.None);
if (oldRiskSerialized != newRiskSerialized)
{
changes.Add($"⚠️ Risk Management: Configuration Updated");
}
}
if (Config.ScenarioName != newConfig.ScenarioName)
{
changes.Add($"📋 Scenario Name: {Config.ScenarioName ?? "None"} → {newConfig.ScenarioName ?? "None"}");
}
if (allowNameChange && Config.Name != newConfig.Name)
{
changes.Add($"🏷️ Name: {Config.Name} → {newConfig.Name}");
}
if (Config.AccountName != newConfig.AccountName)
{
changes.Add($"👤 Account: {Config.AccountName} → {newConfig.AccountName}");
}
if (Config.Ticker != newConfig.Ticker)
{
changes.Add($"📊 Ticker: {Config.Ticker} → {newConfig.Ticker}");
}
if (Config.Timeframe != newConfig.Timeframe)
{
changes.Add($"📈 Timeframe: {Config.Timeframe} → {newConfig.Timeframe}");
}
// Capture current indicators before any changes for scenario comparison
var oldIndicators = Indicators?.ToList() ?? new List<IIndicator>();
// Check if the actual Scenario object changed (not just the name)
var scenarioChanged = false;
if (Config.Scenario != newConfig.Scenario)
{
var oldScenarioSerialized = JsonConvert.SerializeObject(Config.Scenario, Formatting.None);
var newScenarioSerialized = JsonConvert.SerializeObject(newConfig.Scenario, Formatting.None);
if (oldScenarioSerialized != newScenarioSerialized)
{
scenarioChanged = true;
changes.Add(
$"🎯 Scenario: {Config.Scenario?.Name ?? "None"} → {newConfig.Scenario?.Name ?? "None"}");
}
}
// Protect critical properties that shouldn't change for running bots
var protectedIsForBacktest = Config.IsForBacktest;
var protectedName = allowNameChange ? newConfig.Name : Config.Name;
// Log the configuration update (before changing anything)
await LogInformation("⚙️ **Configuration Update**\n" +
"📊 **Previous Settings:**\n" +
$"💰 Balance: ${Config.BotTradingBalance:F2}\n" +
$"⏱️ Max Time: {(Config.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled")}\n" +
$"📈 Flip Only in Profit: {(Config.FlipOnlyWhenInProfit ? "" : "")}\n" +
$"⏳ Cooldown: {Config.CooldownPeriod} candles\n" +
$"📉 Max Loss Streak: {Config.MaxLossStreak}" +
(allowNameChange && newConfig.Name != Config.Name
? $"\n🏷 Name: {Config.Name} → {newConfig.Name}"
: ""));
// Update the configuration
Config = newConfig;
@@ -1505,14 +1665,21 @@ public class TradingBot : Bot, ITradingBot
await LoadAccount();
}
// If scenario changed, reload it
var currentScenario = Config.Scenario?.Name;
var newScenario = newConfig.Scenario?.Name;
if (newScenario != currentScenario)
// If scenario changed, reload it and track indicator changes
if (scenarioChanged)
{
if (newConfig.Scenario != null)
{
LoadScenario(newConfig.Scenario);
// Compare indicators after scenario change
var newIndicators = Indicators?.ToList() ?? new List<IIndicator>();
var indicatorChanges = CompareIndicators(oldIndicators, newIndicators);
if (indicatorChanges.Any())
{
changes.AddRange(indicatorChanges);
}
}
else
{
@@ -1520,13 +1687,17 @@ public class TradingBot : Bot, ITradingBot
}
}
await LogInformation("✅ **Configuration Applied**\n" +
"🔧 **New Settings:**\n" +
$"💰 Balance: ${Config.BotTradingBalance:F2}\n" +
$"⏱️ Max Time: {(Config.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled")}\n" +
$"📈 Flip Only in Profit: {(Config.FlipOnlyWhenInProfit ? "" : "")}\n" +
$"⏳ Cooldown: {Config.CooldownPeriod} candles\n" +
$"📉 Max Loss Streak: {Config.MaxLossStreak}");
// Only log if there are actual changes
if (changes.Any())
{
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");
}
// Save the updated configuration as backup
if (!Config.IsForBacktest)
@@ -1555,6 +1726,7 @@ public class TradingBot : Bot, ITradingBot
MoneyManagement = Config.MoneyManagement,
Ticker = Config.Ticker,
ScenarioName = Config.ScenarioName,
Scenario = Config.Scenario,
Timeframe = Config.Timeframe,
IsForWatchingOnly = Config.IsForWatchingOnly,
BotTradingBalance = Config.BotTradingBalance,
@@ -1567,8 +1739,70 @@ public class TradingBot : Bot, ITradingBot
Name = Config.Name,
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable,
UseSynthApi = Config.UseSynthApi,
UseForPositionSizing = Config.UseForPositionSizing,
UseForSignalFiltering = Config.UseForSignalFiltering,
UseForDynamicStopLoss = Config.UseForDynamicStopLoss,
RiskManagement = Config.RiskManagement,
};
}
/// <summary>
/// Compares two lists of indicators and returns a list of changes (added, removed, modified).
/// </summary>
/// <param name="oldIndicators">The previous list of indicators</param>
/// <param name="newIndicators">The new list of indicators</param>
/// <returns>A list of change descriptions</returns>
private List<string> CompareIndicators(List<IIndicator> oldIndicators, List<IIndicator> newIndicators)
{
var changes = new List<string>();
// Create dictionaries for easier comparison using Type as key
var oldIndicatorDict = oldIndicators.ToDictionary(i => i.Type, i => i);
var newIndicatorDict = newIndicators.ToDictionary(i => i.Type, i => i);
// Find removed indicators
var removedTypes = oldIndicatorDict.Keys.Except(newIndicatorDict.Keys);
foreach (var removedType in removedTypes)
{
var indicator = oldIndicatorDict[removedType];
changes.Add($" **Removed Indicator:** {removedType} ({indicator.GetType().Name})");
}
// Find added indicators
var addedTypes = newIndicatorDict.Keys.Except(oldIndicatorDict.Keys);
foreach (var addedType in addedTypes)
{
var indicator = newIndicatorDict[addedType];
changes.Add($" **Added Indicator:** {addedType} ({indicator.GetType().Name})");
}
// Find modified indicators (same type but potentially different configuration)
var commonTypes = oldIndicatorDict.Keys.Intersect(newIndicatorDict.Keys);
foreach (var commonType in commonTypes)
{
var oldIndicator = oldIndicatorDict[commonType];
var newIndicator = newIndicatorDict[commonType];
// Compare indicators by serializing them (simple way to detect configuration changes)
var oldSerialized = JsonConvert.SerializeObject(oldIndicator, Formatting.None);
var newSerialized = JsonConvert.SerializeObject(newIndicator, Formatting.None);
if (oldSerialized != newSerialized)
{
changes.Add($"🔄 **Modified Indicator:** {commonType} ({newIndicator.GetType().Name})");
}
}
// Add summary if there are changes
if (changes.Any())
{
var summary =
$"📊 **Indicator Changes:** {addedTypes.Count()} added, {removedTypes.Count()} removed, {commonTypes.Count(c => JsonConvert.SerializeObject(oldIndicatorDict[c]) != JsonConvert.SerializeObject(newIndicatorDict[c]))} modified";
changes.Insert(0, summary);
}
return changes;
}
}
public class TradingBotBackup