Manual
You execute every trade by hand. Highest skill ceiling, lowest leverage.
Human error, emotional override, missed alerts while away.
Baseline — your R:R is entirely execution-dependent.
Everything on this page is published for educational and informational purposes only. Nothing here is investment, financial, legal, tax, or trading advice, a recommendation to buy or sell any security or contract, or a solicitation of any kind. Trading futures, options, equities, and crypto involves substantial risk of loss and is not suitable for every investor. Past performance — including any backtests, demos, or examples shown — does not guarantee future results. Consult a licensed professional before acting on anything you read here.
A complete, free guide to building trading automation that doesn’t orphan positions, fire duplicate orders, or silently lose money. Five stages. Seven-layer stack. Twelve weeks. No upsell.
Enter your email to unlock the PDF downloads instantly. Same email gate as the book — if you already unlocked that, refresh to see it here.
// we respect your inbox · unsubscribe anytime
Three moving parts. One terminal window. By the end of this section you will see structured JSON alerts from TradingView land in your own log file — the first real piece of automation infrastructure most people never build.
Save webhook_log.py, install Flask, run it on :5000. Expose it to the public internet with ngrok in one command.
Paste the Pine snippet into TradingView → Pine Editor. Create an alert pointing to your ngrok URL. Use bar-close frequency.
Tail the log. Every bar-close that triggers your condition will land as a structured JSON line you can grep, parse, or pipe to a broker.
| 1 | # webhook_log.py — Minimal Flask receiver that logs every alert |
| 2 | # Run: pip install flask && python webhook_log.py |
| 3 | # Then expose with ngrok: ngrok http 5000 |
| 4 | |
| 5 | import json, time |
| 6 | from flask import Flask, request |
| 7 | |
| 8 | app = Flask(__name__) |
| 9 | |
| 10 | @app.post("/webhook/signal") |
| 11 | def signal(): |
| 12 | payload = request.get_json(silent=True) or {} |
| 13 | payload["received_at"] = time.time() |
| 14 | # Structured log line — one JSON object per request |
| 15 | print(json.dumps(payload), flush=True) |
| 16 | return {"status": "logged"} |
| 17 | |
| 18 | if __name__ == "__main__": |
| 19 | app.run(port=5000) |
| 1 | // pine_alert.pine — Alert with structured JSON payload |
| 2 | // Run at bar close to avoid re-paints |
| 3 | |
| 4 | //@version=5 |
| 5 | strategy("Asymmetric Entry v1", overlay=true, calc_on_every_tick=false) |
| 6 | |
| 7 | // — Inputs |
| 8 | rsi_len = input.int(14, "RSI Length") |
| 9 | ema_fast = input.int(9, "Fast EMA") |
| 10 | ema_slow = input.int(21, "Slow EMA") |
| 11 | risk_pct = input.float(1.0, "Risk %", minval=0.1, maxval=5.0) |
| 12 | |
| 13 | // — Indicators |
| 14 | rsi = ta.rsi(close, rsi_len) |
| 15 | ema_f = ta.ema(close, ema_fast) |
| 16 | ema_s = ta.ema(close, ema_slow) |
| 17 | |
| 18 | // — Entry conditions |
| 19 | long_cond = ta.crossover(ema_f, ema_s) and rsi < 60 |
| 20 | short_cond = ta.crossunder(ema_f, ema_s) and rsi > 40 |
| 21 | |
| 22 | // — Structured JSON payload for webhook receiver |
| 23 | if long_cond |
| 24 | alert( |
| 25 | str.format( |
| 26 | "{"action":"buy","symbol":"{0}","price":{1}," + |
| 27 | ""risk_pct":{2},"bar_time":"{3}"}", |
| 28 | syminfo.ticker, close, risk_pct, str.tostring(time) |
| 29 | ), |
| 30 | alert.freq_once_per_bar_close // no re-paints |
| 31 | ) |
| 32 | strategy.entry("Long", strategy.long) |
| 33 | |
| 34 | if short_cond |
| 35 | alert( |
| 36 | str.format( |
| 37 | "{"action":"sell","symbol":"{0}","price":{1}," + |
| 38 | ""risk_pct":{2},"bar_time":"{3}"}", |
| 39 | syminfo.ticker, close, risk_pct, str.tostring(time) |
| 40 | ), |
| 41 | alert.freq_once_per_bar_close |
| 42 | ) |
| 43 | strategy.entry("Short", strategy.short) |
| 1 | // stdout: tail -f webhook.log |
| 2 | {"action":"buy","symbol":"SPY","price":523.41,"risk_pct":1.0,"bar_time":"1716662400","received_at":1716662400.412} |
| 3 | {"action":"sell","symbol":"SPY","price":524.18,"risk_pct":1.0,"bar_time":"1716663300","received_at":1716663300.221} |
| 4 | // latency from bar close → receiver: ~400ms |
| 5 | // dedup key: sha256(action:symbol:bar_time)[:24] |
| 6 | // next: replace 'print' with submitOrder() in webhook.js |
brew install ngrok or download from ngrok.com (free tier is fine)ngrok http 5000 → copies a public https URL into your clipboardThe receiver above is great for understanding the moving parts. When you want the full version — both Python FastAPI and Node Express, the Pine snippet, a shared .env schema, a notional cap, and a curl-based test script — clone the companion repo:
git clone https://github.com/JasonTeixeira/nexural-automation-starter.git
cd nexural-automation-starter/python
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp ../.env.example .env # edit WEBHOOK_SECRET
uvicorn app:app --reload --port 8000Prefer JavaScript? cd node && npm install && node server.js — same endpoints, same env file, same outcome.
Ready to swap print() for an actual broker call? That’s the next 10 minutes ↓
One free account. Two API keys. Twelve lines of Python. By the end of this section a real broker will have accepted a real order against a real market — with zero capital at risk.
Sign up at alpaca.markets, verify your email, switch to Paper Trading. No funding, no SSN, no waiting period.
Paper Trading → API Keys → Generate. Export them as ALPACA_KEY and ALPACA_SECRET environment variables. Never commit them.
pip install alpaca-py, paste the 12-line script, run it during US market hours. Watch the order appear in the paper dashboard.
| 1 | # first_paper_order.py — 12 lines from "have keys" to "got a fill" |
| 2 | # Run: pip install alpaca-py |
| 3 | # Get free paper keys at https://alpaca.markets (Paper trading → API keys) |
| 4 | |
| 5 | import os |
| 6 | from alpaca.trading.client import TradingClient |
| 7 | from alpaca.trading.requests import MarketOrderRequest |
| 8 | from alpaca.trading.enums import OrderSide, TimeInForce |
| 9 | |
| 10 | client = TradingClient( |
| 11 | api_key=os.environ["ALPACA_KEY"], |
| 12 | secret_key=os.environ["ALPACA_SECRET"], |
| 13 | paper=True, # paper account — no real money at risk |
| 14 | ) |
| 15 | |
| 16 | order = client.submit_order(MarketOrderRequest( |
| 17 | symbol="SPY", qty=1, side=OrderSide.BUY, time_in_force=TimeInForce.DAY, |
| 18 | )) |
| 19 | print(f"submitted: {order.id} — status={order.status}") |
| 1 | // stdout: python first_paper_order.py |
| 2 | submitted: 9c4f2e1a-... — status=OrderStatus.ACCEPTED |
| 3 | |
| 4 | // 200ms later, from your Alpaca paper dashboard: |
| 5 | // SPY 1 share filled @ 523.42 fee $0.00 |
| 6 | // buying_power: $99,476.58 positions: 1 |
| 7 | // |
| 8 | // you just placed a real order through a real broker API. |
| 9 | // the only difference between this and live: paper=False + funded account. |
The Alpaca paper environment is the production code path with paper=True. Every error you can hit live, you can hit here — invalid quantity, wrong session, rate-limit — with no dollar consequence.
Inside webhook_log.py, replace the print() with the Alpaca submit_order() call. That single substitution turns the receiver into a live signal→broker pipeline.
Every trader starts at Stage 1. The goal isn’t to reach Stage 5 as fast as possible — it’s to advance only when the current stage is solid. Each stage compounds the previous one.
You execute every trade by hand. Highest skill ceiling, lowest leverage.
Human error, emotional override, missed alerts while away.
Baseline — your R:R is entirely execution-dependent.
TradingView alerts ping your phone. You still pull the trigger.
Phone not in hand, notification fatigue, slippage from manual lag.
+0.1–0.2 R avg: alerts enforce entry discipline even when distracted.
Alerts hit a webhook → broker submits limit orders. You manage exits manually.
Silent rejections, duplicate orders, no exit logic when phone is off.
+0.2–0.3 R avg: consistent entries eliminate FOMO slippage.
Strategy code owns entries, exits, sizing. You monitor and intervene.
Overfit backtest, regime shift, orphaned positions on crash.
+0.3–0.5 R avg: removes all emotional override from execution.
Strategy detects regime shifts, auto-pauses, self-rebalances. Audit logs everything.
Tail-risk correlation breakdown, black swans, exchange-level failures.
+0.5–0.8 R avg vs baseline: compounding discipline over full market cycles.
Before the matrices and the modules, see the territory. Every diagram is interactive — click anything to expand the underlying definition, code, or trade-off. Nothing here requires a login or a download.
Pre-trade checks: daily loss, max position, account heat, kill switch.
Multiple gate reads against state store.
Stale state — risk values from a previous bar block a valid trade.
Read state atomically. Use Redis for hot risk counters.
Pick the asset class you spend most of your time on.
From signal-fired to broker-ack. Click any segment to see what costs the latency and how a pro shop would cut it.
The trading platform serializes your alert into JSON and POSTs it to your handler URL.
TradingView batches webhooks through their servers in Ireland and queues during volatility. Cold TLS handshakes add 100+ ms.
TradingView → custom webhook handler in us-east-1 typically lands at 120–180 ms.
Drop webhooks entirely and run a Python or C# strategy in-process. If you must use webhooks, terminate TLS in the same region as the platform.
2.0 ms median · 10 ms worst
Every order runs this gauntlet. Click a gate to see the one-line rule, the failure mode it prevents, and the metric you should already be alerting on.
Notional ≤ per-trade cap, post-fill position ≤ per-instrument cap, and broker buying power covers it.
Almost every retail blow-up story starts here. A divide-by-zero in volatility produces an insane size, the strategy fires, and the broker happily fills it because buying power exists.
A single bad signal sizes a position the account cannot survive. Margin calls within minutes.
if order.notional > limits.per_trade_max:
order.qty = math.floor(limits.per_trade_max / order.price)
audit.warn('size_capped', orig=payload.qty, capped=order.qty)Order moves to daily loss check.
Halve the size or reject entirely. Never round up to fit the cap — that hides the bug.
Histogram: order_notional_distribution. Alert on p99 > 3× p50 — the strategy is sizing erratically.
Most automation failures aren’t strategy failures — they’re infrastructure failures. The stack below maps every layer, what it does, what breaks there, and which tools to use.
No rankings. No affiliate links. Emerald = good, amber = partial, rose = limitation. Use the filter chips to narrow by asset class, language, or cost.
Start with TradingView for signals and Alpaca for execution. Once profitable on paper, evaluate NT8 or IBKR for lower fees and deeper order types.
| Platform | Language | Asset Class | Paper Trading | Real-Money Cost | Auditability | Where It Breaks | Who It's For |
|---|---|---|---|---|---|---|---|
NinjaTrader 8 deep dive → | C# (NinjaScript) | Futures, FX | Yes (Simulation) | $50–100/mo or one-time | Full strategy logs | Windows-only, NT8 version lock | Futures traders with code skills |
TradingView + Webhooks deep dive → | Pine Script | All (view only) | Paper mode only | $15–60/mo plan | Limited | Requires external executor, delay free plan | Signal builders, no-code first step |
MetaTrader 5 | MQL5 | FX, Stocks, Futures | Yes (Demo) | Free (broker charges) | Trade log only | Limited crypto, old UI paradigm | FX algo traders, legacy systems |
IBKR API deep dive → | Python, Java, C++ | Stocks, Options, Futures, FX | Yes (Paper acct) | ~$0.005/share | TWS + flex reports | Complex auth, TWS must run | Multi-asset professionals |
Alpaca deep dive → | Python, JS, REST | Stocks, Crypto | Yes (free) | Free (spread) | API event log | US stocks only, no options | Equity algo starters |
ProjectX | REST, WebSocket | Futures | Yes | $0–30/mo | Trade history API | Smaller ecosystem, fewer docs | Futures API first-timers |
Tradovate | REST/WebSocket | Futures only | Yes | $0–2/contract | REST history endpoint | Futures only, rate limits | Pure futures developers |
QuantConnect / Lean deep dive → | Python, C# | Stocks, Options, Futures, Crypto | Yes (paper brokerage) | $0 cloud or self-host | Full backtest + live logs | Cloud latency, data costs add up | Systematic quants, researchers |
Backtrader | Python | Stocks, Futures (data-agnostic) | Yes (built-in sim) | Free (open source) | Strategy log + analyzers | No live data, community-maintained | Python devs learning backtesting |
Lean (standalone) | Python, C# | All asset classes | Yes | Free (self-host) | Comprehensive logs | Docker setup complexity | Devs wanting local QuantConnect |
cTrader / cAlgo deep dive → | C# (cAlgo) | FX, CFDs, Crypto | Yes (Demo) | Free (broker spread) | Full execution log | Broker-specific availability | FX devs wanting modern API |
Sierra Chart | C++ (ACSIL) | Futures, Stocks, FX | Yes (Sim) | $26–54/mo | Order log + journal | Steep learning curve, dated UI | Pro futures traders, latency-sensitive |
MultiCharts | EasyLanguage, C# | Stocks, Futures, FX | Yes | $1,497 once or $97/mo | Strategy log + reports | Windows-only, premium pricing | Migrants from TradeStation |
TradeStation | EasyLanguage | Stocks, Options, Futures | Yes (Sim) | $0–9.99/mo + commissions | Trade history + analytics | EasyLanguage lock-in, US-centric | Legacy retail systematic traders |
MetaTrader 4 | MQL4 | FX, CFDs | Yes (Demo) | Free (broker spread) | Trade log only | Aging platform, no stocks/futures | Retail FX, legacy EAs |
ThinkOrSwim | thinkScript | Stocks, Options, Futures, FX | Yes (paperMoney) | Free (Schwab account) | Limited — study log only | No real automation — alerts only | Options traders, study authors |
// emerald = good · amber = partial/caveat · rose = limitation · no rankings, no affiliates
Crypto demands different criteria: spot vs perps, KYC, taker fee, settlement model. Filter by product type, jurisdiction, or stack. Self-custody beats exchange custody when you can.
| Venue | Type | Products | API | KYC | Taker Fee | Rate Limit | Custody | Where It Breaks |
|---|---|---|---|---|---|---|---|---|
| Coinbase Advanced | CEX | Spot | REST, WebSocket, FIX | Required (US-friendly) | 0.40–0.80% | 30 req/s | Exchange-custodied | No perpetuals on US accounts |
| Kraken | CEX | Spot, Futures | REST, WebSocket | Required (Pro tier) | 0.16–0.26% | 15–20 req/s | Exchange-custodied | Lower liquidity vs Binance |
| Binance | CEX | Spot, Perpetuals, Futures, Options | REST, WebSocket, FIX | Required (non-US) | 0.04–0.10% | 1200/min weight-based | Exchange-custodied | Banned for US users; geo issues |
| Bybit | CEX | Spot, Perpetuals, Options | REST, WebSocket | Tiered (verify for higher limits) | 0.055–0.10% | 120 req/s burst | Exchange-custodied | US restricted, regional access varies |
| OKX | CEX | Spot, Perpetuals, Futures, Options | REST, WebSocket | Required (jurisdiction-based) | 0.05–0.10% | 20 req/2s per endpoint | Exchange-custodied | US restricted; complex API surface |
| CCXT (multi-venue) | Broker | Spot, Derivatives (varies) | Python, JS, PHP | Per underlying exchange | Pass-through | Per underlying exchange | Per underlying exchange | Abstraction lag; new features take time |
| Hyperliquid | DEX | Perpetuals | REST, WebSocket | None (wallet-based) | 0.035% | Generous, per-wallet | Self-custody (on-chain) | Single-chain; smart-contract risk |
| dYdX v4 | DEX | Perpetuals | REST, gRPC, WebSocket | None (wallet-based) | 0.025–0.05% | Per-IP and per-wallet | Self-custody (on-chain) | Cosmos app-chain; bridging required |
// cyan = CEX · lime = DEX · magenta = broker abstraction · self-custody > exchange custody when you can
All 16 platforms and 8 crypto venues in one branded PDF you can keep open while you decide. Includes a decision tree, language quick-pick, three runnable code samples (cAlgo, CCXT, Tradovate), and a clickable source list.
Each module builds on the previous. Check off deliverables as you go — progress is saved locally. Week 4 adapts to your track selection above.
Same destination, different on-ramps. Pick the one that fits the time you actually have right now.
Get one real signal landing in a log file, then one real paper order through Alpaca. Sections 2.5a + 2.5b.
Cover weeks 1–3: market model, strategy anatomy, and the full TradingView → webhook → log loop. Reference the 7-layer stack and platform matrix as you go.
Work the full curriculum below — from auction theory to live capital. Each weekly module ships with deliverables you can check off in place.
Build the mental model: price is the output of an auction, not a random walk. Value areas, point of control, and what buyers vs sellers control at each bar.
Decompose any strategy into its atomic units: signal → filter → size → entry → exit → risk. Every decision point is a potential failure mode. Map them all before writing a line of code.
Get your first alert triggering a real action outside TradingView. Pine Script → JSON payload → webhook receiver → log file. The full signal pipeline in 7 days.
Fill the gap in your weakest skill set. Traders learn pandas, NumPy, and basic data manipulation. Coders learn market structure, order types, and how fills actually work.
Understand delta, footprint, cumulative delta, and how to use them as filters — not signals. Order flow confirms or invalidates what price structure is suggesting.
Run a rigorous backtest with realistic assumptions, no look-ahead bias, and honest transaction costs. Define in-sample and out-of-sample before touching the data.
Build the risk layer from the ground up: R:R gate, position sizing via Kelly fraction, daily loss limit, and expectancy. The math that decides whether your system survives.
Connect to a paper trading account and submit your first programmatic order. Test every order type, every rejection scenario, and every partial fill before touching real capital.
Build the full TradingView → webhook → broker pipeline, end-to-end. Deduplication, payload validation, error handling, and a round-trip latency target under 2 seconds.
Build the observability layer: structured JSON logs, real-time P&L dashboard, trade journal that records entry, exit, R:R, and context for every trade.
Harden the system with kill switches, deadman switch, and drawdown circuit breakers. Every one of them must be tested before going live — not just coded.
Structured go-live with minimum viable capital and full monitoring in place. Two weeks of paper in live-hours conditions. Start with ≤ 25% of intended capital. No strategy changes for 30 days.
10-Check Gauntlet · HMM regime detection · HERC portfolio optimization · MLflow experiment tracking
How to build a production-grade strategy validation pipeline with statistical rigor.
Event-sourced order state machine with 37 tests, fill simulation, and position management.
How a professional order management system handles every state transition.
74 trading skills · 29 swarm agent presets · MCP plugin architecture · LLM-assisted analysis
How to augment quantitative analysis with language model reasoning.
50+ metric analyzer · Monte-Carlo simulation · overfitting detection · strategy scoring
How to systematically score and compare strategies before allocating capital.
Multi-agent LLM system with analysts, trader, risk manager, and portfolio manager roles · LangGraph
How to implement multi-agent systems for trading research and risk review.
Python-based crypto trading bot with Dry-run mode, backtesting, and Telegram integration.
End-to-end production crypto automation with community strategies.
Python backtesting library with Cerebro engine, data feeds, analyzers, and live broker adapters.
Event-driven backtesting architecture — the right mental model for avoiding look-ahead bias.
Institutional-grade algorithmic trading engine supporting Python and C# across all asset classes.
How a professional quant engine handles data, execution, and portfolio attribution.
Maintained fork of Zipline, Quantopian's Python backtesting library.
Pipeline API for factor-based equity research and portfolio construction.
High-performance vectorized backtesting library with Numba acceleration and portfolio analytics.
How to run 1,000+ parameter combinations in seconds using vectorization.
High-performance live trading platform with Rust core and Python API, designed for HFT workloads.
Ultra-low-latency architecture decisions and why Python alone has limits.
Clean Python framework for crypto strategy development, backtesting, and live trading.
Minimal, readable automation code — what production-ready crypto bots look like.
Market-making and arbitrage automation framework for crypto across 40+ exchanges.
Market microstructure from the maker side: spread capture, inventory management.
Every successful automated trader builds risk controls first and strategy logic second. The Risk Layer isn’t a feature — it’s the operating system. These five code patterns represent the minimum viable safety net before any strategy touches real capital.
Every order submission must pass three gates: minimum R:R ratio, maximum position size as a percentage of account equity, and the daily loss limit check. If any gate fails, the function returns a rejection reason and the order never reaches the broker.
| 1 | # risk_gate.py — Pre-trade risk validation |
| 2 | # Rejects any trade that fails R:R, size, or daily-loss-limit checks |
| 3 | |
| 4 | def pre_trade_risk_check( |
| 5 | account_equity: float, |
| 6 | entry_price: float, |
| 7 | stop_price: float, |
| 8 | target_price: float, |
| 9 | proposed_size: float, |
| 10 | daily_pnl: float, |
| 11 | daily_loss_limit: float = -0.02, # -2% account |
| 12 | max_risk_per_trade: float = 0.02, # 2% account |
| 13 | min_rr_ratio: float = 2.0, |
| 14 | ) -> tuple[bool, str]: |
| 15 | """ |
| 16 | Returns (approved, reason). Raise before any order submission. |
| 17 | """ |
| 18 | # 1. R:R check |
| 19 | risk_per_unit = abs(entry_price - stop_price) |
| 20 | reward_per_unit = abs(target_price - entry_price) |
| 21 | if risk_per_unit == 0: |
| 22 | return False, "Stop equals entry — invalid trade parameters" |
| 23 | |
| 24 | rr_ratio = reward_per_unit / risk_per_unit |
| 25 | if rr_ratio < min_rr_ratio: |
| 26 | return False, f"R:R {rr_ratio:.2f} below minimum {min_rr_ratio:.2f}" |
| 27 | |
| 28 | # 2. Position size check |
| 29 | dollar_risk = proposed_size * risk_per_unit |
| 30 | risk_pct = dollar_risk / account_equity |
| 31 | if risk_pct > max_risk_per_trade: |
| 32 | return False, f"Risk {risk_pct:.1%} exceeds max {max_risk_per_trade:.1%}" |
| 33 | |
| 34 | # 3. Daily loss limit check |
| 35 | daily_pnl_pct = daily_pnl / account_equity |
| 36 | if daily_pnl_pct <= daily_loss_limit: |
| 37 | return False, f"Daily loss {daily_pnl_pct:.1%} hit limit — no new trades today" |
| 38 | |
| 39 | return True, "approved" |
| 40 | |
| 41 | # Usage |
| 42 | approved, reason = pre_trade_risk_check( |
| 43 | account_equity=50_000, |
| 44 | entry_price=4_500.00, |
| 45 | stop_price=4_493.50, |
| 46 | target_price=4_517.00, |
| 47 | proposed_size=2, |
| 48 | daily_pnl=-450.00, |
| 49 | ) |
| 50 | if not approved: |
| 51 | raise RuntimeError(f"Trade rejected: {reason}") |
Drag the three sliders. Expectancy, full Kelly, quarter-Kelly, and the daily loss limit recompute live. A negative-expectancy combination turns the top metric coral — that’s the gate refusing the trade in cartoon form.
Share of trades that close in your favor.
Mean $ realized on a winning trade.
Mean $ given back on a losing trade (enter as a positive number).
A 1:1 system needs >50% win rate to be profitable. A 2:1 system only needs >33%. This is why R:R is the lever that matters.
Positive expectancy — over many trades, the math is on your side.
Theoretically optimal fraction of capital to risk per trade. Painful in real drawdowns.
The cap most quants actually use. Smoother equity curve, ~75% of the geometric growth.
That’s 5 losing trades at your average loss before the daily kill-switch trips. When it trips, the strategy stops sending orders for the day. No negotiation.
// tip · drop your win rate by 5pp and watch quarter-Kelly collapse — the math is unforgiving on the right side of the curve. That’s the whole point of the gate.
The key detail most tutorials miss: use alert.freq_once_per_bar_close — not once_per_bar. The bar-close version prevents re-paint alerts from generating orders on candles that haven’t closed yet, which is one of the most common causes of unexpected entries.
| 1 | // pine_alert.pine — Alert with structured JSON payload |
| 2 | // Run at bar close to avoid re-paints |
| 3 | |
| 4 | //@version=5 |
| 5 | strategy("Asymmetric Entry v1", overlay=true, calc_on_every_tick=false) |
| 6 | |
| 7 | // — Inputs |
| 8 | rsi_len = input.int(14, "RSI Length") |
| 9 | ema_fast = input.int(9, "Fast EMA") |
| 10 | ema_slow = input.int(21, "Slow EMA") |
| 11 | risk_pct = input.float(1.0, "Risk %", minval=0.1, maxval=5.0) |
| 12 | |
| 13 | // — Indicators |
| 14 | rsi = ta.rsi(close, rsi_len) |
| 15 | ema_f = ta.ema(close, ema_fast) |
| 16 | ema_s = ta.ema(close, ema_slow) |
| 17 | |
| 18 | // — Entry conditions |
| 19 | long_cond = ta.crossover(ema_f, ema_s) and rsi < 60 |
| 20 | short_cond = ta.crossunder(ema_f, ema_s) and rsi > 40 |
| 21 | |
| 22 | // — Structured JSON payload for webhook receiver |
| 23 | if long_cond |
| 24 | alert( |
| 25 | str.format( |
| 26 | "{"action":"buy","symbol":"{0}","price":{1}," + |
| 27 | ""risk_pct":{2},"bar_time":"{3}"}", |
| 28 | syminfo.ticker, close, risk_pct, str.tostring(time) |
| 29 | ), |
| 30 | alert.freq_once_per_bar_close // no re-paints |
| 31 | ) |
| 32 | strategy.entry("Long", strategy.long) |
| 33 | |
| 34 | if short_cond |
| 35 | alert( |
| 36 | str.format( |
| 37 | "{"action":"sell","symbol":"{0}","price":{1}," + |
| 38 | ""risk_pct":{2},"bar_time":"{3}"}", |
| 39 | syminfo.ticker, close, risk_pct, str.tostring(time) |
| 40 | ), |
| 41 | alert.freq_once_per_bar_close |
| 42 | ) |
| 43 | strategy.entry("Short", strategy.short) |
TradingView can fire the same alert multiple times for the same bar under network retry conditions. Without idempotency keys, each retry becomes a new order. This Express receiver generates a deterministic key from the signal content and rejects duplicates before they reach the broker. In production, replace the in-memory Set with Redis.
| 1 | // webhook.js — Express webhook receiver with idempotency |
| 2 | // Deduplicates by client_order_id to prevent double-orders on retry |
| 3 | |
| 4 | const express = require("express"); |
| 5 | const crypto = require("crypto"); |
| 6 | const app = express(); |
| 7 | app.use(express.json()); |
| 8 | |
| 9 | // In-memory dedup cache (use Redis in production) |
| 10 | const processedIds = new Set(); |
| 11 | |
| 12 | app.post("/webhook/signal", async (req, res) => { |
| 13 | const payload = req.body; |
| 14 | |
| 15 | // 1. Validate payload shape |
| 16 | const { action, symbol, price, risk_pct } = payload; |
| 17 | if (!action || !symbol || !price) { |
| 18 | return res.status(400).json({ error: "invalid_payload" }); |
| 19 | } |
| 20 | |
| 21 | // 2. Generate deterministic idempotency key |
| 22 | const raw = `${action}:${symbol}:${payload.bar_time}`; |
| 23 | const client_order_id = crypto |
| 24 | .createHash("sha256") |
| 25 | .update(raw) |
| 26 | .digest("hex") |
| 27 | .slice(0, 24); |
| 28 | |
| 29 | // 3. Check for duplicate |
| 30 | if (processedIds.has(client_order_id)) { |
| 31 | console.log(`[dedup] Skipping duplicate signal: ${client_order_id}`); |
| 32 | return res.json({ status: "duplicate", client_order_id }); |
| 33 | } |
| 34 | |
| 35 | processedIds.add(client_order_id); |
| 36 | |
| 37 | try { |
| 38 | // 4. Submit order to broker (Alpaca example) |
| 39 | const order = await submitOrder({ |
| 40 | symbol, |
| 41 | side: action === "buy" ? "buy" : "sell", |
| 42 | type: "limit", |
| 43 | limit_price: price, |
| 44 | qty: computeQty(risk_pct, price), |
| 45 | client_order_id, |
| 46 | time_in_force: "day", |
| 47 | }); |
| 48 | |
| 49 | console.log(`[order] Submitted ${action} ${symbol} → ${order.id}`); |
| 50 | res.json({ status: "submitted", order_id: order.id, client_order_id }); |
| 51 | } catch (err) { |
| 52 | // Remove from dedup cache on failure so retry can proceed |
| 53 | processedIds.delete(client_order_id); |
| 54 | console.error(`[error] Order submission failed: ${err.message}`); |
| 55 | res.status(500).json({ error: "submission_failed", message: err.message }); |
| 56 | } |
| 57 | }); |
| 58 | |
| 59 | app.listen(3001, () => console.log("Webhook receiver listening on :3001")); |
NinjaTrader 8 strategies run inside the platform process. This circuit breaker pattern checks daily P&L before every OnBarUpdate entry signal. When the daily loss limit is hit, it flattens all positions and activates the kill switch flag, which blocks all future entries until manually reset.
| 1 | // CircuitBreaker.cs — NinjaScript kill switch + circuit breaker |
| 2 | // Stops new entries when daily P&L hits limit or kill switch is active |
| 3 | |
| 4 | #region Using declarations |
| 5 | using System; |
| 6 | using NinjaTrader.Cbi; |
| 7 | using NinjaTrader.NinjaScript; |
| 8 | #endregion |
| 9 | |
| 10 | namespace NinjaTrader.NinjaScript.Strategies |
| 11 | { |
| 12 | public class AsymmetricStrategy : Strategy |
| 13 | { |
| 14 | private double dailyLossLimit = -500; // dollars |
| 15 | private bool killSwitchActive = false; |
| 16 | |
| 17 | protected override void OnStateChange() |
| 18 | { |
| 19 | if (State == State.SetDefaults) |
| 20 | { |
| 21 | Name = "AsymmetricStrategy"; |
| 22 | Calculate = Calculate.OnBarClose; |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | protected override void OnBarUpdate() |
| 27 | { |
| 28 | // Gate 1: Kill switch — set via UI or external trigger |
| 29 | if (killSwitchActive) |
| 30 | { |
| 31 | Print("[KILL] Kill switch active — no new orders"); |
| 32 | return; |
| 33 | } |
| 34 | |
| 35 | // Gate 2: Daily loss circuit breaker |
| 36 | double todayPnL = GetDailyPnL(); |
| 37 | if (todayPnL <= dailyLossLimit) |
| 38 | { |
| 39 | Print($"[CIRCUIT] Daily P&L {todayPnL:F2} hit limit {dailyLossLimit:F2}"); |
| 40 | FlattenAll(); |
| 41 | killSwitchActive = true; |
| 42 | return; |
| 43 | } |
| 44 | |
| 45 | // Gate 3: Only trade in defined session hours |
| 46 | if (Time[0].Hour < 9 || Time[0].Hour >= 16) |
| 47 | return; |
| 48 | |
| 49 | // — Signal logic (simplified) |
| 50 | double rsi = RSI(14, 3)[0]; |
| 51 | double ema9 = EMA(9)[0]; |
| 52 | double ema21 = EMA(21)[0]; |
| 53 | |
| 54 | if (rsi < 40 && ema9 > ema21 && Position.MarketPosition == MarketPosition.Flat) |
| 55 | EnterLongLimit(0, true, 1, Close[0] - 0.25, "LongEntry"); |
| 56 | |
| 57 | if (rsi > 60 && ema9 < ema21 && Position.MarketPosition == MarketPosition.Flat) |
| 58 | EnterShortLimit(0, true, 1, Close[0] + 0.25, "ShortEntry"); |
| 59 | } |
| 60 | |
| 61 | private double GetDailyPnL() |
| 62 | { |
| 63 | return Performance.AllTrades.TradesPerformance.GrossProfit |
| 64 | - Performance.AllTrades.TradesPerformance.GrossLoss; |
| 65 | } |
| 66 | |
| 67 | private void FlattenAll() |
| 68 | { |
| 69 | if (Position.MarketPosition != MarketPosition.Flat) |
| 70 | { |
| 71 | Print("[FLATTEN] Closing all positions"); |
| 72 | ExitLong(); |
| 73 | ExitShort(); |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | } |
A deadman switch runs as a separate process and watches a heartbeat file. The main strategy touches this file on every bar update. If the heartbeat goes stale for more thanMAX_SILENCE_SECONDS— because the strategy crashed, the machine rebooted, or the network dropped — the deadman triggers a market-order flat-all on every open position.
| 1 | # deadman.py — Flat-all if heartbeat absent > N seconds |
| 2 | # Run as a separate process alongside your main strategy |
| 3 | |
| 4 | import time |
| 5 | import signal |
| 6 | import threading |
| 7 | from datetime import datetime |
| 8 | |
| 9 | HEARTBEAT_FILE = "/tmp/strategy_heartbeat" |
| 10 | MAX_SILENCE_SECONDS = 60 # flat all if no heartbeat for 60s |
| 11 | CHECK_INTERVAL = 5 # poll every 5 seconds |
| 12 | |
| 13 | def flat_all_positions(broker_client): |
| 14 | """Close every open position. Implement for your broker.""" |
| 15 | print(f"[DEADMAN] {datetime.now().isoformat()} — Flattening all positions") |
| 16 | positions = broker_client.get_all_positions() |
| 17 | for position in positions: |
| 18 | symbol = position.symbol |
| 19 | qty = abs(int(position.qty)) |
| 20 | side = "sell" if float(position.qty) > 0 else "buy" |
| 21 | broker_client.submit_order( |
| 22 | symbol=symbol, |
| 23 | qty=qty, |
| 24 | side=side, |
| 25 | type="market", |
| 26 | time_in_force="day", |
| 27 | ) |
| 28 | print(f"[DEADMAN] Closed {qty} {symbol} ({side})") |
| 29 | |
| 30 | def deadman_monitor(broker_client): |
| 31 | """Watch heartbeat file. If stale > MAX_SILENCE_SECONDS, flatten.""" |
| 32 | print(f"[DEADMAN] Monitor started — max silence: {MAX_SILENCE_SECONDS}s") |
| 33 | while True: |
| 34 | time.sleep(CHECK_INTERVAL) |
| 35 | try: |
| 36 | mtime = os.path.getmtime(HEARTBEAT_FILE) |
| 37 | age = time.time() - mtime |
| 38 | if age > MAX_SILENCE_SECONDS: |
| 39 | print(f"[DEADMAN] Heartbeat age {age:.0f}s — triggering flat-all") |
| 40 | flat_all_positions(broker_client) |
| 41 | # Keep monitoring — may need to re-flatten if positions reopen |
| 42 | except FileNotFoundError: |
| 43 | print("[DEADMAN] Heartbeat file missing — creating baseline") |
| 44 | with open(HEARTBEAT_FILE, "w") as f: |
| 45 | f.write(str(time.time())) |
| 46 | |
| 47 | # In your main strategy loop, touch the heartbeat file each bar: |
| 48 | # with open(HEARTBEAT_FILE, "w") as f: |
| 49 | # f.write(str(time.time())) |
cAlgo is cTrader’s native automation API. Robots inherit fromRobotand react to bar/tick events. This bot reads RSI on every closed bar and submits a market order with stop-loss and take-profit in pips on extremes — with a duplicate-position guard via the Labelfield. Same shape works for Forex and CFDs.
| 1 | // RsiBracketBot.cs — cTrader cAlgo example |
| 2 | // Trade RSI extremes with bracketed stop / take-profit (in pips). |
| 3 | using cAlgo.API; |
| 4 | using cAlgo.API.Indicators; |
| 5 | |
| 6 | namespace cAlgo.Robots |
| 7 | { |
| 8 | [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] |
| 9 | public class RsiBracketBot : Robot |
| 10 | { |
| 11 | [Parameter("RSI Periods", DefaultValue = 14, MinValue = 2)] |
| 12 | public int RsiPeriods { get; set; } |
| 13 | |
| 14 | [Parameter("Lots", DefaultValue = 0.10, MinValue = 0.01, Step = 0.01)] |
| 15 | public double Lots { get; set; } |
| 16 | |
| 17 | [Parameter("Stop Loss (pips)", DefaultValue = 25)] |
| 18 | public double StopLossPips { get; set; } |
| 19 | |
| 20 | [Parameter("Take Profit (pips)", DefaultValue = 50)] |
| 21 | public double TakeProfitPips { get; set; } |
| 22 | |
| 23 | private RelativeStrengthIndex _rsi; |
| 24 | private const string Label = "RsiBracketBot"; |
| 25 | |
| 26 | protected override void OnStart() |
| 27 | { |
| 28 | _rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, RsiPeriods); |
| 29 | } |
| 30 | |
| 31 | protected override void OnBar() |
| 32 | { |
| 33 | // Skip if we already have a position with this label |
| 34 | var open = Positions.FindAll(Label, SymbolName); |
| 35 | if (open.Length > 0) return; |
| 36 | |
| 37 | var volume = Symbol.QuantityToVolumeInUnits(Lots); |
| 38 | var prev = _rsi.Result.Last(1); |
| 39 | |
| 40 | if (prev < 30) |
| 41 | ExecuteMarketOrder(TradeType.Buy, SymbolName, volume, Label, |
| 42 | StopLossPips, TakeProfitPips); |
| 43 | else if (prev > 70) |
| 44 | ExecuteMarketOrder(TradeType.Sell, SymbolName, volume, Label, |
| 45 | StopLossPips, TakeProfitPips); |
| 46 | } |
| 47 | |
| 48 | protected override void OnStop() |
| 49 | { |
| 50 | // Optional: flatten on shutdown for safety |
| 51 | foreach (var p in Positions.FindAll(Label, SymbolName)) |
| 52 | ClosePosition(p); |
| 53 | } |
| 54 | } |
| 55 | } |
CCXT is the unified Python/JS library for over 100 crypto venues. The same code targets Coinbase Advanced, Kraken, Bybit, OKX, Binance — you change one env var. Below: spot market-buy by quote-cost on one client, then a separate defaultType: "swap"client for a leveraged perpetual long with isolated margin. CatchesRateLimitExceededand InsufficientFundsseparately because they need different retry strategies.
| 1 | # ccxt_order.py — Spot + perp market order with proper error handling |
| 2 | # pip install ccxt |
| 3 | import os |
| 4 | import ccxt |
| 5 | |
| 6 | VENUE = os.environ["VENUE"] # e.g. "bybit", "okx", "binance" |
| 7 | API_KEY = os.environ["API_KEY"] |
| 8 | API_SECRET = os.environ["API_SECRET"] |
| 9 | PASSPHRASE = os.environ.get("API_PASSPHRASE") # required for OKX, Coinbase |
| 10 | |
| 11 | exchange_cls = getattr(ccxt, VENUE) |
| 12 | exchange = exchange_cls({ |
| 13 | "apiKey": API_KEY, |
| 14 | "secret": API_SECRET, |
| 15 | "password": PASSPHRASE, |
| 16 | "enableRateLimit": True, # CCXT will sleep to respect venue limits |
| 17 | "options": {"defaultType": "spot"}, |
| 18 | }) |
| 19 | |
| 20 | def place_spot_buy(symbol: str, quote_amount: float): |
| 21 | """Market-buy 'quote_amount' of the quote currency (e.g. 100 USDT of BTC).""" |
| 22 | try: |
| 23 | bal = exchange.fetch_balance() |
| 24 | free_quote = bal["free"].get(symbol.split("/")[1], 0) |
| 25 | if free_quote < quote_amount: |
| 26 | raise RuntimeError(f"Insufficient quote balance: {free_quote}") |
| 27 | |
| 28 | # 'createMarketBuyOrderRequiresPrice' is False on most venues when |
| 29 | # passing cost via params; check venue docs. |
| 30 | order = exchange.create_market_buy_order( |
| 31 | symbol, amount=None, |
| 32 | params={"cost": quote_amount}, |
| 33 | ) |
| 34 | return order |
| 35 | except ccxt.RateLimitExceeded as e: |
| 36 | # Back off and retry from caller |
| 37 | raise |
| 38 | except ccxt.InsufficientFunds as e: |
| 39 | raise RuntimeError(f"Exchange rejected order: {e}") |
| 40 | |
| 41 | def place_perp_long(symbol: str, contracts: float, leverage: int = 3): |
| 42 | """Open a long perpetual position with isolated margin.""" |
| 43 | perp = exchange_cls({ |
| 44 | "apiKey": API_KEY, "secret": API_SECRET, "password": PASSPHRASE, |
| 45 | "enableRateLimit": True, |
| 46 | "options": {"defaultType": "swap"}, # perpetual futures |
| 47 | }) |
| 48 | perp.set_leverage(leverage, symbol, params={"marginMode": "isolated"}) |
| 49 | return perp.create_market_buy_order(symbol, contracts) |
| 50 | |
| 51 | if __name__ == "__main__": |
| 52 | print(place_spot_buy("BTC/USDT", 100.0)) |
| 53 | # print(place_perp_long("BTC/USDT:USDT", 0.001, leverage=3)) |
Tradovate is one of the most retail-friendly futures brokers and exposes a clean access-token REST API plus a WebSocket fill feed. This module handles three real production concerns: token-bearer auth with explicit 401 surfacing, anisAutomated: trueflag (required by Tradovate for bot orders), and a self-healingon_closeWebSocket reconnect for fill streams.
| 1 | # tradovate_order.py — Auth + place market order on Tradovate REST |
| 2 | # Tradovate uses an access-token flow. Tokens expire — refresh on 401. |
| 3 | import os |
| 4 | import time |
| 5 | import requests |
| 6 | from websocket import WebSocketApp # pip install websocket-client |
| 7 | |
| 8 | BASE = "https://demo.tradovateapi.com/v1" # live: https://live.tradovateapi.com/v1 |
| 9 | WS = "wss://demo.tradovateapi.com/v1/websocket" |
| 10 | |
| 11 | def get_access_token() -> str: |
| 12 | payload = { |
| 13 | "name": os.environ["TRADOVATE_USERNAME"], |
| 14 | "password": os.environ["TRADOVATE_PASSWORD"], |
| 15 | "appId": os.environ["TRADOVATE_APP_ID"], |
| 16 | "appVersion": "1.0", |
| 17 | "cid": os.environ["TRADOVATE_CID"], |
| 18 | "sec": os.environ["TRADOVATE_SECRET"], |
| 19 | } |
| 20 | r = requests.post(f"{BASE}/auth/accesstokenrequest", json=payload, timeout=10) |
| 21 | r.raise_for_status() |
| 22 | body = r.json() |
| 23 | if "accessToken" not in body: |
| 24 | raise RuntimeError(f"Auth failed: {body}") |
| 25 | return body["accessToken"] |
| 26 | |
| 27 | def place_market_order(token: str, account_id: int, symbol: str, qty: int, action: str = "Buy"): |
| 28 | """action: 'Buy' or 'Sell'. symbol e.g. 'MESM5' (Micro E-mini June '25).""" |
| 29 | headers = {"Authorization": f"Bearer {token}"} |
| 30 | body = { |
| 31 | "accountSpec": os.environ["TRADOVATE_USERNAME"], |
| 32 | "accountId": account_id, |
| 33 | "action": action, |
| 34 | "symbol": symbol, |
| 35 | "orderQty": qty, |
| 36 | "orderType": "Market", |
| 37 | "isAutomated": True, |
| 38 | } |
| 39 | r = requests.post(f"{BASE}/order/placeorder", json=body, headers=headers, timeout=10) |
| 40 | if r.status_code == 401: |
| 41 | raise PermissionError("Token expired — re-auth and retry") |
| 42 | r.raise_for_status() |
| 43 | return r.json() |
| 44 | |
| 45 | def stream_fills(token: str, on_fill): |
| 46 | """Reconnect-on-drop fill stream. Caller passes a callback(message_dict).""" |
| 47 | def _on_open(ws): |
| 48 | ws.send(f"authorize\n1\n\n{token}") |
| 49 | def _on_message(ws, msg): |
| 50 | on_fill(msg) |
| 51 | def _on_close(ws, code, reason): |
| 52 | print(f"[WS] closed {code} {reason} — reconnecting in 3s") |
| 53 | time.sleep(3) |
| 54 | stream_fills(token, on_fill) # tail-call reconnect |
| 55 | WebSocketApp(WS, on_open=_on_open, on_message=_on_message, on_close=_on_close).run_forever() |
| 56 | |
| 57 | if __name__ == "__main__": |
| 58 | tok = get_access_token() |
| 59 | print(place_market_order(tok, account_id=int(os.environ["TRADOVATE_ACCOUNT_ID"]), |
| 60 | symbol="MESM5", qty=1, action="Buy")) |
These aren’t edge cases. Every one of these has caused real capital loss for someone running automation without proper safeguards.
The strategy produced a Sharpe ratio of 4.2 in-sample. Out-of-sample it lost money from day one. The developer had inadvertently optimized over 3 years of data without holding out a test set.
In-sample Sharpe 4.2 → Out-of-sample Sharpe −0.3. Overfitting penalty.
Define in-sample and out-of-sample periods before you touch the data. Never optimize against the test set.
Alerts fired. No orders appeared. The webhook receiver had silently crashed hours earlier. The strategy missed 12 trades before anyone noticed.
MTTR (mean time to detect): 4.5 hours. Missed P&L opportunity: ~$900.
Heartbeat monitoring on the webhook receiver is non-negotiable. Alert within 60 seconds of any process failure.
The data feed returned timestamps in UTC. The broker expected EST. The system submitted orders 5 hours before intended session start, during pre-market when spreads were enormous.
Slippage on 1 contract: 4 ticks = $50. Over 30 trades: $1,500 unmodeled cost.
Normalize all timestamps to UTC at ingestion. Convert to local session time only at the final display or session-filter layer.
A TradingView alert fired three times on the same bar due to a misconfigured alert frequency. Three identical market orders executed, tripling the intended position size.
Intended exposure: 1 × $5,000 risk. Actual exposure: 3 × $5,000. Drawdown: $8,200.
Use alert.freq_once_per_bar_close in Pine Script. Implement client_order_id deduplication on the receiver.
Strategy process crashed mid-trade. The exit logic never ran. An open short position sat overnight, then gapped up 2% against the strategy at the open.
Overnight gap: +2.1% × $10,000 position = $210 unmanaged loss.
Deadman switch flattens all positions if the heartbeat stops. Startup code reconciles broker state against strategy state before resuming.
A mean-reversion strategy backtested beautifully on 2019-2022 data — a mostly range-bound equity market. It deployed into the 2022 bear market and lost steadily for 8 months.
Annualized return in-sample: +34%. Live during regime shift: −28%.
Test across multiple regimes explicitly. Use a regime filter (e.g., 200-day SMA slope) to pause mean-reversion strategies during trending conditions.
API keys were rotated as part of a routine security audit. The strategy's config file wasn't updated. All order submissions returned 401 Unauthorized for 3 hours before anyone noticed.
3 hours of missed signal time. 4 valid setups unexecuted. Approx. $650 opportunity cost.
Store credentials in a secrets manager with expiry alerts. Health-check the API connection at startup and on a 5-minute heartbeat.
Backtest used fill-at-close prices. Live trading used market orders. In the 30 minutes before the close, ES spreads widen and fills came in 2–4 ticks worse than modeled.
Modeled slippage: 0.5 ticks. Actual: 2.8 ticks. Profit factor dropped from 1.8 to 1.1.
Model slippage at 2× your average spread for market orders. Switch to limit orders with a modest offset from current price.
Reference documents designed to live on your desk, not in a folder you never open. Enter your email above to unlock instant download access.
27-point pre-deployment audit across Code, Data, Risk, Broker, Monitoring, and Recovery categories.
IBKR, Alpaca, Tradovate, NT8, and TradingView side-by-side across 10 dimensions. No affiliates.
Step-by-step runbook for the 6 most common automation failures. Severity levels, decision trees, recovery procedures.
Score yourself on the 5-stage ladder across 5 dimensions. 25 questions. Identifies exactly what to build next.
Printable week-by-week plan with deliverables, resource links, and checkboxes for each module.
This guide covers the implementation. If you want the underlying mental models — asymmetric R:R, why markets are auctions, how to build a system that survives — read the book first.
read The Asymmetric Investor — freeThe automation guide teaches you to build it. The Journal tracks every trade it makes. The Swing Desk shows you what to automate. Sage AI explains why.
The mental models behind every automation decision. 37 chapters. 120k words. Free forever.
The ranked, regime-gated dashboard. Build your automation to trade the setups it surfaces.
Twelve modules that turn theory into practiced reps before you automate anything.
Nine tactical playbooks with exact triggers — the signal layer your automation can execute.
The hub above is the overview. Each module below has its own page and a printable PDF. Platforms has six per-broker deep-dives underneath.
Python, git, shell — the habits the rest of the curriculum assumes.
Six honest platform deep-dives plus the 14-page survey PDF.
How to design a backtest that does not lie to you.
Bars, ticks, vendors, alignment — the layer everything rides on.
Models that survive contact with markets — and how to tell which do not.
The desk is the system. Runbooks, incidents, monitors, gates.
// platform deep dives · 06