Order Execution Lifecycle¶
Orders enter the system through three paths: TradingView webhooks (automated alerts from Pine Script strategies), dashboard UI (manual orders placed by users), and background fill verification (Lambda-driven status polling). All three paths converge at the per-user trading worker — a dedicated ECS task that maintains broker connections and executes orders. The API layer never touches broker SDKs directly; every order flows through Redis queues to the user's isolated worker.
End-to-end timing
Webhook to broker: typically < 2 seconds when the worker pool is warm (897ms pool claim + broker API call). Fill verification: asynchronous, 30–60 seconds after order placement via SQS FIFO and Lambda. Auto-reversal (closing opposite position before opening new one): adds 1–5 seconds for the close leg fill confirmation, with a 5-second abort timeout to prevent double exposure.
Three Execution Paths¶
Webhook Order Flow¶
The webhook path handles automated orders from TradingView alerts. This is the primary execution path for production trading.
Step-by-Step Breakdown¶
| Step | Component | Action | Failure Mode |
|---|---|---|---|
| 1 | TradingView | Fires alert with action, symbol, quantity, timestamp, secret | Alert misconfigured → no request sent |
| 2 | WAF | Rate limit, IP filter, managed rules (TradingView IPs exempted) | Blocked → 403 |
| 3 | ALB | TLS termination, health check routing | Unhealthy target → 502 |
| 4 | FastAPI | 4-layer validation: token → timestamp → secret → replay | Any failure → 401/403 |
| 5 | FastAPI | Subscription check, daily order limit | Over limit → 429 |
| 6 | FastAPI | Credential verification, ToS acceptance | Not verified → 403 |
| 7 | Redis | Check worker:active:{user_id} |
Redis down → 503 |
| 8 | SQS → Lambda | Start worker via pool claim (897ms) or RunTask fallback (3.6s) | All fail → 503 (worker unavailable) |
| 9 | Redis | Push order to per-user queue | Redis down → 503 |
| 10 | Worker | Pick up order via BLPOP | Worker crashed → request times out |
| 11 | Broker | Execute order on broker API | Broker rejects → error in OrderResult |
| 12–13 | Worker → Redis | Write response | Response lost → API times out → 202 |
| 14 | FastAPI | BLPOP on response key (30s timeout) | Timeout → 202 Accepted (order may still fill) |
| 15 | FastAPI → TV | Return result | TradingView has 10s limit |
| 16–18 | SQS → Lambda | Background fill verification | Retry via SQS visibility timeout |
Dashboard Order Flow¶
The dashboard path handles manual orders placed through the trading UI. Authentication uses JWT (HttpOnly cookies) + CSRF tokens instead of webhook validation.
| Difference from Webhook | Dashboard | Webhook |
|---|---|---|
| Authentication | JWT cookie + CSRF token | Webhook token + secret |
| Validation layers | 2 (JWT + CSRF) | 4 (token + timestamp + secret + replay) |
| Order source | source="dashboard" |
source="webhook" |
| Symbol validation | User selects from loaded symbols | Must be in allowed list |
| Rate limiting | Standard API rate limit | Per-webhook daily order limit |
Fill Verification¶
After every order placement, a background Lambda verifies the fill status by polling the broker API. This handles the gap between order submission and fill confirmation — especially important for brokers with asynchronous fill reporting.
Verification Details¶
| Parameter | Value |
|---|---|
| Queue | SQS FIFO (order-tasks.fifo) |
| Trigger | Lambda (order_tasks) |
| Initial delay | 2–3 seconds after order placement |
| Retry interval | 30s visibility timeout |
| Max retries | 3 (then moves to DLQ) |
| Partial fill handling | Re-queues with updated filled quantity |
Why SQS FIFO?
FIFO ordering ensures fill checks for the same user happen sequentially (MessageGroupId = user_id). This prevents race conditions where two concurrent verifications could write conflicting fill data. The 180-second visibility timeout gives the Lambda enough time to poll the broker and update the database.
Auto-Reversal¶
When a long_entry signal arrives but the user holds a short position, the system automatically closes the short before opening the long. This mirrors TradingView's strategy.entry() behavior where entering a position in the opposite direction reverses the existing one.
Reversal Matrix¶
| Current Position | Signal | Step 1 | Step 2 | Final Position |
|---|---|---|---|---|
| None | long_entry (buy 1) |
— | Buy 1 | +1 (long) |
| +1 (long) | long_entry (buy 1) |
— | Buy 1 | +2 (long) |
| -2 (short) | long_entry (buy 1) |
Close short (buy 2) | Buy 1 | +1 (long) |
| None | short_entry (sell 1) |
— | Sell 1 | -1 (short) |
| +3 (long) | short_entry (sell 1) |
Close long (sell 3) | Sell 1 | -1 (short) |
| -1 (short) | short_entry (sell 1) |
— | Sell 1 | -2 (short) |
Supported Actions¶
| Action | Description | Queue Operation | Broker Method |
|---|---|---|---|
long_entry |
Open or add to a long position | PLACE_ENTRY_ORDER (action=buy) |
place_entry_order_with_reversal() |
long_exit |
Close an existing long position | PLACE_EXIT_ORDER (direction=long) |
place_exit_order() |
short_entry |
Open or add to a short position | PLACE_ENTRY_ORDER (action=sell) |
place_entry_order_with_reversal() |
short_exit |
Close an existing short position | PLACE_EXIT_ORDER (direction=short) |
place_exit_order() |
Webhook Action Mapping¶
TradingView alerts use a simplified action format that maps to internal operations:
Alert action |
order_type |
Internal Operation |
|---|---|---|
buy |
entry (default) |
PLACE_ENTRY_ORDER (action=buy) |
sell |
entry (default) |
PLACE_ENTRY_ORDER (action=sell) |
buy |
exit |
PLACE_EXIT_ORDER (direction=short) |
sell |
exit |
PLACE_EXIT_ORDER (direction=long) |
close_long |
— | CLOSE_POSITION |
close_short |
— | CLOSE_POSITION |
Error Handling¶
Broker Rejection¶
| Rejection Reason | System Response |
|---|---|
| Insufficient margin | Return error to caller, log to order_history |
| Market closed | Return error with market hours information |
| Invalid symbol | Return error, symbol not found in broker contracts |
| Rate limited by broker | Retry after broker-specified delay |
| Position mode mismatch (crypto) | Return error: "Switch to one-way mode" |
Worker Unavailable¶
| Failure Scenario | Behavior |
|---|---|
| Worker not running | Automatic start via pool claim (897ms) or RunTask (3.6s) |
| Worker start timeout (60s) | 503 returned, user retries |
| Worker crashed mid-request | Request times out (30s), 202 returned |
| Worker disconnected from broker | Auto-reconnect on next request (up to 3 retries) |
| Redis unavailable | 503 returned, all order paths blocked |
Timeout Behavior¶
| Timeout | Duration | Outcome |
|---|---|---|
| BLPOP response timeout | 30s | API returns 202 Accepted (order may still fill) |
| Worker idle timeout | 12h (configurable) | Worker shuts down, next request starts a new one |
| SQS visibility timeout (order-tasks) | 180s | Lambda has 3 minutes to verify fill |
| Reversal fill poll timeout | 5s (10 × 0.5s) | Abort reversal, return error |
The 202 Accepted case
When the API times out waiting for the worker response, it returns 202 Accepted — meaning the order was submitted but the result is unknown. The background fill verification Lambda will eventually determine the outcome and update the order_history record. The frontend should poll /trading/orders/{id} to get the final status.
Order History¶
Every order is recorded in the order_history table:
| Field Group | Fields |
|---|---|
| Request | symbol, action, quantity, order_type, source (webhook/dashboard) |
| Result | order_id, broker_refs, status, submitted_at |
| Fill | fill_price, fill_quantity, filled_at (updated by verification) |
| Reversal | auto_exit, exit_quantity, position_before, exit_order_result |
| Metadata | user_id, trading_account_id, broker_name, simulation |
File Map¶
| Concept | File |
|---|---|
| Webhook endpoint + validation | app/routers/webhook.py |
| Dashboard trading endpoints | app/routers/trading.py |
| Webhook request schema | app/schemas/webhook.py |
| Queue client (API → Worker) | app/services/trading_queue.py |
| Trading worker (broker execution) | app/services/trading_worker.py |
| Order verification service | app/services/order_verification.py |
| Lambda: order_tasks (fill verification) | lambda/order_tasks.py |
| Lambda: worker_control (lifecycle) | lambda/worker_control.py |
| Order history model | app/models/models.py (OrderHistory) |
| Trading operations enum | app/enums.py (TradingOperation) |
| SQS queue definitions | terraform/sqs.tf |