Appearance
UserPaperTradePortfolio
Per-user Durable Object managing the full paper trading lifecycle: position management, order execution, challenge tracking, subscriptions, and account health. Each user gets one instance, persisting all state in SQLite with BigNumber precision for monetary values.
No alarm loop — all execution is event-driven via callbacks from PriceAlert (onAlert, onPriceArrival) and AccountValueAggregationTracker (onHourlyExtremes, onDailyExtremesFinalized).
Architecture
31 managers organized by domain, all inheriting from BaseDurableManager<UserPaperTradePortfolio>. The DO itself is a thin shell — business logic lives entirely in managers that communicate through the parent DO instance.
Price & Order Execution
AlertRegistrationManager
│ registers/deregisters alerts
▼
PriceAlert ────────────┐
(read replica) │
▲ │ onAlert
getPriceData │ │ onPriceArrival
│ ▼
PriceFetchManager PriceAlertManager
│ │
│ │
OrderExecutionManager ◀─────┘
│ │ │ │
▼ ▼ ▼ ▼ ┌─────────────┐
Position Balance Fee WebSocket ───▶ │ Frontend UI │
Manager Manager Manager Manager └─────────────┘Challenge, Account Value & MLL
ChallengeManager
│
├─ startChallenge()
│ ├─ setBalanceForChallenge(capital, minBalance) ← enables MLL detection
│ └─ AccountValueTrackerManager.subscribe()
│ │
│ ▼
│ AccountValueAggregationTracker (external DO)
│ ▲ │
│ updateCoefficients() │ callbacks at time boundaries
│ (on each position change) │
│ ├─ onHourlyExtremes(peak, bottom)
│ └─ onDailyExtremesFinalized()
│ │
│ ▼
│ MLL breach check
│ bottom ≤ minBalance?
│ │ yes
│ ▼
├─ failChallenge() ◀───────── MllRiskManager ───▶ MllBreachRegistry (external DO)
│
└─ AccountHealthManager (periodic safety net)Subscription Lifecycle
SubscriptionManager
│ create / cancel / resume / renew
│
├─ PlanChangeManager
│ upgrade (mid-cycle proration) / scheduled downgrade
│
├─ PaymentHistoryManager
│ records all payment events
│
├─ ResetCreditsManager
│ credits for challenge resets (active + pending)
│
└─ SubscriptionReminderManager
schedules expiry reminders (7d, 3d, 1d, expired, grace)
via TaskScheduler → EmailNotificationManagerManager Domains
Trading Core
| Manager | Responsibility |
|---|---|
| OrderExecutionManager | Central orchestrator for all trade execution — validates, executes position updates, applies PnL, registers alerts, broadcasts events |
| PositionManager | Position netting (add/partial close/reversal), liquidation price calculation, entry price averaging |
| PendingOrderManager | Pending market order state machine (CREATED → PENDING → EXECUTED/FAILED), executes on price arrival callback |
| LimitOrderManager | Limit order lifecycle with margin reservation at creation, PriceAlert registration, execute/cancel |
| BalanceManager | Account balance, multi-account lifecycle, account failure detection (balance ≤ minBalance) |
| FeeManager | 7-tier volume-based fee calculation with 14-day rolling window, daily volume tracking |
| LeverageSettingsManager | Per-symbol leverage persistence, strictly-increasing enforcement, auto-adjustment on position open |
Price & Alert Integration
| Manager | Responsibility |
|---|---|
| PriceFetchManager | Fetches prices from PriceAlert read replica with in-memory caching |
| AlertRegistrationManager | Registers/deregisters alerts with PriceAlert DOs (0-9 instances, random load balancing) for all 4 alert types |
| PriceAlertManager | Translates external onAlert()/onPriceArrival() callbacks into internal trade execution |
Challenge & Risk
| Manager | Responsibility |
|---|---|
| ChallengeManager | Challenge lifecycle (start/pass/fail), criteria evaluation (profit, consistency, trading days), enables failure detection via setBalanceForChallenge() |
| ChallengeAdminManager | Admin operations: force pass, reset, fail with reason — all logged to audit trail |
| MllRiskManager | MLL breach event tracking, risk level calculation (safe/warning/critical) |
| AccountValueTrackerManager | Subscribes to AccountValueAggregationTracker for peak/bottom tracking, sends coefficient snapshots on position changes |
| AccountHealthManager | Periodic health checks for MLL breach detection |
Subscription & Billing
| Manager | Responsibility |
|---|---|
| SubscriptionManager | Subscription lifecycle (create/cancel/resume/renew), status calculation at read-time |
| PlanChangeManager | Mid-cycle upgrades with proration, scheduled downgrades |
| PaymentHistoryManager | Payment record storage and paginated retrieval |
| ResetCreditsManager | Reset credit management (active + pending), challenge reset with credit deduction |
| SubscriptionReminderManager | Schedules expiry reminders (7d, 3d, 1d, expired, grace) via TaskScheduler with email notifications |
User & Notifications
| Manager | Responsibility |
|---|---|
| NotificationManager | In-app notification CRUD with filtering and pagination |
| EmailNotificationManager | Email address management, verification, notification queuing |
| WebSocketManager | Real-time event broadcasting via Hibernation API |
| SocialAccountManager | OAuth 2.0 social account linking (Twitter/X) with AES-GCM token encryption |
| ReferralManager | Referral code generation (ACE-{FIRST5}-{LAST3}), status tracking, rebate calculation |
| PromoCodeRedemptionManager | Promo/referral code validation via PromoCodeRegistry DO |
| PreferencesManager | User display and trading preferences |
| AnalyticsManager | Analytics queries: daily/hourly snapshots, trade statistics, volume history |
Helpers
| Manager | Responsibility |
|---|---|
| IdempotencyManager | 24-hour TTL deduplication keys with PROCESSING status for race condition prevention |
| InputValidation | Parameter validation for all trading operations (leverage, margin, size constraints) |
| StateSeedingManager | Test scenario seeding (dev/test only) |
Key Data Flows
Market Order Execution
User: openPosition(symbol, direction, sizeUsd, leverage)
│
├─ IdempotencyManager.withIdempotency()
├─ InputValidation.validateOpenPositionParams()
├─ PriceFetchManager.fetchPriceForOrder(timestamp)
│
├─ IF price unavailable:
│ ├─ PendingOrderManager.createPendingOrder()
│ ├─ AlertRegistrationManager → PriceAlert.registerPriceArrivalAlert()
│ └─ Return { status: "PENDING" }
│
└─ IF price available:
└─ OrderExecutionManager.executeOrder()
├─ PositionManager.executePositionUpdate() → netting logic
├─ FeeManager.calculateFee()
├─ BalanceManager.applyPnL()
│ └─ IF balance ≤ minBalance → ChallengeManager.failChallenge()
├─ AlertRegistrationManager.registerLiquidationWatch()
├─ AccountValueTrackerManager.updateCoefficients()
├─ WebSocketManager.broadcast(POSITION_OPENED)
└─ Return { status: "EXECUTED", trade, position }Limit Order Flow
User: openLimitOrder(symbol, direction, sizeUsd, leverage, targetPrice)
│
├─ Validate params, check available margin
├─ Reserve margin (sizeUsd / leverage)
├─ PriceAlert.registerLimitOrderWithPriceCheck()
│ └─ Atomic: check current price + register condition alert
│
├─ IF price already crosses target → execute immediately
├─ IF price data not yet available → create PENDING_PRICE order, wait for arrival
└─ IF price exists but target not met → create ACTIVE order with condition alert
│
... later, when price crosses target ...
│
PriceAlert.onAlert() → PriceAlertManager
├─ LimitOrderManager.getLimitOrderByAlertId()
├─ OrderExecutionManager.executeOrder()
├─ Release reserved margin
└─ WebSocketManager.broadcast(LIMIT_ORDER_TRIGGERED)Challenge Lifecycle
User: startChallenge(plan)
│
├─ Close all positions, cancel all limit orders
├─ Deregister all alerts
├─ Reset balance to plan capital
├─ BalanceManager.setBalanceForChallenge(capital, minBalance)
│ └─ Enables MLL failure detection
├─ AccountValueTrackerManager.subscribe()
│ └─ AccountValueAggregationTracker starts peak/bottom tracking
└─ Challenge status: ACTIVE
│
... during trading ...
│
├─ Each trade → BalanceManager.applyPnL() checks MLL
├─ Each position change → updateCoefficients() to tracker
├─ Hourly → onHourlyExtremes(peak, bottom)
└─ Daily at UTC midnight → onDailyExtremesFinalized()
└─ IF bottom ≤ minBalance → failChallenge()
│
... user requests pass ...
│
passChallenge()
├─ Check: profit ≥ target, consistency ≤ 0.5, days ≥ target
├─ Register PriceArrivalAlert for latest prices
└─ onPassChallengeValidation() → verify MLL with real prices
└─ IF all criteria met → PASSEDKey Concepts
Deterministic Pricing
All orders use Math.floor(Date.now() / 1000) as their target timestamp. There is no "latest available price" — every order targets a specific second. This makes retries idempotent: the same timestamp always resolves to the same price.
Pending Order State Machine
When a price is unavailable at order time (common for recent timestamps still being collected), the order enters a state machine that survives DO eviction:
CREATED → PENDING → EXECUTED | FAILEDMargin is reserved at CREATED. PriceAlert fires onPriceArrival() when the timestamp's data becomes available, which triggers execution. This makes the system "eventually correct" — like a limit order that fills when data arrives.
Position Netting Algorithm
Three cases when a new trade interacts with an existing position:
| Scenario | Behavior |
|---|---|
| Same direction | Weighted average entry price, combined size |
| Opposite, partial close | Realize PnL on closed portion, keep original entry, reduce size |
| Opposite, full reversal | Realize PnL on full old position, open new position at current price |
Stale Alert Rejection
Alerts carry an accountId from creation time. When an alert fires after account reset, the current accountId won't match — the alert is rejected as stale. This prevents old alerts from a previous challenge attempt executing on a new account.
Margin Reservation
Limit orders reserve margin at creation (sizeUsd / leverage), blocking it from being used by other trades. This ensures the order can execute when its condition triggers, even if the user has opened other positions since. Reserved margin is released on execution or cancellation.
Margin Modes
Two margin modes: CROSS (default) and ISOLATED. Cannot be changed with open positions or limit orders.
- CROSS:
availableMargin = balance + unrealizedPnL - marginUsed - reservedMargin - ISOLATED:
availableMargin = balance - marginUsed - reservedMargin(ignores unrealized PnL)
Leverage: Strictly Increasing
Integer-only, 1 to token-dependent max (see src/constants/symbols/leverage.ts). Leverage can only increase for an existing position (1x → 20x allowed, 20x → 10x rejected). When opening a new position or limit order at higher leverage, the system auto-adjusts the existing position's leverage first. Higher leverage releases margin and updates the liquidation price. Adding to a position must not cause immediate liquidation.
Challenge Pass Validation
Passing isn't instant — it triggers a price arrival alert to get the latest market prices, then validates all criteria with real account values. This prevents passing when entry-price-based calculations show a pass but real market prices would breach MLL.
Key Constants
| Constant | Value |
|---|---|
| Starting balance | $10,000 |
| Minimum order size | $10 USD (configurable) |
| Idempotency TTL | 24 hours |
| Fee tiers | 7 (volume-based) |
| Volume window | 14-day rolling |
| Challenge plans | FREE, STARTER, STANDARD, PRO |
| WebSocket events | POSITION_OPENED/CLOSED, LIMIT_ORDER_EXECUTED/CANCELLED, CHALLENGE_PASSED/FAILED |
Interactions
| DO | Direction | Purpose |
|---|---|---|
| PriceAlert | ↔ | Price queries (read replica), alert registration/deregistration, onAlert()/onPriceArrival() callbacks |
| AccountValueAggregationTracker | → ← | subscribe(), updateCoefficients(), receives onHourlyExtremes()/onDailyExtremesFinalized() |
| PromoCodeRegistry | → | Promo/referral code validation |
| UserRegistry | → | User registration by wallet/email |
| PublicTradesFeed | → | Publishes closed trades |
| UserPaperTradeLeaderboard | → | Ranking updates on challenge events |
| MllBreachRegistry | → | MLL breach event recording |
| TradeFundAccountRegistry | → | Funded account applications |