cd /news/machine-learning/i-built-an-ai-native-trading-engine-… Β· home β€Ί topics β€Ί machine-learning β€Ί article
[ARTICLE Β· art-30860] src=dev.to β†— pub= topic=machine-learning verified=true sentiment=↑ positive

I Built an AI-Native Trading Engine in Python. 5 Months Later, Here's What Changed

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.

read7 min views1 publishedJun 17, 2026

9 strategies β†’ 12. ML scoring, backtesting, partial take-profit, Telegram bot that survived a 3-echelon audit. Open source, MIT, trading real money.

Trading bots come in two flavors: black-box SaaS at $50/month, or GitHub scripts that crash at 3 AM. I wanted neither.

Five 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.

This 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.

v1 was clean. v2 is battle-tested.

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚      Hermes Agent          β”‚
                    β”‚  Voice/chat orchestrator   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚ MCP / REST (port 8766)
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚     bybit-ws (systemd)      β”‚
                    β”‚                             β”‚
                    β”‚  main.py β€” 30s light cycle  β”‚
                    β”‚         β€” 120s heavy cycle  β”‚
                    β”‚         β€” 480s x10 cycle    β”‚
                    β”‚                             β”‚
                    β”‚  β”Œβ”€ auto_sl.py              β”‚
                    β”‚  β”œβ”€ trailing_sl.py           β”‚
                    β”‚  β”œβ”€ trailing_sl_x10.py   β˜…   β”‚
                    β”‚  β”œβ”€ partial_tp.py        β˜…   β”‚
                    β”‚  β”œβ”€ funding_rotation.py  β˜…   β”‚
                    β”‚  β”œβ”€ ml_scorer.py         β˜…   β”‚
                    β”‚  β”œβ”€ backtest.py          β˜…   β”‚
                    β”‚  β”œβ”€ gridsignal_scanner.py    β”‚
                    β”‚  β”œβ”€ pump_detect.py           β”‚
                    β”‚  β”œβ”€ rpc.py (JSON-RPC)        β”‚
                    β”‚  └─ state_db.py (SQLite)     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚ WebSocket
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚       Bybit API v5          β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β˜… = new in v2

Three 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.

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.

Strategy Lev Timeframe What's New
Bollinger Grid LONG 3x Daily β€”
Bollinger Grid SHORT 3x Daily β€”
Junk Short 3x Daily β€”
SL Re-entry 3x Daily β€”
DCA Ladder 3x β€” β€”
BB Scalping 10x M5 β€”
Mean Reversion 10x Daily β€”
Funding Momentum 10x Daily β€”
ATR Risk Sizing layer 15m β€”
Partial TP
β˜… dynamic 20β†’50% scale-out, no numpy
Trailing SL x10
β˜… x10 only Tight trailing on high-lev positions
Funding Rotation
β˜… auto Closes positions before negative funding hits

The 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.

After 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.

I trained a RandomForest classifier on it:

features = ['score', 'price_vs_lower', 'price_vs_upper',
            'volume_24h', 'funding_rate', 'rsi_14',
            'bb_squeeze', 'consecutive_down_days']
target   = 'is_profitable'  # 1 if PnL > 0, else 0

Results:

The 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.

Is 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.

You 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:

for day in trading_days:
    klines = fetch_klines(symbol, start=day.start_ms)
    signals = scan(klines, strategy='bollinger_grid')
    for sig in signals:
        trade = simulate(sig, klines[day:day+30])
        results.append(trade)

Tested on BTC, ETH, SUI, and ADA with Daily signals:

Symbol Win Rate Avg PnL Best Trade Worst Trade
SUIUSDT 42.9% +6.56% +27.3% βˆ’12.1%
ADAUSDT 38.5% +4.82% +19.4% βˆ’11.7%
BTCUSDT 35.7% +3.11% +14.2% βˆ’9.3%
ETHUSDT 33.3% +1.95% +11.8% βˆ’10.5%

Not 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.

Most bots close a position all at once. Partial TP scales out gradually:

tp_levels = calculate_partial_tp(
    entry_price, mark_price, unrealized_pnl_pct
)

No numpy. Pure statistics

module. Works on any Python 3.11+ without extra dependencies.

High-leverage positions move fast. The new trailing_sl_x10

module runs every heavy cycle, but only for positions with leverage β‰₯10. It tightens the stop-loss when:

Negative funding rates eat your margin. The rotation module checks every heavy cycle:

The @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.

Not "slow" β€” dead silent. But the logs were clean, the process was running, systemd showed active

. Classic Heisenbug.

I ran a 3-echelon AI audit β€” three agents in parallel, each with a different focus:

Four minutes later: 14 findings. Five CRITICAL. Highlights:

_valid_symbol()

β€” called in 3 places, subprocess.run()

calls inside async handlersUPDATE ... SET +1

without WHERE check).gitignore

blind spotAfter fixes: 45/45 smoke tests green, bot responds instantly. Full story in the separate article.

Every fix, every feature, every release β€” the smoke test suite runs:

@pytest.mark.asyncio
async def test_scan_button_does_not_block_event_loop():
    """Verify scan handler yields control back to event loop."""
    with patch('subprocess.run') as mock_run:
        mock_run.return_value = CompletedProcess(...)
        start = time.monotonic()
        await cmd_scan(update, context)
        assert time.monotonic() - start < 0.5

def test_race_condition_scan_limit():
    """Double-tap must not exceed daily limit."""
    db.execute("UPDATE users SET scans_today = 9 WHERE id = 1")
    await cmd_scan(...)   # 10th β€” pass
    with pytest.raises(RateLimitExceeded):
        await cmd_scan(...)  # 11th β€” blocked

Tools: pytest

, pytest-asyncio

, unittest.mock

, SQLite :memory:

databases. No external services β€” pure deterministic tests in under 2 seconds.

45 tests. 0 failures. CI green.

Metric v1 (Jan 2026) v2 (Jun 2026)
Strategies 9 12
Codebase 2,100 lines 4,600+ lines
Test coverage 0 tests 45 smoke tests
ML layer None RandomForest F1=0.69
Backtesting None Walk-forward on REST klines
Risk management Basic SL Partial TP + trailing x10 + funding rotation
Telegram bot Working, untested 45/45 tests, 14 bugs fixed
Deployment Manual systemd + white-label script
Monitoring None Prometheus /metrics + daily health alerts
Memory ~200 MB ~23.5 MB (SQLite beats in-memory dicts)

The memory drop isn't a typo. Moving from Python dicts to SQLite cut RAM by 88%.

git clone https://github.com/poliakarmai/bybit-ws
cd bybit-ws
cp config.example.yaml config.yaml
pip install -r requirements.txt
python -m bybit-ws

Or deploy as a systemd service:

sudo cp bybit-ws.service /etc/systemd/system/
sudo systemctl enable --now bybit-ws

GitHub: poliakarmai/bybit-ws

Bot: @Gridbolbot

License: MIT

The author is a trader and AI engineer. Writes about trading infrastructure, multi-agent systems, and the boring risk management that actually saves accounts.

── more in #machine-learning 4 stories Β· sorted by recency
── more on @bybit 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/i-built-an-ai-native…] indexed:0 read:7min 2026-06-17 Β· β€”