Fix realized pnl on backtest save + add tests (not all passing)

This commit is contained in:
2025-11-14 02:38:15 +07:00
parent 1f7d914625
commit 460a7bd559
34 changed files with 6012 additions and 500 deletions

150
COMPOUNDING_FIX.md Normal file
View File

@@ -0,0 +1,150 @@
# 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:
1. **Position Close**: When a position closed, the net P&L was added to `Config.BotTradingBalance` in `TradingBotBase.cs` (line 1942)
```csharp
Config.BotTradingBalance += position.ProfitAndLoss.Net;
```
2. **State Synchronization**: The updated config was synced to Orleans grain state (line 586 in `LiveTradingBotGrain.cs`)
```csharp
_state.State.Config = _tradingBot.Config;
```
3. **Persistence**: The grain state was written to Orleans storage (line 476)
```csharp
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:
1. Load the bot configuration (which should include the compounded balance)
2. Send the configuration back to the backend
3. **Overwrite the compounded balance** with the value from the request
The bug was in `BotController.cs` (line 727):
```csharp
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:
```csharp
// 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:
```tsx
<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
1. **Initial Bot Creation**: User sets an initial trading balance (e.g., $1000)
2. **Position Opens**: Bot uses the current balance to calculate position size
```csharp
decimal balanceToRisk = Math.Round(request.AmountToTrade, 0, MidpointRounding.ToZero);
```
3. **Position Closes with Profit**: If a position closes with +$300 profit:
```csharp
Config.BotTradingBalance += position.ProfitAndLoss.Net; // $1000 + $300 = $1300
```
4. **Next Position Opens**: Bot now uses $1300 to calculate position size
5. **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)
## Testing Recommendations
To verify the fix works correctly:
1. **Create a bot** with initial balance of $1000
2. **Wait for a position to close** with profit/loss
3. **Check the balance is updated** in the bot's state
4. **Update any bot configuration** (e.g., change scenario)
5. **Verify the balance is preserved** after the update
6. **Open a new position** and verify it uses the compounded balance
## Files Modified
1. `/src/Managing.Api/Controllers/BotController.cs` - Preserve balance from grain state during config updates
2. `/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