5.6 KiB
Trading Balance Compounding Fix
Issue Description
Users reported that the traded value was not correctly compounded when positions closed with profits or losses. For example, if a bot had an initial balance of $1000 and achieved a 130% ROI (ending with $1300), subsequent positions were still being opened with only $1000 instead of the compounded $1300.
Root Cause Analysis
The system was correctly implementing compounding in memory during bot execution:
-
Position Close: When a position closed, the net P&L was added to
Config.BotTradingBalanceinTradingBotBase.cs(line 1942)Config.BotTradingBalance += position.ProfitAndLoss.Net; -
State Synchronization: The updated config was synced to Orleans grain state (line 586 in
LiveTradingBotGrain.cs)_state.State.Config = _tradingBot.Config; -
Persistence: The grain state was written to Orleans storage (line 476)
await _state.WriteStateAsync();
However, there was a critical bug in the bot configuration update flow:
When users updated their bot configuration through the UI (e.g., changing scenario, timeframe, or other settings), the system would:
- Load the bot configuration (which should include the compounded balance)
- Send the configuration back to the backend
- Overwrite the compounded balance with the value from the request
The bug was in BotController.cs (line 727):
BotTradingBalance = request.Config.BotTradingBalance, // ❌ Uses stale value from request
This meant that even though the balance was being compounded correctly, any configuration update would reset it back to the value that was in the request, effectively erasing the compounded gains.
Solution Implemented
1. Backend Fix (BotController.cs)
Changed line 727-729 to preserve the current balance from the grain state:
// BEFORE
BotTradingBalance = request.Config.BotTradingBalance,
// AFTER
BotTradingBalance = config.BotTradingBalance, // Preserve current balance from grain state (includes compounded gains)
Now when updating bot configuration, we use the current balance from the grain state (config.BotTradingBalance) instead of the potentially stale value from the request.
2. Frontend Enhancement (BotConfigModal.tsx)
Made the Trading Balance field read-only in update mode to prevent user confusion:
<input
type="number"
className="input input-bordered"
value={formData.botTradingBalance}
onChange={(e) => handleInputChange('botTradingBalance', parseFloat(e.target.value))}
min="1"
step="0.01"
disabled={mode === 'update'} // ✅ Read-only in update mode
title={mode === 'update' ? 'Balance is automatically managed and cannot be manually edited' : ''}
/>
Added visual indicators:
- Badge: Shows "Auto-compounded" label next to the field
- Tooltip: Explains that the balance is automatically updated as positions close
- Helper text: "💡 Balance automatically compounds with trading profits/losses"
How Compounding Now Works
-
Initial Bot Creation: User sets an initial trading balance (e.g., $1000)
-
Position Opens: Bot uses the current balance to calculate position size
decimal balanceToRisk = Math.Round(request.AmountToTrade, 0, MidpointRounding.ToZero); -
Position Closes with Profit: If a position closes with +$300 profit:
Config.BotTradingBalance += position.ProfitAndLoss.Net; // $1000 + $300 = $1300 -
Next Position Opens: Bot now uses $1300 to calculate position size
-
Configuration Updates: If user updates any other setting:
- Backend retrieves current config from grain:
var config = await _botService.GetBotConfig(request.Identifier); - Backend preserves the compounded balance:
BotTradingBalance = config.BotTradingBalance; - User sees the compounded balance in UI (read-only field)
- Backend retrieves current config from grain:
Testing Recommendations
To verify the fix works correctly:
- Create a bot with initial balance of $1000
- Wait for a position to close with profit/loss
- Check the balance is updated in the bot's state
- Update any bot configuration (e.g., change scenario)
- Verify the balance is preserved after the update
- Open a new position and verify it uses the compounded balance
Files Modified
/src/Managing.Api/Controllers/BotController.cs- Preserve balance from grain state during config updates/src/Managing.WebApp/src/components/mollecules/BotConfigModal/BotConfigModal.tsx- Make balance read-only in update mode
Technical Details
Balance Update Flow
Position Closes →
Calculate P&L →
Update Config.BotTradingBalance →
Sync to Grain State →
Persist to Orleans Storage →
Next Position Uses Updated Balance
Configuration Update Flow (After Fix)
User Updates Config →
Backend Loads Current Config from Grain →
Backend Creates New Config with Current Balance →
Backend Updates Grain →
Compounded Balance Preserved ✅
Impact
✅ Fixed: Trading balance now correctly compounds across all positions ✅ Fixed: Configuration updates no longer reset the compounded balance ✅ Improved: Users can see their compounded balance in the UI (read-only) ✅ Enhanced: Clear visual indicators that balance is auto-managed
Notes
- The balance is stored in Orleans grain state, which persists across bot restarts
- The balance is updated ONLY when positions close with realized P&L
- Users cannot manually override the compounded balance (by design)
- For bots with 130% ROI, the next position will correctly use 130% of the initial balance