541 lines
20 KiB
Markdown
541 lines
20 KiB
Markdown
# Trading Development Plan — Alpaca Data & Execution
|
||
|
||
This document defines how Cyber Hybrid Hub will add **Alpaca market data gathering**, **Alpaca trade actuation**, and a **dynamically configurable data-input layer** that feeds the existing **interval worker** (`QuestionBackgroundWorker`), **Postgres** persistence, **user questions** (SignalR), and **automated trade decisions**.
|
||
|
||
It extends the current architecture documented in `server/README.md` and implemented in `server/lib/pipeline/question_pipeline.dart` — not replace it.
|
||
|
||
---
|
||
|
||
## 1. Goals & constraints
|
||
|
||
| Goal | Detail |
|
||
|------|--------|
|
||
| **Data** | Ingest US equity (and optional crypto) prices from Alpaca Market Data; store normalized snapshots in Postgres for the worker to read. |
|
||
| **Questions** | Worker evaluates stored data + pipeline config → enqueues swipe/numeric questions via `QuestionService` → Flutter receives `ReceiveQuestion` over SignalR. |
|
||
| **Trades** | After rules + optional user confirmation via answers, place orders through Alpaca Trading API (paper first). |
|
||
| **Configuration** | Watchlists, thresholds, and pipeline steps are **DB-driven** (and env defaults), not hard-coded in Dart. |
|
||
| **Security** | Alpaca secret keys live **only** on the Dart API server; Flutter never holds trading credentials. |
|
||
| **Cost** | Target Alpaca **Basic** market data (IEX, ~30 WS symbols) + **paper** trading until strategies are validated. |
|
||
|
||
**Non-goals (initial phases):** sub-second HFT, full SIP market data, client-side WebSockets to Alpaca, or multi-broker routing.
|
||
|
||
---
|
||
|
||
## 2. Fit with the current system
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph flutter [Flutter app]
|
||
UI[HomeScreen / SwipeQuestionTile]
|
||
Hub[QuestionsHubService SignalR]
|
||
end
|
||
|
||
subgraph server [Dart API server]
|
||
API[Shelf handlers]
|
||
Worker[QuestionBackgroundWorker interval]
|
||
MDP[MarketDataPipeline NEW]
|
||
TP[TradingPipeline NEW]
|
||
QP[QuestionPipeline existing]
|
||
QS[QuestionService]
|
||
AlpacaMD[Alpaca Market Data client]
|
||
AlpacaTR[Alpaca Trading client]
|
||
end
|
||
|
||
subgraph pg [Postgres]
|
||
MD[market_data_snapshots]
|
||
CFG[trading_config / user_trading_state]
|
||
Q[questions]
|
||
UPS[user_pipeline_state]
|
||
ORD[trade_orders]
|
||
end
|
||
|
||
AlpacaMD -->|REST poll or WS| MDP
|
||
MDP --> MD
|
||
Worker --> MDP
|
||
Worker --> TP
|
||
Worker --> QP
|
||
TP --> MD
|
||
TP --> CFG
|
||
TP --> ORD
|
||
TP --> AlpacaTR
|
||
QP --> QS
|
||
QS --> Q
|
||
QS --> Hub
|
||
API --> QS
|
||
Hub --> UI
|
||
API --> UI
|
||
```
|
||
|
||
**Existing touchpoints to extend:**
|
||
|
||
| Component | Path | Role |
|
||
|-----------|------|------|
|
||
| Interval worker | `server/lib/workers/question_background_worker.dart` | Orchestrates tick; will call market-data ingest + trading evaluation before/alongside `QuestionPipeline.runMaintenanceCycle`. |
|
||
| Question pipeline | `server/lib/pipeline/question_pipeline.dart` | Add `PipelineKeys.trading` branch; reuse `BranchDecision` for threshold-style answers. |
|
||
| External fetcher pattern | `server/lib/pipeline/external_data_fetcher.dart` | Precedent for HTTP clients; Alpaca clients live in `server/lib/alpaca/`. |
|
||
| Env loading | `server/lib/env.dart` | Add Alpaca + trading feature flags. |
|
||
| Flutter | `lib/services/questions_hub_service.dart`, `lib/widgets/swipe_question_tile.dart` | Unchanged contract: questions in, numeric answers out. |
|
||
|
||
---
|
||
|
||
## 3. Alpaca accounts & APIs
|
||
|
||
Use **one Alpaca account** with two API surfaces:
|
||
|
||
| Surface | Base URL (paper) | Purpose |
|
||
|---------|------------------|---------|
|
||
| **Market Data** | `https://data.alpaca.markets` | Snapshots, bars, trades, quotes; WS `wss://stream.data.alpaca.markets/v2/iex` (Basic). |
|
||
| **Trading** | `https://paper-api.alpaca.markets` | Orders, positions, account; switch to live URL only after explicit env flip. |
|
||
|
||
**Authentication:** Headers `APCA-API-KEY-ID` and `APCA-API-SECRET-KEY` on every request.
|
||
|
||
**Free-tier limits to design around:**
|
||
|
||
- Market Data Basic: **IEX real-time**, **~30 WebSocket subscriptions**, historical REST capped (use DB as source of truth for worker ticks).
|
||
- Trading: paper account; rate limits documented by Alpaca; idempotent `client_order_id` required.
|
||
|
||
---
|
||
|
||
## 4. Dynamically configurable data input design
|
||
|
||
Configuration is split into **global templates** (reusable) and **per-user overrides** (Firebase UID). The worker always resolves effective config before reading market data or emitting questions.
|
||
|
||
### 4.1 Configuration layers
|
||
|
||
```text
|
||
trading_config_templates (optional defaults by name)
|
||
↓ merge
|
||
user_trading_config (per user: watchlist, rules, enabled flag)
|
||
↓ resolved at tick
|
||
EffectiveTradingConfig (in-memory for one worker cycle)
|
||
```
|
||
|
||
### 4.2 Config document shape (JSONB)
|
||
|
||
Store in Postgres as `JSONB`; validate on write via API or seed migrations.
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"enabled": true,
|
||
"mode": "paper",
|
||
"data_inputs": [
|
||
{
|
||
"id": "primary_watchlist",
|
||
"source": "alpaca",
|
||
"asset_class": "us_equity",
|
||
"symbols": ["AAPL", "MSFT", "SPY"],
|
||
"feed": "iex",
|
||
"poll_interval_seconds": 60,
|
||
"metrics": ["last_trade", "daily_bar", "prev_close"]
|
||
}
|
||
],
|
||
"rules": [
|
||
{
|
||
"id": "dip_confirm",
|
||
"type": "price_below_pct_of_ref",
|
||
"symbol": "SPY",
|
||
"ref_metric": "prev_close",
|
||
"threshold_pct": -1.5,
|
||
"question_template": "SPY is down {{pct}}% vs yesterday. Swipe +10 to approve a small buy, -10 to skip.",
|
||
"on_answer_match": { "action": "propose_order", "side": "buy", "notional_usd": 10 }
|
||
}
|
||
],
|
||
"guardrails": {
|
||
"max_orders_per_day": 3,
|
||
"max_notional_usd_per_4h": 100,
|
||
"require_question_before_order": true,
|
||
"symbols_blocklist": []
|
||
}
|
||
}
|
||
```
|
||
|
||
**Design principles:**
|
||
|
||
- **`data_inputs[]`** — what to fetch and how often; worker matches `poll_interval_seconds` against `QUESTION_WORKER_INTERVAL_SECONDS` or stores last fetch time per input id in `user_trading_state.context`.
|
||
- **`rules[]`** — declarative triggers evaluated against **Postgres snapshots**, not live Alpaca on every rule check (decouples question logic from API rate limits).
|
||
- **`question_template`** — supports `{{symbol}}`, `{{price}}`, `{{pct}}` substitution when creating `question_text`.
|
||
- **User answers** map to actions via existing `pipeline_key` / `pipeline_step` / `source_tag` conventions (e.g. `trading:dip_confirm:await_answer`).
|
||
|
||
### 4.3 Config administration
|
||
|
||
| Method | Phase |
|
||
|--------|-------|
|
||
| SQL seed / migration | Phase 1 — dev templates |
|
||
| `PUT /v1/me/trading/config` | Phase 2 — authenticated user override |
|
||
| Admin script | Optional bulk updates |
|
||
|
||
Flutter does **not** edit raw JSON initially; a settings screen can come later. Phase 1 uses server-side defaults keyed by `firebase_uid`.
|
||
|
||
---
|
||
|
||
## 5. Postgres schema (new migration `004_trading.sql`)
|
||
|
||
### 5.1 `market_data_snapshots`
|
||
|
||
Append-only (or upsert latest) normalized facts per symbol.
|
||
|
||
```sql
|
||
CREATE TABLE market_data_snapshots (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
symbol TEXT NOT NULL,
|
||
asset_class TEXT NOT NULL DEFAULT 'us_equity',
|
||
feed TEXT NOT NULL DEFAULT 'iex',
|
||
metric TEXT NOT NULL, -- last_trade | daily_bar | prev_close
|
||
price NUMERIC,
|
||
volume NUMERIC,
|
||
as_of TIMESTAMPTZ NOT NULL,
|
||
raw JSONB, -- optional Alpaca payload fragment
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
|
||
CREATE INDEX market_data_snapshots_symbol_as_of_idx
|
||
ON market_data_snapshots (symbol, as_of DESC);
|
||
```
|
||
|
||
Worker queries: “latest row per `(symbol, metric)`” via `DISTINCT ON` or a materialized view `market_data_latest` (Phase 2 optimization).
|
||
|
||
### 5.2 `trading_config_templates`
|
||
|
||
```sql
|
||
CREATE TABLE trading_config_templates (
|
||
name TEXT PRIMARY KEY,
|
||
config JSONB NOT NULL,
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
```
|
||
|
||
### 5.3 `user_trading_config`
|
||
|
||
```sql
|
||
CREATE TABLE user_trading_config (
|
||
firebase_uid TEXT PRIMARY KEY REFERENCES users (firebase_uid) ON DELETE CASCADE,
|
||
template_name TEXT REFERENCES trading_config_templates (name),
|
||
config JSONB NOT NULL DEFAULT '{}',
|
||
enabled BOOLEAN NOT NULL DEFAULT false,
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
```
|
||
|
||
### 5.4 `user_trading_state`
|
||
|
||
Extends the pipeline state pattern in `user_pipeline_state` for trading-specific cursor.
|
||
|
||
```sql
|
||
CREATE TABLE user_trading_state (
|
||
firebase_uid TEXT PRIMARY KEY REFERENCES users (firebase_uid) ON DELETE CASCADE,
|
||
last_ingest_at TIMESTAMPTZ,
|
||
last_eval_at TIMESTAMPTZ,
|
||
context JSONB NOT NULL DEFAULT '{}',
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
```
|
||
|
||
`context` holds: last fired rule ids, pending order proposal ids, daily order counts, last fetch times per `data_inputs[].id`.
|
||
|
||
### 5.5 `trade_orders`
|
||
|
||
Audit trail and idempotency.
|
||
|
||
```sql
|
||
CREATE TABLE trade_orders (
|
||
id UUID PRIMARY KEY,
|
||
firebase_uid TEXT NOT NULL REFERENCES users (firebase_uid) ON DELETE CASCADE,
|
||
client_order_id TEXT NOT NULL UNIQUE,
|
||
alpaca_order_id TEXT,
|
||
symbol TEXT NOT NULL,
|
||
side TEXT NOT NULL,
|
||
order_type TEXT NOT NULL,
|
||
notional_usd NUMERIC,
|
||
qty NUMERIC,
|
||
status TEXT NOT NULL,
|
||
question_id UUID REFERENCES questions (id),
|
||
rule_id TEXT,
|
||
submitted_at TIMESTAMPTZ,
|
||
filled_at TIMESTAMPTZ,
|
||
raw JSONB,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
```
|
||
|
||
### 5.6 Questions table linkage
|
||
|
||
Reuse existing columns:
|
||
|
||
- `source_tag` — e.g. `trading:rule:dip_confirm`
|
||
- `pipeline_key` — `trading`
|
||
- `pipeline_step` — rule id + phase (`propose`, `confirm`, `idle`)
|
||
|
||
Optional Phase 2: `questions.metadata JSONB` for symbol/price at question time.
|
||
|
||
---
|
||
|
||
## 6. Server modules (proposed layout)
|
||
|
||
```text
|
||
server/lib/
|
||
alpaca/
|
||
alpaca_env.dart # keys, paper vs live, data host
|
||
alpaca_market_data_client.dart
|
||
alpaca_trading_client.dart
|
||
alpaca_models.dart
|
||
trading/
|
||
trading_config.dart # parse/merge JSONB → EffectiveTradingConfig
|
||
trading_config_db.dart
|
||
market_data_ingest.dart # data_inputs → snapshots
|
||
market_data_db.dart
|
||
rule_engine.dart # rules × snapshots → RuleEvaluation
|
||
trading_pipeline.dart # questions + order proposals
|
||
trade_orders_db.dart
|
||
pipeline/
|
||
question_pipeline.dart # wire trading branch in onAnswerSubmitted
|
||
workers/
|
||
question_background_worker.dart # invoke trading tick
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Worker tick sequence (single interval)
|
||
|
||
On each `QuestionBackgroundWorker._tick()` when `TRADING_ENABLED=true`:
|
||
|
||
```text
|
||
1. For each user with user_trading_config.enabled:
|
||
a. Resolve EffectiveTradingConfig (template + user JSON merge).
|
||
b. MarketDataIngest.runIfDue(user, config.data_inputs)
|
||
→ Alpaca REST (or WS service) → INSERT market_data_snapshots
|
||
c. TradingPipeline.evaluate(user, config, snapshots from DB)
|
||
→ if rule fires and guardrails OK:
|
||
- maybe create question (respect maxQueuedQuestions)
|
||
- or stage pending order in user_trading_state.context
|
||
d. QuestionPipeline.runMaintenanceCycle() for same user (existing geography/weather/trading branches)
|
||
|
||
2. For users with pending order + confirmed answer (see §8):
|
||
TradeActuator.submitOrder() → Alpaca Trading → UPDATE trade_orders
|
||
```
|
||
|
||
**Concurrency:** keep `_running` guard; ingest and evaluate per user sequentially first; optimize later.
|
||
|
||
**Alignment with `QUESTION_WORKER_INTERVAL_SECONDS`:** default 60s matches Alpaca Basic REST polling for watchlists ≤30 symbols. A dedicated **long-lived WS ingest task** (optional Phase 3) writes snapshots on every tick and reduces REST calls.
|
||
|
||
---
|
||
|
||
## 8. Question ↔ trade decision flow
|
||
|
||
### 8.1 Phases per rule
|
||
|
||
| Phase | `pipeline_step` | Worker behavior |
|
||
|-------|-----------------|-----------------|
|
||
| `idle` | — | Evaluate rules against DB snapshots only. |
|
||
| `await_confirm` | rule id | Question delivered; `correct_answer` encodes expected confirmation (+10 / -10). |
|
||
| `submit_order` | rule id | User answered; if match and guardrails pass → POST Alpaca order. |
|
||
| `done` | rule id | Log outcome; return to `idle`. |
|
||
|
||
### 8.2 Branching (reuse `BranchDecision`)
|
||
|
||
| User swipe | Meaning | Trade action |
|
||
|------------|---------|--------------|
|
||
| `+10` (yes) | Approve proposed action | `TradeActuator` places configured order |
|
||
| `-10` (no) | Reject | No order; log skip in `user_trading_state.context` |
|
||
| near 0 | Ambiguous | Optional follow-up question (same pattern as weather `close`) |
|
||
|
||
### 8.3 Guardrails (always before Alpaca POST)
|
||
|
||
- `require_question_before_order` and unanswered question must be resolved.
|
||
- `max_orders_per_day` (calendar-day) and `max_notional_usd_per_4h` (rolling 4-hour window) from config. The 4-hour notional window bounds runaway-rule blast radius while still allowing intraday "double-down" sizing as a thesis strengthens.
|
||
- `mode == paper` unless `ALPACA_ALLOW_LIVE=true`.
|
||
- Symbol in user watchlist and not in `symbols_blocklist`.
|
||
- Idempotent `client_order_id` = `uuid` or `{uid}-{rule_id}-{question_id}`.
|
||
|
||
---
|
||
|
||
## 9. Alpaca data gathering (implementation notes)
|
||
|
||
### 9.1 Phase 1 — REST polling (recommended start)
|
||
|
||
For each symbol in `data_inputs[].symbols`:
|
||
|
||
| Metric | Alpaca endpoint (v2) |
|
||
|--------|----------------------|
|
||
| `last_trade` | `GET /v2/stocks/{symbol}/trades/latest` |
|
||
| `daily_bar` | `GET /v2/stocks/bars?symbols=...&timeframe=1Day&limit=1` |
|
||
| `prev_close` | Derived from previous daily bar or snapshot |
|
||
|
||
Batch symbols where possible (`bars` multi-symbol) to minimize calls.
|
||
|
||
### 9.2 Phase 2 — WebSocket ingest service
|
||
|
||
- Connect to `wss://stream.data.alpaca.markets/v2/iex`.
|
||
- Subscribe to union of all enabled users’ symbols (cap 30 on Basic — enforce in config validator).
|
||
- On trade/quote events → upsert `market_data_snapshots` with `as_of = now()`.
|
||
- Worker reads DB only; WS process runs independently inside server isolate or secondary entrypoint.
|
||
|
||
### 9.3 Normalization
|
||
|
||
All ingested values stored as `NUMERIC` in USD where applicable; `raw` JSONB for debugging. Rule engine never parses Alpaca JSON directly — only snapshot rows.
|
||
|
||
---
|
||
|
||
## 10. Alpaca trade actuation (implementation notes)
|
||
|
||
### 10.1 Order types (initial)
|
||
|
||
| Config | Alpaca order |
|
||
|--------|--------------|
|
||
| `notional_usd` | `POST /v2/orders` with `notional`, `side`, `type: market`, `time_in_force: day` |
|
||
| Future: qty | `qty` instead of `notional` |
|
||
|
||
### 10.2 Paper vs live
|
||
|
||
| Env var | Default |
|
||
|---------|---------|
|
||
| `ALPACA_TRADING_BASE_URL` | `https://paper-api.alpaca.markets` |
|
||
| `ALPACA_ALLOW_LIVE` | `false` |
|
||
|
||
Server refuses live URL unless `ALPACA_ALLOW_LIVE=true` and `TRADING_MODE=live` in user config.
|
||
|
||
### 10.3 Post-submit
|
||
|
||
- Poll order status or use Alpaca trade updates (Phase 3).
|
||
- Update `trade_orders.status` and notify user via optional future SignalR event `ReceiveTradeUpdate` (not required for MVP).
|
||
|
||
---
|
||
|
||
## 11. Environment variables
|
||
|
||
Add to `server/.env.example` (document only in plan until implemented):
|
||
|
||
```bash
|
||
# Trading feature gate
|
||
TRADING_ENABLED=false
|
||
TRADING_WORKER_INGEST_ENABLED=true
|
||
TRADING_WORKER_EVAL_ENABLED=true
|
||
|
||
# Alpaca (server only — never in Flutter)
|
||
ALPACA_API_KEY_ID=
|
||
ALPACA_API_SECRET_KEY=
|
||
ALPACA_TRADING_BASE_URL=https://paper-api.alpaca.markets
|
||
ALPACA_DATA_BASE_URL=https://data.alpaca.markets
|
||
ALPACA_DATA_FEED=iex
|
||
ALPACA_ALLOW_LIVE=false
|
||
```
|
||
|
||
Existing worker flags remain:
|
||
|
||
- `QUESTION_WORKER_ENABLED` — master switch for interval timer.
|
||
- `QUESTION_WORKER_INTERVAL_SECONDS` — drives ingest due-times and evaluation frequency.
|
||
- `QUESTION_PIPELINE_TEST_MODE` — when true, skip real Alpaca calls; use fixture snapshots and fake order ids.
|
||
|
||
---
|
||
|
||
## 12. Flutter app responsibilities
|
||
|
||
| Responsibility | Owner |
|
||
|----------------|-------|
|
||
| Display questions, collect swipe/numeric answer | Flutter (`SwipeQuestionTile`) |
|
||
| Submit answer `POST /v1/me/questions/{id}/answer` | Flutter |
|
||
| Real-time question delivery | SignalR `ReceiveQuestion` |
|
||
| Alpaca credentials | **Never on device** |
|
||
| Market data storage | Postgres via server |
|
||
| Trade execution | Server only |
|
||
|
||
Optional later: read-only `GET /v1/me/trading/status` for positions and recent `trade_orders` (no secrets).
|
||
|
||
---
|
||
|
||
## 13. HTTP API additions (Phase 2+)
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| `GET` | `/v1/me/trading/config` | Return merged effective config (sanitized). |
|
||
| `PUT` | `/v1/me/trading/config` | Update user JSONB override. |
|
||
| `GET` | `/v1/me/trading/orders` | List `trade_orders` for user. |
|
||
| `GET` | `/v1/me/trading/snapshots?symbol=SPY` | Debug/latest metrics (auth required). |
|
||
|
||
Phase 1 can omit public endpoints and use SQL seeds for test users.
|
||
|
||
---
|
||
|
||
## 14. Implementation phases
|
||
|
||
### Phase 1 — Foundation (MVP)
|
||
|
||
- [ ] Migration `004_trading.sql`
|
||
- [ ] `AlpacaMarketDataClient` + `AlpacaTradingClient` (paper, REST only)
|
||
- [ ] `MarketDataIngest` + `market_data_snapshots` writes
|
||
- [ ] `TradingConfig` resolver + seed template `default_paper_watchlist`
|
||
- [ ] `RuleEngine` with one rule type: `price_below_pct_of_ref`
|
||
- [ ] `TradingPipeline.evaluate` → create question via `QuestionService`
|
||
- [ ] Extend `QuestionPipeline.onAnswerSubmitted` for `PipelineKeys.trading`
|
||
- [ ] `TradeActuator` on confirm → `trade_orders` + Alpaca POST
|
||
- [ ] Wire into `QuestionBackgroundWorker` tick behind `TRADING_ENABLED`
|
||
- [ ] `QUESTION_PIPELINE_TEST_MODE` fixtures for CI/local without Alpaca keys
|
||
|
||
### Phase 2 — Configuration API & hardening
|
||
|
||
- [ ] `PUT/GET /v1/me/trading/config`
|
||
- [ ] Config validation (symbol cap 30, required fields)
|
||
- [ ] `market_data_latest` view or cached latest query
|
||
- [ ] Daily guardrail counters in `user_trading_state.context`
|
||
- [ ] Order status polling + failed order user question
|
||
|
||
### Phase 3 — Streaming & observability
|
||
|
||
- [ ] Alpaca IEX WebSocket ingest sidecar
|
||
- [ ] `ReceiveTradeUpdate` SignalR event (optional)
|
||
- [ ] Metrics logging (ingest lag, rule fires, order latency)
|
||
|
||
---
|
||
|
||
## 15. Testing strategy
|
||
|
||
| Layer | Approach |
|
||
|-------|----------|
|
||
| Unit | `RuleEngine` with fixture snapshot rows; no network. |
|
||
| Integration | Alpaca paper account; verify order appears in Alpaca dashboard. |
|
||
| Worker | Run server with `QUESTION_PIPELINE_TEST_MODE=true` and `TRADING_ENABLED=true` using seeded snapshots. |
|
||
| Flutter | Existing question flow; manual swipe on trading copy. |
|
||
|
||
**Safety checklist before live:**
|
||
|
||
- [ ] `ALPACA_ALLOW_LIVE=false` in production until explicit review
|
||
- [ ] Guardrail notional limits enforced in code, not only config
|
||
- [ ] Every order traceable to `question_id` + `rule_id`
|
||
|
||
---
|
||
|
||
## 16. Risks & mitigations
|
||
|
||
| Risk | Mitigation |
|
||
|------|------------|
|
||
| Stale DB snapshots | Store `as_of`; rules ignore data older than `max_staleness_seconds` in config. |
|
||
| Alpaca rate limits | Batch REST; cap symbols; WS in Phase 3. |
|
||
| Duplicate orders | `client_order_id` UNIQUE; check `trade_orders` before POST. |
|
||
| User confusion on auto-trades | Clear `question_text`; default `require_question_before_order: true`. |
|
||
| Regulatory / suitability | Paper only initially; disclaimers in app; no investment advice in question copy. |
|
||
|
||
---
|
||
|
||
## 17. Example end-to-end scenario
|
||
|
||
1. **Seed** `user_trading_config` for user U: enabled, `SPY` in watchlist, `dip_confirm` rule at -1.5% vs `prev_close`.
|
||
2. **Worker tick (T0):** ingest writes `prev_close=500`, `last_trade=492` → rule fires.
|
||
3. **Worker:** queue question: “SPY is down 1.6% … +10 approve buy $10, -10 skip” with `pipeline_key=trading`, `pipeline_step=dip_confirm:await_confirm`.
|
||
4. **Flutter:** user swipes +10 → `POST .../answer`.
|
||
5. **Pipeline:** `onAnswerSubmitted` → `TradingPipeline` sees match → `TradeActuator` POST paper market order $10 SPY → row in `trade_orders`.
|
||
6. **Next tick:** rule not re-fired (cooldown in `user_trading_state.context`).
|
||
|
||
---
|
||
|
||
## 18. References
|
||
|
||
- [Alpaca Market Data API](https://docs.alpaca.markets/docs/market-data-api)
|
||
- [Alpaca Trading API](https://docs.alpaca.markets/docs/trading-api)
|
||
- [Alpaca WebSocket streaming](https://docs.alpaca.markets/docs/streaming-market-data)
|
||
- Existing pipeline: `server/lib/pipeline/question_pipeline.dart`
|
||
- Existing worker: `server/lib/workers/question_background_worker.dart`
|
||
|
||
---
|
||
|
||
*Document version: 1.0 — Alpaca unified data + execution, Postgres-backed configurable inputs, interval worker integration.*
|