Implement balance update callback in TradingBotBase for immediate sync

- Removed the private _currentBalance field and replaced it with direct access to Config.BotTradingBalance.
- Added OnBalanceUpdatedCallback to TradingBotBase for immediate synchronization and database saving when the balance is updated.
- Updated LiveTradingBotGrain to set the callback for balance updates, ensuring accurate state management.
- Modified PostgreSqlBotRepository to save the updated bot trading balance during entity updates.
This commit is contained in:
2026-01-09 03:34:35 +07:00
parent 8d4be59d10
commit ae353aa0d5
3 changed files with 37 additions and 6 deletions

View File

@@ -593,6 +593,18 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// Load state into the trading bot instance // Load state into the trading bot instance
LoadStateIntoTradingBot(tradingBot); LoadStateIntoTradingBot(tradingBot);
// Set up callback for immediate balance sync and save when balance is updated
tradingBot.OnBalanceUpdatedCallback = async () =>
{
SyncStateFromBase();
await _state.WriteStateAsync();
// Save to database immediately so GetStrategiesPaginated shows correct balance
var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0);
var status = await botRegistry.GetBotStatus(this.GetPrimaryKey());
await SaveBotAsync(status);
};
return tradingBot; return tradingBot;
} }

View File

@@ -40,7 +40,6 @@ public abstract class TradingBotBase : ITradingBot
public Dictionary<string, LightSignal> Signals { get; set; } public Dictionary<string, LightSignal> Signals { get; set; }
public Dictionary<Guid, Position> Positions { get; set; } public Dictionary<Guid, Position> Positions { get; set; }
public Dictionary<DateTime, decimal> WalletBalances { get; set; } public Dictionary<DateTime, decimal> WalletBalances { get; set; }
private decimal _currentBalance;
public DateTime PreloadSince { get; set; } public DateTime PreloadSince { get; set; }
public int PreloadedCandlesCount { get; set; } public int PreloadedCandlesCount { get; set; }
public long ExecutionCount { get; set; } = 0; public long ExecutionCount { get; set; } = 0;
@@ -51,6 +50,12 @@ public abstract class TradingBotBase : ITradingBot
// OPTIMIZATION 2: Cache open position state to avoid expensive Positions.Any() calls // OPTIMIZATION 2: Cache open position state to avoid expensive Positions.Any() calls
private bool _hasOpenPosition = false; private bool _hasOpenPosition = false;
/// <summary>
/// Callback to notify the grain when balance is updated (for immediate sync and save to database).
/// Set by the grain after creating the bot instance.
/// </summary>
public Func<Task>? OnBalanceUpdatedCallback { get; set; }
public TradingBotBase( public TradingBotBase(
ILogger<TradingBotBase> logger, ILogger<TradingBotBase> logger,
IServiceScopeFactory scopeFactory, IServiceScopeFactory scopeFactory,
@@ -65,7 +70,6 @@ public abstract class TradingBotBase : ITradingBot
Signals = new Dictionary<string, LightSignal>(); Signals = new Dictionary<string, LightSignal>();
Positions = new Dictionary<Guid, Position>(); Positions = new Dictionary<Guid, Position>();
WalletBalances = new Dictionary<DateTime, decimal>(); WalletBalances = new Dictionary<DateTime, decimal>();
_currentBalance = config.BotTradingBalance;
PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe); PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe);
} }
@@ -433,13 +437,13 @@ public abstract class TradingBotBase : ITradingBot
if (WalletBalances.Count == 0) if (WalletBalances.Count == 0)
{ {
WalletBalances[date] = _currentBalance; WalletBalances[date] = Config.BotTradingBalance;
return; return;
} }
if (!WalletBalances.ContainsKey(date)) if (!WalletBalances.ContainsKey(date))
{ {
WalletBalances[date] = _currentBalance; WalletBalances[date] = Config.BotTradingBalance;
} }
} }
@@ -1270,13 +1274,15 @@ public abstract class TradingBotBase : ITradingBot
if (position.ProfitAndLoss != null) if (position.ProfitAndLoss != null)
{ {
// Update the current balance when position closes // Update the balance when position closes
_currentBalance += position.ProfitAndLoss.Net;
Config.BotTradingBalance += position.ProfitAndLoss.Net; Config.BotTradingBalance += position.ProfitAndLoss.Net;
await LogDebugAsync( await LogDebugAsync(
string.Format("💰 Balance Updated\nNew bot trading balance: `${0:F2}`", string.Format("💰 Balance Updated\nNew bot trading balance: `${0:F2}`",
Config.BotTradingBalance)); Config.BotTradingBalance));
// For live trading, immediately sync and save to database
await OnBalanceUpdatedAsync();
} }
} }
else else
@@ -2060,6 +2066,18 @@ public abstract class TradingBotBase : ITradingBot
await CancelAllOrders(); await CancelAllOrders();
} }
/// <summary>
/// Called when the bot trading balance is updated (e.g., after a position closes).
/// Calls the OnBalanceUpdatedCallback if set (by the grain for live trading).
/// </summary>
protected virtual async Task OnBalanceUpdatedAsync()
{
if (OnBalanceUpdatedCallback != null)
{
await OnBalanceUpdatedCallback();
}
}
// Interface implementation // Interface implementation
public async Task LogInformation(string message) public async Task LogInformation(string message)
{ {

View File

@@ -83,6 +83,7 @@ public class PostgreSqlBotRepository : IBotRepository
existingEntity.AccumulatedRunTimeSeconds = bot.AccumulatedRunTimeSeconds; existingEntity.AccumulatedRunTimeSeconds = bot.AccumulatedRunTimeSeconds;
existingEntity.MasterBotUserId = existingEntity.MasterBotUserId =
bot.MasterBotUserId ?? existingEntity.MasterBotUserId; bot.MasterBotUserId ?? existingEntity.MasterBotUserId;
existingEntity.BotTradingBalance = bot.BotTradingBalance;
await _context.SaveChangesAsync().ConfigureAwait(false); await _context.SaveChangesAsync().ConfigureAwait(false);
} }