brighter-trading/MARGIN_POSITION_EDITOR_PLAN.md

14 KiB

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():

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:

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

{
  "broker_key": "paper_margin",
  "symbol": "BTC/USDT",
  "mode": "collateral_first",
  "additional_collateral": 500,
  "execution_leverage": 3
}

Or size-first mode:

{
  "broker_key": "paper_margin",
  "symbol": "BTC/USDT",
  "mode": "size_first",
  "additional_size": 0.05
}

Preview Response Schema:

{
  "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):

{
  "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:

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