Disclaimer: This is a tool, not financial advice. Automated trading carries significant risk of loss. Start with paper trading only and never risk capital you cannot afford to lose. You are solely responsible for all trading decisions and outcomes.
Project Cynosure is a fully local, autonomous AI trading system for OKX perpetual swaps — crypto majors, commodities (gold/silver), and US equity index/stock perps. It runs 24/7 with no cloud LLM dependency, no external reasoning services, and no capital decisions made by anything other than your own hardware.
The name "Cynosure" (from the Greek for "dog's tail," referring to the guiding star Polaris) reflects the system's purpose: a reliable, always-on guiding intelligence that navigates volatile perpetual swap markets.
The system is not a simple rules-based bot. It uses a local LLM (Qwen3.5-4B via Ollama) as a synthesizer — not a reasoner — and does all the heavy quantitative work in Python before the LLM ever sees a single token. The LLM reads a pre-computed MarketBrief (~500 tokens) containing expert-analyzed signals and outputs a single structured JSON trading decision.
main.py (APScheduler)
└─ run_analysis_cycle()
└─ For each ticker on watchlist:
1. data_gatherer.gather() ← Python expert pipeline
2. LLM synthesis (single call) ← reads MarketBrief, outputs JSON
3. RiskEngine.check() ← deterministic risk gates
4. execute() ← OKX perpetual swap order
Every decision is logged with full signal context and LLM reasoning to logs/reasoning.jsonl and logs/trades.jsonl.
The pipeline fetches and computes everything before the LLM is called. The LLM receives a compact ~500-token MarketBrief, not raw candle data.
What gets computed per ticker per cycle:
| Source | Signals |
|---|---|
technical_expert.py |
EMA9/20/50 alignment, RSI(14), MACD(12,26,9), Bollinger, ATR, OFI (order flow imbalance/VPIN), realized vol + jump detection, VWAP deviation, flow toxicity — across 15m / 1H / 4H timeframes |
forecaster.py |
TimesFM 2.5 200M zero-shot directional price forecast (±0.30% threshold); short-term (1H→4H) and medium-term (4H→24H) |
orderbook_expert.py |
20-level L2 depth: order imbalance, whale walls, liquidity profile, slippage estimation |
regime.py |
Fear & Greed Index, BTC market regime (Trend/Range/Reversal), soft regime transitions |
sentiment.py |
Recent news headlines (last 24h, with age and sentiment tags) |
db.py |
Signal streak history: consecutive bullish/bearish cycle count for each ticker |
| OKX MCP | Funding rate, Open Interest delta, Long/Short Ratio, recent large liquidations |
GatherResult is the output:
@dataclass
class GatherResult:
brief: str # The full MarketBrief text for the LLM
atr_pct_avg: float # ATR % averaged across timeframes (for stop sizing)
tf_alignment: int # Deterministic bullish signal count (0–15)
dominant_direction: str # "bullish" | "bearish" | "neutral"
direction_streak: int # Consecutive cycles in same direction (from DB)- Model:
qwen3.5:4bvia Ollama — sufficient for signal synthesis, no tool-calling required - Context: 3072 tokens — MarketBrief (~500 tok) + system prompt + JSON output
- Thinking disabled:
/nothinkprefix +think=false— saves 30–120s per call - Single call: no ReAct loops, no tool calls, no multi-turn reasoning
The system prompt (prompts/trading_system.py) encodes all trading rules:
- Signal alignment thresholds (≥5 aligned signals, ≥2 timeframes)
- Signal persistence requirement (≥2 consecutive bullish cycles for BUY)
- Exit discipline (let OCO stop/TP work; don't exit on single bearish cycle)
- Funding rate interpretation, Fear & Greed contrarian bias
- Long/Short Ratio crowding signals, liquidation cluster interpretation
- Asset-class-specific rules (gold/silver vs crypto vs stock perps)
- Performance-adaptive suffix: tightens rules when Sharpe < 0 or win rate < 45%
LLM output (JSON, ~200 tokens):
{
"action": "BUY",
"signals_aligned": 7,
"timeframes_aligned": 2,
"reasoning": "1H+4H EMA bullish, OFI=+0.22 buy pressure, TimesFM +0.8% horizon, funding neutral.",
"notional": 5.00
}The LLM's self-reported values are overridden by deterministic Python before risk checks:
| Field | LLM value | Override |
|---|---|---|
signals_aligned |
LLM estimate | _count_bullish_signals() — 5 indicators × 3 TFs, max 15 |
confidence |
Not used | compute_signal_strength() — weighted TFSignal score (4H=50%, 1H=35%, 15m=15%) |
This prevents hallucinated signal counts from bypassing risk gates.
Two hard gates in agent.py block premature entries and exits:
- BUY gate:
direction_streak == 1→ force HOLD. A single-cycle bullish shift is noise. Require ≥2 consecutive cycles. - SELL gate:
direction_streak >= 2(still bullish) → force HOLD. Don't exit a position mid-trend; let OCO handle it.
These gates run before the risk engine, so they cannot be bypassed by high confidence scores.
All checks are deterministic. The LLM has no role in risk decisions.
Pre-trade checks (in order):
- Daily loss limit exceeded → HALT
- Daily trade count exceeded → skip ticker
- Max open positions reached → skip ticker
- Consecutive loss circuit breaker (
max_consecutive_losses) → HALT - Margin ratio guard (OKX
mgnRatio < 1.20) → HALT - Per-ticker 14-day cumulative loss (
max_ticker_loss_pct) → block re-entry - Correlation check (≥0.85 with existing position) → skip
- ATR = 0 (no candle data) → force HOLD
- Expected Value gate:
EV = (win_rate × tp_pct) - ((1 - win_rate) × sl_pct) ≥ 0.5%
Position sizing (Kelly Criterion):
half_kelly = (edge / odds) / 2
conf_scale = 0.5 + confidence × 0.5 # maps [0,1] → [0.5, 1.0]
notional = portfolio_value × half_kelly × conf_scale × regime_vol_mult
Capped at max_position_size_pct of portfolio.
ATR-adaptive stops:
stop_loss = entry × (1 - ATR_pct × sl_mult × signal_strength)
take_profit = entry × (1 + ATR_pct × tp_mult × kelly_edge)
Placed as OCO algo orders (okx__place_algo_order) — stop-loss + take-profit bracket.
For borderline BUYs (confidence in [0.65, 0.78]), a second LLM call challenges the original decision:
- ABORT → converted to HOLD
- REDUCE_SIZE → sets
size_reduction_factor = 0.6(applied after Kelly, not overriding it) - PROCEED → trade goes through
Custom MCP server wrapping the python-okx SDK. Provides 11 tools:
- Account info, position listing
- Market/limit order placement
- OCO algo orders (stop + TP bracket)
- Cancel orders, close positions
- Candle/ticker/orderbook data fetch
constellation.py routes tool calls to the MCP subprocess via stdio, with a 60-second result cache to avoid redundant API calls.
| Component | Purpose |
|---|---|
analysis/scanner.py |
Hourly RS (relative strength) scoring — ranks watchlist by momentum |
analysis/outcome_tracker.py |
Syncs closed trade outcomes from OKX; sends Telegram P&L notifications |
analysis/regime.py |
BTC market regime detection + Fear & Greed + soft regime multiplier blending |
risk/calibration.py |
ConfidenceCalibrator — maps raw signal strength to calibrated confidence via historical curve |
risk/signal_quality.py |
SignalQualityMonitor — Spearman IC tracking per signal type; reported in daily review |
risk/correlation.py |
Pairwise correlation check before new position entry |
backtest.py |
Walk-forward backtest using real technical_expert pipeline (3-window IS/OOS) |
db.py |
SQLite persistence: trades, signals, outcomes, calibration data, streak history |
main.py |
APScheduler daemon: analysis cycle (15m), scanner (60m), daily review (00:00 UTC) |
Prerequisites: Ollama, Python 3.11+, OKX account with API keys.
# 1. Pull the model
ollama pull qwen3.5:4b
# 2. Install dependencies
pip install -r requirements.txt
# 3. Configure
cp config.example.yaml config.yaml
# Edit config.yaml: add OKX API keys, set mode: paper, adjust watchlist
# 4. Run (paper trading)
python main.pyTo run paper trading (OKX demo account), set in config.yaml:
goals:
mode: paper
apis:
okx:
flag: "1" # 1 = OKX demo/simulated tradingFor live trading, set flag: "0" and provide real OKX API keys. Observe paper trading for weeks first.
Key sections:
llm:
model: qwen3.5:4b
temperature: 0.1
num_ctx: 3072
think: false # Disable chain-of-thought (saves 30-120s)
da_confidence_min: 0.65 # Devil's Advocate fires in this band
da_confidence_max: 0.78
goals:
mode: paper # "paper" | "live"
target_annual_return_pct: 40
risk:
min_signals_aligned: 5 # Deterministic signal count threshold (0–15 scale)
max_position_size_pct: 40.0 # % of portfolio per position
daily_loss_limit_pct: 35.0
min_confidence: 0.65
max_open_positions: 10
max_consecutive_losses: 5 # Halt after N sequential losses
max_ticker_loss_pct: 10.0 # Block ticker if down >10% over 14 days
trading:
trail_percent: 2.5 # Trailing stop %
min_hold_minutes: 180 # Block LLM exits for 3h after entry (OCO handles it)
watchdog:
telegram_token: "..." # Telegram bot for P&L notifications
telegram_chat_id: "..."cynosure/
├── main.py # APScheduler 24/7 daemon
├── agent.py # Core trading loop: gather → synthesize → risk → execute
├── config.py # Config loader + hot-reload watcher + validation
├── config.yaml # Your settings (not committed with real keys)
├── config.example.yaml # Template
├── db.py # SQLite: trades, signals, outcomes, streaks
├── constellation.py # MCP subprocess router (stdio)
├── utils.py # Shared utilities
│
├── analysis/
│ ├── data_gatherer.py # Parallel fetch + expert pipeline → MarketBrief
│ ├── technical_expert.py # HFT heuristic signals (EMA/RSI/MACD/OFI/ATR/VWAP)
│ ├── forecaster.py # TimesFM 2.5 200M zero-shot price forecast
│ ├── orderbook_expert.py # L2 orderbook heuristics (imbalance, whale walls)
│ ├── regime.py # Market regime detection + Fear & Greed
│ ├── scanner.py # Hourly RS momentum scoring
│ ├── outcome_tracker.py # Trade outcome sync + Telegram notifications
│ └── sentiment.py # News headline fetching
│
├── risk/
│ ├── rules.py # Kelly sizing, EV gate, ATR stops, all pre-trade checks
│ ├── calibration.py # Confidence calibrator (historical curve)
│ ├── signal_quality.py # Spearman IC per signal type
│ └── correlation.py # Pairwise correlation guard
│
├── exchange/
│ └── okx_mcp.py # MCP server wrapping python-okx SDK (11 tools)
│
├── prompts/
│ └── trading_system.py # System prompt builder (rules + performance suffix)
│
├── backtest.py # Walk-forward backtester (real signal pipeline)
├── logs/
│ ├── trades.jsonl # Trade execution log
│ └── reasoning.jsonl # LLM reasoning trace per decision
└── storage/ # SQLite database files
The default watchlist covers four categories of OKX perpetual swaps:
| Category | Instruments |
|---|---|
| Commodities | XAU-USDT-SWAP (gold), XAG-USDT-SWAP (silver) |
| US Indices | QQQ-USDT-SWAP, SPY-USDT-SWAP |
| AI/Tech Stocks | NVDA, MSFT, META, AAPL, GOOGL, AMZN, TSLA, PLTR |
| Crypto Majors | BTC-USDT-SWAP, ETH-USDT-SWAP, SOL-USDT-SWAP |
| Mid-tier Crypto | XRP-USDT-SWAP, SUI-USDT-SWAP |
All instruments are USDT-margined perpetual swaps on OKX — same mechanics, different underlying.
| Component | Minimum | Recommended |
|---|---|---|
| GPU VRAM | 4 GB | 8 GB (RTX 4070) |
| RAM | 16 GB | 32 GB |
| Storage | 10 GB | 30 GB (model + logs) |
| OS | Windows/Linux/macOS | Any |
RTX 4070 (8 GB VRAM): Qwen3.5-4B at Q4_K_M fits fully on-GPU with room for the KV cache. Each LLM call takes ~5–8 seconds with think=false. A full 15-ticker analysis cycle completes in ~2 minutes.
- Markets are unpredictable. Even a well-designed system loses money. Backtests do not guarantee future results.
- This is not financial advice. You control this system. You are responsible for all trades and losses.
- Perpetual swaps carry liquidation risk. The system runs at 1x leverage by default. Never increase leverage without fully understanding the implications.
- Start with paper trading. Set
mode: paperandflag: "1"(OKX demo). Run for weeks before using real money. - API key security: Keep your
config.yamlprivate. Prefer storing keys in a.envfile — the system readsOKX_API_KEY,OKX_SECRET_KEY,OKX_PASSPHRASEfrom the environment if set.
Project Status: Active development — live trading on OKX perpetual swaps License: MIT
"Follow the star. Trade with clarity."