{"slug": "i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed", "title": "I Built an AI-Native Trading Engine in Python. 5 Months Later, Here's What Changed", "summary": "A developer built an AI-native trading engine for Bybit futures, evolving from a simple Bollinger Bands scanner to a system with machine learning scoring, backtesting, partial take-profit, and a Telegram bot. After five months, the engine logged 282 real signals and trained a RandomForest classifier to predict trade profitability. The open-source project, bybit-ws, now runs three trading cycles and uses SQLite as a single source of truth.", "body_md": "9 strategies → 12. ML scoring, backtesting, partial take-profit, Telegram bot that survived a 3-echelon audit. Open source, MIT, trading real money.\n\nTrading bots come in two flavors: black-box SaaS at $50/month, or GitHub scripts that crash at 3 AM. I wanted neither.\n\nFive months ago I shipped v1 of **bybit-ws** — an AI-native trading engine for Bybit futures. The core loop was simple: scan Bollinger Bands daily, score every signal across 8 metrics, enter when the score passes 5.5/10. It worked. It made money. And then I kept building.\n\nThis is the v2 story: machine learning on real trade data, a backtesting engine that runs against historical klines, partial take-profit logic, and a Telegram bot that survived 14 production bugs found by three AI agents in parallel.\n\nv1 was clean. v2 is battle-tested.\n\n```\n                    ┌──────────────────────────┐\n                    │      Hermes Agent          │\n                    │  Voice/chat orchestrator   │\n                    └───────────┬────────────────┘\n                                │ MCP / REST (port 8766)\n                    ┌───────────▼────────────────┐\n                    │     bybit-ws (systemd)      │\n                    │                             │\n                    │  main.py — 30s light cycle  │\n                    │         — 120s heavy cycle  │\n                    │         — 480s x10 cycle    │\n                    │                             │\n                    │  ┌─ auto_sl.py              │\n                    │  ├─ trailing_sl.py           │\n                    │  ├─ trailing_sl_x10.py   ★   │\n                    │  ├─ partial_tp.py        ★   │\n                    │  ├─ funding_rotation.py  ★   │\n                    │  ├─ ml_scorer.py         ★   │\n                    │  ├─ backtest.py          ★   │\n                    │  ├─ gridsignal_scanner.py    │\n                    │  ├─ pump_detect.py           │\n                    │  ├─ rpc.py (JSON-RPC)        │\n                    │  └─ state_db.py (SQLite)     │\n                    └───────────┬────────────────┘\n                                │ WebSocket\n                    ┌───────────▼────────────────┐\n                    │       Bybit API v5          │\n                    └────────────────────────────┘\n```\n\n★ = new in v2\n\nThree cycles instead of two. The new **x10 cycle** (every 8 minutes) handles trailing stops for high-leverage positions without touching the 3x positions. The heavy cycle (every 2 minutes, down from 7) now includes partial take-profit and funding rotation checks.\n\n**State is SQLite, period.** v1 had a mix of in-memory dicts and JSON snapshots. v2 uses SQLite with WAL mode as the single source of truth. 8 tables, atomic UPDATEs with WHERE clauses, zero race conditions. JSON snapshots are backup-only.\n\n| Strategy | Lev | Timeframe | What's New |\n|---|---|---|---|\n| Bollinger Grid LONG | 3x | Daily | — |\n| Bollinger Grid SHORT | 3x | Daily | — |\n| Junk Short | 3x | Daily | — |\n| SL Re-entry | 3x | Daily | — |\n| DCA Ladder | 3x | — | — |\n| BB Scalping | 10x | M5 | — |\n| Mean Reversion | 10x | Daily | — |\n| Funding Momentum | 10x | Daily | — |\n| ATR Risk Sizing | layer | 15m | — |\nPartial TP |\n★ | dynamic | 20→50% scale-out, no numpy |\nTrailing SL x10 |\n★ | x10 only | Tight trailing on high-lev positions |\nFunding Rotation |\n★ | auto | Closes positions before negative funding hits |\n\nThe golden rule still applies: every entry passes through **scoring across 8+ metrics** with a 5.5/10 threshold. But now there's a new layer on top.\n\nAfter 5 months, the system had logged **282 real signals** with outcomes — entry price, exit price, PnL, whether it hit take-profit or stop-loss. That's a dataset.\n\nI trained a **RandomForest classifier** on it:\n\n```\nfeatures = ['score', 'price_vs_lower', 'price_vs_upper',\n            'volume_24h', 'funding_rate', 'rsi_14',\n            'bb_squeeze', 'consecutive_down_days']\ntarget   = 'is_profitable'  # 1 if PnL > 0, else 0\n```\n\nResults:\n\nThe model runs every heavy cycle, re-scores open positions, and can veto new entries. It's not a black box — feature importance is logged, so I can see *why* it made a decision.\n\nIs F1=0.69 \"AI that prints money\"? No. It's a filter that catches bad entries the heuristic would miss. In backtesting, the ML layer improved average PnL per trade by 18% just by rejecting the bottom quartile of signals.\n\nYou can't trust a strategy you haven't backtested. I built a walk-forward engine that pulls historical klines from Bybit's REST API and replays them day by day:\n\n```\n# Each day: scan → score → simulate entry → track PnL\nfor day in trading_days:\n    klines = fetch_klines(symbol, start=day.start_ms)\n    signals = scan(klines, strategy='bollinger_grid')\n    for sig in signals:\n        trade = simulate(sig, klines[day:day+30])\n        results.append(trade)\n```\n\nTested on BTC, ETH, SUI, and ADA with Daily signals:\n\n| Symbol | Win Rate | Avg PnL | Best Trade | Worst Trade |\n|---|---|---|---|---|\n| SUIUSDT | 42.9% | +6.56% | +27.3% | −12.1% |\n| ADAUSDT | 38.5% | +4.82% | +19.4% | −11.7% |\n| BTCUSDT | 35.7% | +3.11% | +14.2% | −9.3% |\n| ETHUSDT | 33.3% | +1.95% | +11.8% | −10.5% |\n\nNot every strategy is a winner. The backtest exposed that ETH is borderline — win rate barely above random, average PnL close to fees. That's valuable information. I'd rather know from backtesting than from a blown account.\n\nMost bots close a position all at once. Partial TP scales out gradually:\n\n```\n# Dynamic split: 20% at first TP → up to 50% at final TP\ntp_levels = calculate_partial_tp(\n    entry_price, mark_price, unrealized_pnl_pct\n)\n# First fill: 20% of position, SL moves to breakeven\n# Second fill: 30% more, trailing SL activates\n# 50% left rides with trailing stop\n```\n\nNo numpy. Pure `statistics`\n\nmodule. Works on any Python 3.11+ without extra dependencies.\n\nHigh-leverage positions move fast. The new `trailing_sl_x10`\n\nmodule runs every heavy cycle, but **only for positions with leverage ≥10**. It tightens the stop-loss when:\n\nNegative funding rates eat your margin. The rotation module checks every heavy cycle:\n\nThe @Gridbolbot (formerly @GridSignalBot) is a 2,153-line Telegram bot that scans markets, sends alerts, and lets users execute trades inline. Five months in, it went silent.\n\nNot \"slow\" — dead silent. But the logs were clean, the process was running, systemd showed `active`\n\n. Classic Heisenbug.\n\nI ran a **3-echelon AI audit** — three agents in parallel, each with a different focus:\n\nFour minutes later: **14 findings**. Five CRITICAL. Highlights:\n\n`_valid_symbol()`\n\n— called in 3 places, `subprocess.run()`\n\ncalls inside async handlers`UPDATE ... SET +1`\n\nwithout WHERE check)`.gitignore`\n\nblind spotAfter fixes: **45/45 smoke tests green**, bot responds instantly. Full story in the [separate article](https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-heres-how-it-works-54p0).\n\nEvery fix, every feature, every release — the smoke test suite runs:\n\n``` python\n@pytest.mark.asyncio\nasync def test_scan_button_does_not_block_event_loop():\n    \"\"\"Verify scan handler yields control back to event loop.\"\"\"\n    with patch('subprocess.run') as mock_run:\n        mock_run.return_value = CompletedProcess(...)\n        start = time.monotonic()\n        await cmd_scan(update, context)\n        assert time.monotonic() - start < 0.5\n\ndef test_race_condition_scan_limit():\n    \"\"\"Double-tap must not exceed daily limit.\"\"\"\n    db.execute(\"UPDATE users SET scans_today = 9 WHERE id = 1\")\n    await cmd_scan(...)   # 10th — pass\n    with pytest.raises(RateLimitExceeded):\n        await cmd_scan(...)  # 11th — blocked\n```\n\nTools: `pytest`\n\n, `pytest-asyncio`\n\n, `unittest.mock`\n\n, SQLite `:memory:`\n\ndatabases. No external services — pure deterministic tests in under 2 seconds.\n\n45 tests. 0 failures. CI green.\n\n| Metric | v1 (Jan 2026) | v2 (Jun 2026) |\n|---|---|---|\n| Strategies | 9 | 12 |\n| Codebase | 2,100 lines | 4,600+ lines |\n| Test coverage | 0 tests | 45 smoke tests |\n| ML layer | None | RandomForest F1=0.69 |\n| Backtesting | None | Walk-forward on REST klines |\n| Risk management | Basic SL | Partial TP + trailing x10 + funding rotation |\n| Telegram bot | Working, untested | 45/45 tests, 14 bugs fixed |\n| Deployment | Manual | systemd + white-label script |\n| Monitoring | None | Prometheus /metrics + daily health alerts |\n| Memory | ~200 MB | ~23.5 MB (SQLite beats in-memory dicts) |\n\nThe memory drop isn't a typo. Moving from Python dicts to SQLite cut RAM by 88%.\n\n```\ngit clone https://github.com/poliakarmai/bybit-ws\ncd bybit-ws\ncp config.example.yaml config.yaml\n# Insert your Bybit API keys\npip install -r requirements.txt\npython -m bybit-ws\n```\n\nOr deploy as a systemd service:\n\n```\nsudo cp bybit-ws.service /etc/systemd/system/\nsudo systemctl enable --now bybit-ws\n```\n\n**GitHub:** [poliakarmai/bybit-ws](https://github.com/poliakarmai/bybit-ws)\n\n**Bot:** [@Gridbolbot](https://t.me/Gridbolbot)\n\n**License:** MIT\n\n*The author is a trader and AI engineer. Writes about trading infrastructure, multi-agent systems, and the boring risk management that actually saves accounts.*", "url": "https://wpnews.pro/news/i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed", "canonical_source": "https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-5-months-later-heres-what-changed-317", "published_at": "2026-06-17 10:44:34+00:00", "updated_at": "2026-06-17 10:51:23.620759+00:00", "lang": "en", "topics": ["machine-learning", "artificial-intelligence", "developer-tools"], "entities": ["Bybit", "RandomForest", "SQLite", "Telegram", "MIT"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed", "markdown": "https://wpnews.pro/news/i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed.md", "text": "https://wpnews.pro/news/i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed.txt", "jsonld": "https://wpnews.pro/news/i-built-an-ai-native-trading-engine-in-python-5-months-later-here-s-what-changed.jsonld"}}