## 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)