brighter-trading/MARGIN_POSITION_EDITOR_PLAN.md

407 lines
14 KiB
Markdown

## Margin Position Editor Plan
### Goal
Replace the current Reduce / Add Margin prompt flow with a real Edit Position dialog, and add a true Increase Size capability for isolated margin positions.
---
### Product Rules
- One margin position per broker_key + symbol.
- Increase Size means adding exposure in the same direction to the existing position.
- Add Collateral changes risk only; it does not change size.
- Remove Collateral withdraws excess collateral when margin ratio permits.
- Reduce Size partially closes the position and realizes proportional P/L.
- Close Position fully exits the position.
**Increase Size Inputs (two modes):**
| Mode | User Supplies | System Derives |
|------|---------------|----------------|
| Collateral-first | `additional_collateral`, `execution_leverage` | `added_size`, everything else |
| Size-first | `additional_size` | `required_collateral` (at current price), everything else |
**System-derived values for both modes:**
- `added_size` or `required_collateral` (whichever wasn't supplied)
- `new_total_size`
- `new_average_entry`
- `new_total_collateral`
- `new_effective_leverage`
- `new_liquidation_price`
- `new_margin_ratio`
---
### Phase 1: Backend Model
**Paper Broker (`paper_margin_broker.py`):**
Add `increase_position()`:
```python
def increase_position(
self,
symbol: str,
additional_collateral: Optional[float] = None,
execution_leverage: Optional[float] = None,
additional_size: Optional[float] = None,
current_price: Optional[float] = None
) -> MarginPosition:
```
Implementation requirements:
- Accrue interest on existing position before merging (locks in interest to this point).
- Calculate added size from inputs (collateral-first or size-first mode).
- Merge into existing position using weighted average entry.
- Recalculate: `borrowed`, `collateral`, `liquidation_price`, `margin_ratio`, `effective_leverage`.
- Persist increase as a history event, not a separate position.
Rejection rules:
- Position does not exist for symbol.
- Inputs would flip side (use close then open instead).
- Insufficient available balance for collateral.
- Resulting leverage exceeds `_max_leverage`.
- Invalid parameter combinations (must supply either collateral+leverage OR size).
**Live Broker (`live_margin_broker.py`):**
Add `increase_position()` with same signature. Additional requirements:
- Transfer additional collateral to isolated margin account first.
- Place autoBorrow order for the additional size.
- Wait for fill confirmation before reporting success.
- On partial fill: sync actual state, return `partial: True` with real position snapshot.
- On order failure after collateral transfer: attempt rollback transfer.
---
### Phase 1b: Paper Broker Unit Tests
Before building preview endpoints, validate paper broker math:
```python
# tests/test_paper_margin_broker_increase.py
def test_increase_long_collateral_first():
"""Increase long position using collateral + leverage inputs."""
def test_increase_long_size_first():
"""Increase long position using direct size input."""
def test_increase_short_collateral_first():
"""Increase short position using collateral + leverage inputs."""
def test_increase_weighted_average_entry():
"""Entry price is correctly weighted across increases."""
def test_increase_accrues_interest_before_merge():
"""Interest is locked in before position is merged."""
def test_increase_recalculates_liquidation_price():
"""Liquidation price reflects new effective leverage."""
def test_increase_rejects_opposite_side():
"""Cannot increase a long with short-side inputs."""
def test_increase_rejects_insufficient_balance():
"""Fails cleanly when balance cannot cover collateral."""
def test_increase_rejects_excessive_leverage():
"""Fails when resulting leverage exceeds max."""
def test_increase_rejects_missing_position():
"""Fails when no position exists for symbol."""
def test_multiple_increases_then_close():
"""Full lifecycle: open, increase twice, close. P/L correct."""
```
---
### Phase 2: Preview APIs
All preview endpoints use broker logic so UI and execution stay aligned.
**Endpoints:**
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/margin/positions/preview-increase` | Preview increase operation |
| POST | `/api/margin/positions/preview-reduce` | Preview partial close |
| POST | `/api/margin/positions/preview-add-margin` | Preview collateral addition |
| POST | `/api/margin/positions/preview-remove-margin` | Preview collateral withdrawal |
**Preview Request (increase):**
```json
{
"broker_key": "paper_margin",
"symbol": "BTC/USDT",
"mode": "collateral_first",
"additional_collateral": 500,
"execution_leverage": 3
}
```
Or size-first mode:
```json
{
"broker_key": "paper_margin",
"symbol": "BTC/USDT",
"mode": "size_first",
"additional_size": 0.05
}
```
**Preview Response Schema:**
```json
{
"preview_type": "increase",
"valid": true,
"current": {
"symbol": "BTC/USDT",
"side": "long",
"size": 0.1,
"entry_price": 60000,
"collateral": 1000,
"effective_leverage": 6,
"liquidation_price": 52000,
"margin_ratio": 85,
"unrealized_pnl": 150
},
"projected": {
"total_size": 0.15,
"average_entry": 61333.33,
"total_collateral": 1500,
"effective_leverage": 6.13,
"liquidation_price": 53200,
"margin_ratio": 82,
"added_size": 0.05,
"required_collateral": 500
},
"warnings": [
"Effective leverage will exceed 5x"
],
"errors": []
}
```
**Error response (invalid preview):**
```json
{
"preview_type": "increase",
"valid": false,
"current": { ... },
"projected": null,
"warnings": [],
"errors": ["Insufficient balance: need 500, have 200"]
}
```
---
### Phase 3: Execution APIs
**New endpoint:**
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/margin/positions/increase` | Execute position increase |
| POST | `/api/margin/positions/remove-margin` | Withdraw excess collateral |
**Keep existing:**
- POST `/api/margin/positions/reduce`
- POST `/api/margin/positions/add-margin`
- POST `/api/margin/positions/close`
**Execution requirements:**
- For live margin, do not report `success: true` until order is filled or clear partial-fill state is returned.
- Return actual position snapshot after sync, not optimistic projection.
- Include `order_id` and `client_order_id` for audit trail.
---
### Phase 4: UI
**Replace buttons:**
- Remove individual "Reduce" and "Add Margin" buttons from position cards.
- Add single "Edit" button per position.
- Keep "Close" as a separate prominent action (not buried in dialog).
**Create `edit_position_dialog.html`:**
Dialog structure:
```
┌─────────────────────────────────────────────────────────┐
│ Edit Position: BTC/USDT LONG [X] │
├─────────────────────────────────────────────────────────┤
│ CURRENT POSITION │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Size: 0.1 BTC Entry: $60,000 │ │
│ │ Collateral: $1,000 Leverage: 6x │ │
│ │ Liq Price: $52,000 Health: 85% │ │
│ │ Unrealized P/L: +$150 │ │
│ └────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ [Increase Size] [Reduce Size] [Add Collateral] [Remove]│
├─────────────────────────────────────────────────────────┤
│ INCREASE SIZE │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Mode: (•) Collateral + Leverage ( ) Direct Size │ │
│ │ │ │
│ │ Additional Collateral: [____500____] USDT │ │
│ │ Execution Leverage: [____3x_____] │ │
│ │ │ │
│ │ ─── PROJECTED ─── │ │
│ │ New Size: 0.15 BTC (+0.05) │ │
│ │ Avg Entry: $61,333 │ │
│ │ New Collateral: $1,500 │ │
│ │ Effective Leverage: 6.13x │ │
│ │ New Liq Price: $53,200 │ │
│ │ New Health: 82% │ │
│ │ │ │
│ │ ⚠ Effective leverage will exceed 5x │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Confirm Increase]│
└─────────────────────────────────────────────────────────┘
```
**UI behavior:**
- Call preview endpoints on input change with **300ms debounce**.
- Show loading spinner in projected section while preview is in-flight.
- Disable submit button while request is in-flight.
- Show inline validation errors from preview response.
- Show warnings (yellow) vs errors (red) distinctly.
- On success: close dialog, refresh all panels.
- On partial fill (live): show warning modal with actual state, refresh panels.
**Do NOT:**
- Use `prompt()`.
- Use frontend-only liquidation math.
- Allow submit when preview shows `valid: false`.
---
### Phase 5: Trade Panel Integration
**Position cards:**
- Replace "Reduce" / "Add Margin" buttons with single "Edit" button.
- Keep "Close" button visible (not in dialog).
- Show current margin_ratio as health bar.
**Refresh after edits:**
- Positions list
- History/activity feed
- Account stats (balance, equity)
- P/L summary
**Activity/history rows for:**
- Position increased (show size delta, new avg entry)
- Position reduced (show size delta, realized P/L)
- Collateral added (show amount, new health)
- Collateral removed (show amount, new health)
- Position closed (show final P/L)
---
### History Schema
Record for each edit operation:
```python
@dataclass
class MarginPositionEdit:
id: str # UUID
broker_key: str
symbol: str
action_type: str # 'increase', 'reduce', 'add_margin', 'remove_margin', 'close'
side: str # 'long' or 'short'
timestamp: datetime
# Deltas
size_delta: float # Positive for increase, negative for reduce
collateral_delta: float # Positive for add, negative for remove
# Before state
size_before: float
entry_price_before: float
collateral_before: float
effective_leverage_before: float
liquidation_price_before: float
margin_ratio_before: float
# After state
size_after: float
entry_price_after: float
collateral_after: float
effective_leverage_after: float
liquidation_price_after: float
margin_ratio_after: float
# For reduce/close
execution_price: Optional[float]
realized_pnl: Optional[float]
interest_paid: Optional[float]
# Audit
order_id: Optional[str] # For live trades
client_order_id: Optional[str]
```
---
### Testing
**Paper broker:**
- Increase long (collateral-first mode)
- Increase long (size-first mode)
- Increase short
- Weighted average entry calculation
- Interest accrual before merge
- Add collateral
- Remove collateral (with min health check)
- Partial reduce
- Full close after multiple increases
- Preview matches execution values
**Live broker unit tests:**
- Full fill flow
- Partial fill handling
- Failed collateral transfer
- Failed order after collateral transfer → rollback
- Order timeout handling
**UI tests:**
- Dialog opens from position card Edit button
- Tab switching works
- Previews update on input (with debounce)
- Loading states shown correctly
- Submit disabled during in-flight
- Success closes dialog and refreshes panels
- Errors display inline
- Warnings display distinctly from errors
---
### Rollout Order
| Step | Scope | Deliverable |
|------|-------|-------------|
| 1 | Backend | Paper broker `increase_position()` |
| 2 | Backend | Paper broker unit tests (Phase 1b) |
| 3 | Backend | Preview endpoints (all four) |
| 4 | Backend | `remove_margin` execution endpoint |
| 5 | Frontend | Edit dialog (wired to paper margin) |
| 6 | Backend | Live broker `increase_position()` |
| 7 | Backend | Live broker `remove_margin()` |
| 8 | Testing | Live execution hardening, tiny-position edge cases |
| 9 | Frontend | Wire dialog to live margin |
| 10 | QA | End-to-end paper + live testing |
---
### Out of Scope (Future)
- Cross-margin mode support
- Margin mode switching (isolated ↔ cross)
- Batch position edits
- Conditional increase (limit orders for averaging in)