Found a `BOT_CREATION_CHECKLIST.md` in the minimax folder. Let me read it along with `bot_rules.md`:
2026-04-22 15:56:49
TOOL (hermes, Hermes-4-405B)
{"content": " 1|# Bot Creation Checklist\n 2|\n 3|A comprehensive guide for creating a new trading bot with proper risk management, API calls, and system integration.\n 4|\n 5|---\n 6|\n 7|## 1. Configuration (momentum_config.py)\n 8|\n 9|### Why This Matters\n 10|The configuration defines what you're trading and how. Getting this wrong means wrong symbols, wrong lot sizes, or trading the wrong underlying.\n 11|\n 12|### Required Fields\n 13|\n 14|```python\n 15|MOMENTUM_PROFILES = {\n 16| \"SBIN_MOMENTUM\": {\n 17| \"symbol\": \"SBIN\", # Trading symbol (for options)\n 18| \"symbol_underlying\": \"SBIN\", # Underlying for options\n 19| \"symbol_futures\": \"SBINFUT\", # ← CRITICAL: Futures for LTP (NOT stock!)\n 20| \"broker\": \"FLATTRADE\", # Broker name\n 21| \"exchange\": \"NSE\", # NSE for historical data\n 22| \"lot_size\": 750, # Futures lot size\n 23| \"max_lots\": 1,\n 24| \"product\": \"NRML\", # NRML/MIS/CNC\n 25| \"expiry_date\": \"30MAR26\", # Format: DDMMMYY\n 26| \"strike_step\": 5, # 5 for SBIN, 100 for NIFTY/BANKNIFTY\n 27| \"itm_offset\": 1, # 0=ATM, -1=ITM, +1=OTM\n 28| },\n 29|}\n 30|```\n 31|\n 32|### Common Mistakes\n 33|- **Using stock symbol for LTP**: SBIN (stock) vs SBINFUT (futures) - they have different prices!\n 34|- **Wrong strike step**: SBIN=5, NIFTY=100, BANKNIFTY=100\n 35|- **Wrong expiry format**: Must be \"30MAR26\", not \"30-03-2026\"\n 36|\n 37|---\n 38|\n 39|## 2. API Calls\n 40|\n 41|### The Critical Fix: quotes(symbol=) NOT quote(token=)\n 42|\n 43|**OpenAlgo uses `quotes()` with `symbol=` parameter:**\n 44|\n 45|```python\n 46|# WRONG - This will fail or return wrong data\n 47|data = self.client.quote(token=\"SBINFUT\", exchange=\"NFO\")\n 48|\n 49|# CORRECT - Use quotes() with symbol=\n 50|data = self.client.quotes(symbol=\"SBINFUT\", exchange=\"NFO\")\n 51|```\n 52|\n 53|### Why This Matters\n 54|Using the wrong API method causes:\n 55|- Failed LTP fetches → no stop loss checks\n 56|- Wrong price data → incorrect entries/exits\n 57|- Bot crashes or hangs\n 58|\n 59|### Complete LTP Fetch Example\n 60|```python\n 61|def get_ltp(self):\n 62| \"\"\"Get LTP of futures (not stock)\"\"\"\n 63| try:\n 64| # Use quotes() not quote()\n 65| data = self.client.quotes(symbol=self.symbol_futures, exchange=\"NFO\")\n 66| if data:\n 67| # Extract LTP - can be 'lp' or 'last_price'\n 68| return float(data.get('lp', data.get('last_price', 0)))\n 69| except Exception as e:\n 70| print(f\"Error getting futures LTP: {e}\")\n 71| return 0.0\n 72|\n 73|def g
... [18277 chars total, truncated]
2026-04-22 15:56:49
TOOL (hermes, Hermes-4-405B)
{"content": " 1|---\n 2|name: bot_rules\n 3|description: Critical trading bot rules for new Renko VIDYA bot\n 4|type: project\n 5|---\n 6|\n 7|## Symbol Construction (dynamic, user-configured)\n 8|- **Futures symbol**: `{SYMBOL}{EXPIRY}FUT` e.g., `LT30MAR26FUT`\n 9|- **Options symbol**: `{SYMBOL}{EXPIRY}{STRIKE}{CE/PE}` e.g., `LT30MAR263660PE`\n 10|- **Strike ladder**: 20-point increments\n 11|- Lot size per symbol: configured by user (e.g., LT = 175)\n 12|- Bot must construct both futures and option symbols dynamically from config\n 13|\n 14|## Position & Order Rules\n 15|\n 16|**Broker is source of truth** — always confirm positions with broker. Never rely solely on local state.\n 17|\n 18|**Never exceed 2 lots position** at any time.\n 19|\n 20|**Derivative trades are always LIMIT orders** — never market orders.\n 21|- Entry (BUY): option LTP **+ 5** points (aggressive — willing to pay more to get filled)\n 22|- Exit (SELL): option LTP **- 5** points (passive — willing to accept slightly less to get filled)\n 23|- ⚠️ CRITICAL: BUY and SELL offsets are **opposite directions**. Using LTP+5 for SELL places limit above market → order never fills.\n 24|- +5 / -5 is applied to option LTP, NOT futures price\n 25|\n 26|**Exit failure protocol**:\n 27|1. Attempt to exit (sell) up to 2 times\n 28|2. If both attempts fail → immediately buy opposite ATM option\n 29|3. Send Telegram warning alert\n 30|4. Do not wait — act, then alert\n 31|\n 32|**Exit monitoring**: After entry, do NOT pre-place exit order. Monitor passively and place SELL only when SL condition is hit.\n 33|\n 34|**SL check intervals**:\n 35|- 15-min candle close → check 2-candle level break\n 36|- 5-min polling → check INR 1000 loss threshold\n 37|\n 38|**No cooldown period** after exit — next order can be placed immediately if within window limits.\n 39|\n 40|## Market Open (9:15) Position Check\n 41|- Fetch open positions from broker\n 42|- **If position is ours** (from previous day) → continue monitoring for SL as normal\n 43|- **If position is NOT ours** (orphan) → send Telegram warning every 5 minutes until resolved\n 44|", "total_lines": 43, "file_size": 1926, "truncated": false, "is_binary": false, "is_image": false}
2026-04-22 15:56:49
ASSISTANT (hermes, Hermes-4-405B)
Found two very useful bot creation documents:
### 1. `/home/ubuntu/bots/Claude_bot/minimax/BOT_CREATION_CHECKLIST.md`
A comprehensive 620-line checklist covering the full bot creation process:
| Section | What it covers |
|---------|---------------|
| 1. Configuration | `momentum_config.py` - symbol, futures vs stock LTP pitfall, lot size, expiry format |
| 2. API Calls | `quotes(symbol=)` not `quote(token=)`, LTP fetch example, timeout settings |
| 3. Stop Loss Logic | Option value decreases when wrong direction, using futures for LTP vs stock |
| 4. Risk Management | Entry retry logic, option LTP validation (MUST validate before orders), exit retry + hedge, position verification via broker |
| 5. Database/Candles | Incremental candle updates, filtering in-progress candles |
| 6. State Management | Save/load state for bot restart recovery |
| 7. Telegram Alerts | Alert types and implementation |
| 8. Polling Intervals | LTP (5min), ROC (15min), heartbeat (30min) |
| 9. Market Hours | Skip non-trading hours, holiday list |
| 10. Systemd Service | Service file template + install/control commands |
### 2. `/home/ubuntu/bots/three_candle_bot/bot_rules.md`
A shorter 43-line rules document for that bot:
- Symbol construction (futures vs options format)
- Max 2 lots position limit
- Limit orders only (never market)
- Exit failure protocol (2 attempts → hedge → alert)
- 15-min candle SL checks, 5-min polling
- Market open position check (orphan detection)
---
**Summary:** There's a solid checklist already in place at `/home/ubuntu/bots/Claude_bot/minimax/BOT_CREATION_CHECKLIST.md`. Want me to read the rest of it (lines 501-620) or start using it as a template for your new bot?
2026-04-22 15:56:49
USER (hermes, Hermes-4-405B)
did you read two current bots and find the nuances
2026-04-22 15:59:22
ASSISTANT (hermes, Hermes-4-405B)
2026-04-22 15:59:22
TOOL (hermes, Hermes-4-405B)
{"content": " 1|#!/usr/bin/env python3\n 2|\"\"\"\n 3|LT 3-Candle Renko VIDYA — Intraday Trading Bot\n 4|===============================================\n 5|Candle data flow:\n 6| Every 5-min poll → fetch new 5-min candles from broker → append to candles_5min DB\n 7| Every 3 x 5-min complete a 15-min slot → build OHLC → append to candles_15min DB\n 8| 3-candle window runs off candles_15min (local DB, no live API for history)\n 9|\n 10|Strategy:\n 11| - Daily bias: futures LTP at 9:15 vs VIDYA(20) on 5-pt close-based Renko\n 12| - GREEN (price > VIDYA) → monitor for CALL entries\n 13| - RED (price < VIDYA) → monitor for PUT entries\n 14|\n 15|Entry (2-candle setup):\n 16| - GREEN: track candle-1 low. If candle 2 holds above it → BUY ATM CALL\n 17| - RED: track candle-1 high. If candle 2 holds below it → BUY ATM PUT\n 18| - Break candle becomes new candle 1 (rolling window)\n 19| - Strike = floor(futures_ltp / 20) * 20\n 20|\n 21|Exit triggers:\n 22| - 2-candle level break: 15-min candle closes below/above SL level\n 23| - Rs 1000 loss: checked every 5-min poll\n 24|\n 25|Limits:\n 26| - 2 orders per direction per 3-hour window (W1: 9:15-12:15, W2: 12:15-15:30)\n 27| - Max 2 lots at any time (broker check before every order)\n 28| - NRML — holds overnight, resumes next morning\n 29|\n 30|While in position:\n 31| - Skipped setups tracked as #1, #2... with futures high since entry (Telegram)\n 32| - Exit failure → 2 retries → hedge ATM opposite → Telegram every 5 min\n 33|\"\"\"\n 34|\n 35|import sys\n 36|import os\n 37|import sqlite3\n 38|import json\n 39|import time\n 40|import math\n 41|from datetime import datetime as dt, timedelta, time as dtime\n 42|from collections import defaultdict\n 43|\n 44|sys.path.insert(0, \"/home/ubuntu/Claude_bot\")\n 45|from openalgo import api\n 46|\n 47|_DIR = os.path.dirname(os.path.abspath(__file__))\n 48|sys.path.insert(0, _DIR)\n 49|from lt_3candle_eod import (\n 50| DB_PATH, init_db,\n 51| build_renko, compute_vidya, fetch_daily_ohlc,\n 52| compute_and_store_anchors, load_anchors,\n 53|)\n 54|\n 55|# Kotak — live trading (quotes, orders, positions)\n 56|# Flattrade — all historical candle fetching (5-min intraday + daily OHLC)\n 57|# Kotak does NOT have a history/candle API\n 58|API_KEY=\"f6a95f...4716\"\n 59|HOST = \"https://kotak.openalgo.theworkpc.com\"\n 60|\n 61|FT_API_KEY=\"bace7f...7b62\"\n 62|FT_HOST = \"https://flattrade.openalgo.theworkpc.com\"\n 63|\n 64|# ── Log setup ─────────────────────────────────────────────────────────────────\n 65|_LOG_DIR = os.path.join(_DIR, \"logs\")\n 66|os.makedirs(_LOG_DIR, exist_ok=True)\n 67|LOG_FILE = os.path.join(_LOG_DIR, \"lt_3candle.log\")\n 68|try:\n 69| sys.stdout = open(LOG_FILE, \"a\", buffering=1)\n 70|
... [23774 chars total, truncated]
Let me read the rest of both bots to get the full picture:
2026-04-22 15:59:22
TOOL (hermes, Hermes-4-405B)
{"content": " 501| price=price, strategy=STRATEGY_TAG,\n 502| )\n 503| log(f\"ORDER {action} {symbol} qty={LOT_SIZE} price={price} [{reason}]\")\n 504| return order\n 505| except Exception as e:\n 506| log(f\"Order error {action} {symbol}: {e}\")\n 507| self.telegram.send(f\"🔴 Order error {action} {symbol}: {e}\")\n 508| return None\n 509|\n 510| # ── BOD setup ─────────────────────────────────────────────────────────────\n 511| def setup_day(self):\n 512| \"\"\"Called once at market open. Load VIDYA, set bias, check positions, seed candles.\"\"\"\n 513| today = dt.now().strftime(\"%Y-%m-%d\")\n 514|\n 515| # Load VIDYA anchor\n 516| anchors = load_anchors(self.conn, today)\n 517| if not anchors:\n 518| log(\"No anchors for today — computing on-demand\")\n 519| try:\n 520| all_candles = fetch_daily_ohlc(self.ft_client, self.conn)\n 521| past = [c for c in all_candles if c['date'] < today]\n 522| bricks = build_renko(past)\n 523| vidya = compute_vidya(bricks)\n 524| compute_and_store_anchors(self.conn, bricks, vidya, for_date=today)\n 525| anchors = load_anchors(self.conn, today)\n 526| except Exception as e:\n 527| log(f\"EOD fallback error: {e}\")\n 528|\n 529| if not anchors:\n 530| log(\"CRITICAL: Cannot determine VIDYA — bot will not trade today\")\n 531| return False\n 532|\n 533| self.state['fixed_vidya'] = anchors['fixed_vidya']\n 534| vidya = anchors['fixed_vidya']\n 535|\n 536| fut_ltp = self.get_futures_ltp()\n 537| if not fut_ltp:\n 538| log(\"Cannot fetch futures LTP at BOD\")\n 539| return False\n 540|\n 541| bias = \"GREEN\" if fut_ltp > vidya else \"RED\"\n 542| self.state['bias'] = bias\n 543| log(f\"BOD | VIDYA={vidya} Futures={fut_ltp:.1f} Bias={bias}\")\n 544|\n 545| # Seed 5-min/15-min candle DB\n 546| self.seed_intraday_candles()\n 547|\n 548| # Replay window if fresh start (no last_15min_ts from today)\n 549| if not self.state.get('last_15min_ts'):\n 550| self.replay_15min_window()\n 551|\n 552| # Check overnight positions\n 553| pos = self.state.get('position')\n 554| if pos:\n 555| broker_qty = self.get_broker_qty(pos['symbol'])\n 556| if broker_qty == 0:\n 557| log(f\"Overnight position {pos['symbol']} not at broker — clearing\")\n 558| self.telegram.send(f\"⚠️ Overnight {pos['symbol']} not at broker — state cleared\")\n 559| self.state['position'] = None\n 560| self.state['setup_count'] = 0\n 561
... [24905 chars total, truncated]
2026-04-22 15:59:22
TOOL (hermes, Hermes-4-405B)
{"content": " 501| start_date=start_date,\r\n 502| end_date=end_date\r\n 503| )\r\n 504|\r\n 505| # Handle pandas DataFrame (Flattrade returns DataFrame)\r\n 506| if hasattr(data, 'iterrows'): # It's a DataFrame\r\n 507| if data.empty:\r\n 508| return []\r\n 509| candles = []\r\n 510| for idx, row in data.iterrows():\r\n 511| # Get timestamp from index\r\n 512| ts = row.timestamp if hasattr(row, 'timestamp') else idx.timestamp() if hasattr(idx, 'timestamp') else 0\r\n 513| candles.append({\r\n 514| 'timestamp': ts,\r\n 515| 'open': float(row.get('open', row.get('Open', 0))),\r\n 516| 'high': float(row.get('high', row.get('High', 0))),\r\n 517| 'low': float(row.get('low', row.get('Low', 0))),\r\n 518| 'close': float(row.get('close', row.get('Close', 0))),\r\n 519| 'volume': float(row.get('volume', row.get('Volume', 0))),\r\n 520| })\r\n 521| # Filter out in-progress candles\r\n 522| now_ts = time.time()\r\n 523| interval_seconds = interval_minutes * 60\r\n 524| candles = [c for c in candles if (now_ts - c['timestamp']) >= interval_seconds]\r\n 525| candles.sort(key=lambda x: x['timestamp'])\r\n 526| return candles\r\n 527|\r\n 528| if not data:\r\n 529| return []\r\n 530|\r\n 531| # Parse candles - handle multiple response formats\r\n 532| candles = []\r\n 533|\r\n 534| # Format 1: Dict with 'data' key containing list\r\n 535| if isinstance(data, dict):\r\n 536| data_arr = data.get('data', [])\r\n 537| for c in data_arr:\r\n 538| # Handle list format [timestamp, open, high, low, close, volume]\r\n 539| if isinstance(c, list):\r\n 540| candles.append({\r\n 541| 'timestamp': c[0],\r\n 542| 'open': float(c[1]),\r\n 543| 'high': float(c[2]),\r\n 544| 'low': float(c[3]),\r\n 545| 'close': float(c[4]),\r\n 546| 'volume': float(c[5]) if len(c) > 5 else 0,\r\n 547| })\r\n 548| # Handle dict format\r\n 549| elif isinstance(c, dict):\r\n 550| # Handle both 'timestamp' and 'time' keys\r\n 551| ts = c.get('timestamp', c.get('time', 0))\r\n 552| candles.append({\r\n 553|
... [25239 chars total, truncated]
2026-04-22 15:59:22
ASSISTANT (hermes, Hermes-4-405B)
2026-04-22 15:59:22
TOOL (hermes, Hermes-4-405B)
{"content": " 1001| self.trailing_activated = True\r\n 1002| self.trailing_stop_price = self.entry_price # Move to breakeven first\r\n 1003| self.save_state()\r\n 1004|\r\n 1005| if self.trailing_activated:\r\n 1006| # Update trailing stop to lock in profits\r\n 1007| if current_opt_ltp > self.trailing_stop_price:\r\n 1008| self.trailing_stop_price = current_opt_ltp\r\n 1009| self.save_state()\r\n 1010| if current_opt_ltp < self.trailing_stop_price:\r\n 1011| return \"trailing_stop\"\r\n 1012|\r\n 1013| else: # SHORT\r\n 1014| unrealized_pnl = (self.entry_price - current_opt_ltp) * self.quantity\r\n 1015| if unrealized_pnl <= -STOP_LOSS_AMOUNT:\r\n 1016| return \"stop_loss\"\r\n 1017|\r\n 1018| # 3. Trailing stop - update to lowest price seen\r\n 1019| if not self.trailing_activated:\r\n 1020| profit_pct = (self.entry_price - current_opt_ltp) / self.entry_price\r\n 1021| if profit_pct >= TRAILING_ACTIVATION_PCT:\r\n 1022| self.trailing_activated = True\r\n 1023| self.trailing_stop_price = self.entry_price\r\n 1024| self.save_state()\r\n 1025|\r\n 1026| if self.trailing_activated:\r\n 1027| # Update trailing stop to lock in profits\r\n 1028| if current_opt_ltp < self.trailing_stop_price:\r\n 1029| self.trailing_stop_price = current_opt_ltp\r\n 1030| self.save_state()\r\n 1031| if current_opt_ltp > self.trailing_stop_price:\r\n 1032| return \"trailing_stop\"\r\n 1033|\r\n 1034| # 4. Time exit\r\n 1035| if self.entry_time:\r\n 1036| hold_seconds = (dt.now() - self.entry_time).total_seconds()\r\n 1037| if hold_seconds >= MAX_HOLD_MINUTES * 60:\r\n 1038| return \"time_exit\"\r\n 1039|\r\n 1040| return None\r\n 1041|\r\n 1042| # ============================================================================\r\n 1043| # MAIN TRADING LOOP\r\n 1044| # ============================================================================\r\n 1045|\r\n 1046| def run(self):\r\n 1047| \"\"\"Main trading loop\"\"\"\r\n 1048| self.logger.info(f\"Starting Momentum Bot: {self.profile_name} for {self.symbol}\")\r\n 1049|\r\n 1050| # Initial candle fetch\r\n 1051| self.update_candles()\r\n 1052| self.logger.info(f\"Loaded {len(self.candles_60min)} 60-min, {len(self.candles_15min)} 15-min candles\")\r\n 1053|\r\n 1054| while True:\r\n 1055| try:\r\n 1056| # Check market hours\r\n 1057| if not self.is_market_open():\r\n 1058|
... [7089 chars total, truncated]
2026-04-22 15:59:22
ASSISTANT (hermes, Hermes-4-405B)
Now I have a full picture of both bots. Here's the breakdown of **nuances** I found — things that aren't in the generic checklist but matter a lot in practice:
---
## Key Nuances Across Both Bots
### 1. **Dual-Broker Setup** (three_candle_bot)
The 3-candle bot uses **two different brokers simultaneously**:
- **Kotak** for live trading (quotes, orders, positions)
- **Flattrade** for historical candle data (Kotak has NO history API)
- This is a critical infrastructure decision — the checklist implies single-broker but in practice they needed Flattrade for candles since Kotak doesn't provide them.
### 2. **LTP Parsing — The `data` Wrapper Problem**
Both bots handle responses differently across brokers:
```python
# 3-candle bot uses quotes() and handles 'data' wrapper:
def _parse_ltp(self, data):
inner = data.get('data', data) # ← Kotak wraps in 'data'
for key in ('ltp', 'lp', 'last_price'):
v = inner.get(key, 0)
```
But momentum_bot uses `quote(token=)` (wrong API call per the checklist!) and doesn't handle the `data` wrapper — it would **fail on Kotak** but works on Flattrade/Shoonya (no wrapper).
### 3. **In-Progress Candle Filtering**
Both bots filter out candles younger than the interval period:
```python
# momentum_bot:
candles = [c for c in candles if (now_ts - c['timestamp']) >= interval_seconds]
# 3-candle_bot:
c['ts'] < current_5min # slot-based exclusion
```
This is **critical** — in-progress candles corrupt indicator calculations.
### 4. **3-Candle Bot's Candle Architecture** (unique)
The 3-candle bot builds its own candles locally:
- Fetches 5-min from Flattrade → stores in SQLite
- Builds 15-min OHLC from 3× 5-min candles in DB
- Replays 15-min window on restart to restore state
- This is a **sophisticated multi-layer local candle system** not covered in the checklist
### 5. **State Reset on New Day**
```python
# 3-candle_bot — keeps position but resets counters
if s.get("date") != today:
s = {**DEFAULT_STATE, "date": today, "position": position} # ← position preserved
```
NRML positions carry overnight; only counters reset.
### 6. **Orphan Position Detection**
Both bots check broker for positions not tracked in state:
```python
# 3-candle_bot
total = self.total_broker_long_lots()
if total and total > 0:
self.telegram.send(f"⚠️ ORPHAN POSITION | {total} lots at broker")
```
This catches cases where orders filled but bot state is out of sync.
### 7. **optionsymbol API Fallback**
```python
# 3-candle_bot
resp = self.client.optionsymbol(exchange="NFO", expiry_date=EXPIRY_DATE,
offset="ATM", option_type=option_type, underlying=SYMBOL_UNDER)
# Falls back to manual if API fails:
return f"{SYMBOL_UNDER}{EXPIRY_DATE}{atm_strike(fut_ltp)}{option_type}"
```
### 8. **Candle Staleness Alerting**
3-candle bot has a dedicated stale candle detector:
- Triggers after 20 min gap in 5-min candle feed
- One alert per episode (throttled with `_candle_stale_alerted` flag)
### 9. **Exit Price Logic — BUY vs SELL Offsets Are Opposite**
```python
# 3-candle_bot — CHECKLIST EVEN EMPHASIZES THIS
offset = LIMIT_OFFSET if action == "BUY" else -LIMIT_OFFSET
price = round(opt_ltp + offset, 1) # Same formula, opposite meaning!
```
`+5` for BUY (aggressive fill), `-5` for SELL (passive fill). The checklist calls this out explicitly.
### 10. **momentum_bot BUG: Uses `quote(token=)` not `quotes(symbol=)`**
Line 666-669 and 954: `self.client.quote(token=..., exchange=...)` — the checklist explicitly warns this is wrong. The 3-candle bot does it correctly with `quotes(symbol=...)`.
### 11. **BOD Setup Pattern**
3-candle bot has elaborate BOD (Beginning of Day) initialization:
- Compute/store VIDYA anchors
- Seed intraday candles from Flattrade
- Replay 15-min window to restore state
- Check overnight positions at broker
### 12. **Order Verification — Broker as Source of Truth**
Both bots verify fills by **re-querying broker position** after every order:
```python
time.sleep(2)
qty = self.get_broker_qty(sym)
if qty and qty >= LOT_SIZE:
# confirmed
```
Never trust the order response — always check broker.
---
## Summary: What's NOT in the Checklist
1. Dual-broker architecture (Kotak for trading, Flattrade for candles)
2. The `data` wrapper parsing for Kotak responses
3. Local multi-layer candle building (5-min → 15-min in SQLite)
4. Candle staleness monitoring & alerting
5. Orphan position detection at every poll
6. `optionsymbol` API fallback chain
7. BOD VIDYA anchor computation pattern
8. `quote(token=)` vs `quotes(symbol=)` — momentum_bot uses the wrong one
Want me to update the checklist with these nuances, or start building the new bot based on this understanding?