bybit-ws: a trading tool, an analyst, and an assistant
bybit-ws: a trading tool, an analyst, and an assistant A live Bybit futures system that scans, scores, argues with itself before every entry, learns from its own closed trades β and answers when I message it from my p
bybit-ws: a trading tool, an analyst, and an assistant
A live Bybit futures system that scans, scores, argues with itself before every entry, learns from its own closed trades β and answers when I message it from my phone. Here is the machinery, including the parts that are still broken.
The entry signal is the least interesting part of a live trading system. What decides whether the thing survives is everything around it: what happens when the LLM times out, what happens when a balance reads zero and the bot quietly refuses to trade for two days.
This is a write-up of that part. No performance numbers, on purpose: I can't show you the query behind a PnL figure here, so I don't show the figure.
I built bybit-ws for one user: me. Python and asyncio, SQLite in WAL mode as the single source of truth, a systemd unit, an HTTP-RPC surface on localhost, an MCP wrapper so an agent can drive it. Boring interfaces on purpose: every clever coupling is something I'd have to debug at four in the morning.
Three jobs, one process
An execution tool, an analyst, and an assistant β in one systemd unit, over one SQLite file.
Telegram (me, sleepy, holding a phone)
β
agent layer (Hermes profile)
β β β
MCP tools HTTP-RPC shell
β β β
βββββββββββΌβββββββββΌββββββββββΌβββββββββββββββ
β bybit-ws (systemd user unit) β
β async loop βββΊ market data (WebSocket) β
β βββΊ entry scoring ββΊ LLM judge ββ β
β βββΊ regime model β β
β βββΊ risk gate βββββββββββββββββββ€ β
β βββΊ order router ββββββββββββββββ β
β SQLite Β· self-learn Β· offline bench β
βββββββββββββββββ¬ββββββββββββββββββββββββββββ
Bybit API v5
Two decisions matter:
- The exchange is not the database, and neither is trusted blindly. Positions are reconciled across the RPC view, the exchange and SQLite. When they disagree, the disagreement is the event worth alerting on.
- AI is not the decision-maker. The LLM sits inside the decision path as a veto, never as the thing that picks the trade.
The request path: what happens when I ask for a trade
I type something human into Telegram. Not a command grammar β a sentence. The agent parses intent, then works through this, reporting each step.
1. Scan. Candidates get scored on structure: band position, RSI, volume, funding, band compression, multi-timeframe confluence. If I name a symbol outside the ranking, the scanner does not pretend it looked β the symbol is either analysed from public candles or reported as skipped, never as no data. A candidate that vanishes silently looks identical to one that was never checked.
2. Size it. A sizing function maps my intent β "risk about this much of the account" β onto a quantity. Equity comes from the account level, not a per-coin balance line; the quantity is rounded to the exchange's step and formatted as a clean decimal string, because 18.900000000000002 is a valid float and an invalid order.
3. Preview, then execute. The entry endpoint is two-phase. The first call returns what would happen β side, quantity, stop, target, leverage β with confirm_required: true, and places nothing. The second, explicitly confirmed, places the order. That preview is the last point where a human catches a misparsed intent, and it's the same endpoint the automated branch uses. The caller sends an Idempotency-Key derived from the confirmed preview; a replay inside the hour returns the original response instead of opening a second position.
4. Verify, don't assume. After the order the system reads the position back and compares it with what it asked for. Fills land in the database one cycle later, so an instant check produces a false "it didn't open" β the agent waits, re-reads, and reports what it observed.
5. Mark it as mine. A manual entry is tagged as manual. That tag decides what the learner sees: self-learning excludes manual trades. My discretionary trades aren't the bot's performance, and letting the learner eat them would teach it my worst habits and call that strategy.
Two choices here look like bugs otherwise: the manual endpoint doesn't silently cap the leverage I ask for β my account, my call, discipline lives in the sizing step β and a kill switch closes everything and blocks new entries until I say otherwise.
Trust, and how it's enforced
Credibility isn't in the strategy; it's in whether the system's own reporting can be caught lying.
-
Three money flows, kept apart. Bybit's
closed-pnlis net of the closing fee but not the opening one. Funding arrives as a separate ledger settlement. The transaction log carries gross and net per trade. The identity is asserted in code: net equals gross minus both fees, and account PnL equals trade flow plus funding. Sum them into one number and you report a profit that never existed. - Reconciliation against the exchange, not against itself. A local position the exchange doesn't have is a bug, not a rounding error β and because fills land a cycle late, the comparison carries a grace window.
- Health checks with exit codes, after every change: service state, RPC liveness, cycle progression, the service's interpreter having its dependencies, position parity, database integrity, smoke suite. Prose lies silently; a script with an exit code doesn't.
And a watchdog with a narrow definition of silence: no entries for a window and enough block reasons inside it is an incident; a wall of "skipped, not a candidate" lines is normal filtering. The first version got this backwards β it counted a stopped bot as a quiet, healthy one.
Analysis before a trade: three kinds of "AI"
The word AI does too much work in most trading-bot descriptions. Here there are three layers, and mixing them up is how you get a bot that "decides" things nobody can explain.
| Layer | What it is | How it fails |
|---|---|---|
| Deterministic | Scoring, sizing, stops, targets, routing, risk limits | Bugs, exchange quirks β reproducible and testable |
| Statistical | Regime classifier, bandit parameter choice, drift detection | Stale or unrepresentative sample; silently confident |
| Language model | Pre-entry judge: pass or revise, with reasons | Timeouts, drifted judgement, cost, latency |
The deterministic layer owns money movement. The statistical layer only re-weights parameters β how eager the entries are, how tight a stop sits β and it can't touch the absolute limits: per-trade risk cap and the leverage the automated branch is allowed to set. The LLM only gets a veto.
The judge: fail-closed up to a threshold
Before an entry that scored high enough to be worth the tokens, the signal becomes a compact factual blob β band position, score components, proposed stop and target, funding, regime, confluence β handed to a hosted chat model with one question: is anything here contradictory?
Three choices make it something I trust more than my own opinion at 3 a.m.
- Errors block by default. If the judge times out or returns junk, the verdict is revise and the entry does not happen. A model that is down is not a model that says yes.
-
A circuit breaker with a stated escape hatch. Consecutive failures open a cooldown instead of hammering a dead dependency. While it's open, entries stay blocked; only after an outage lasts long enough does the system drop into an explicit degraded mode β deterministic path only, low confidence, a
fallbacknote in the log. Fail-closed by default, fail-open by announcement. - Caching by signal fingerprint. Identical signals don't re-bill the model; verdicts are reused for a short window.
What the judge is good at is catching what the rules can't cross-check: the score looks strong, but funding is paying the other side and the regime model has this as a downtrend on a timeframe your scanner doesn't read. What it is bad at is predicting the market. I don't ask it to.
Learning from closed trades β and its limits
Self-learning has been rewritten the most, and the hard part was never the update rule β it was telling real improvement from noise.
The learner reads closed trades from SQLite, scores them across win rate, profit factor, Sharpe, drawdown and holding time, and adjusts parameters per regime β "the right tightness for a stop" is a different question in a trend than in chop. Every closed trade produces a small update; the heavier re-selection of a parameter set runs on a sample trigger, with a time fallback so a quiet market doesn't starve it.
What survived:
- Bandit selection with pruning. Parameters are arms, not one global setting. Losing arms get pruned; uncertainty picks what to try next, not what looks best so far.
- Drift and change-point detection. The learner asks whether the recent distribution still resembles the one its parameters were chosen on. If not, it prefers exploration over doubling down on an outdated winner.
- Canary comparison. A challenger runs alongside in a small, time-boxed comparison; the incumbent keeps the crown until the challenger wins on the sample, not on one good week.
- Robust updates and versioning. Outliers are skipped instead of dragging the average, and every parameter change is a versioned artefact β so "why did it behave differently last Tuesday" has an answer that is a diff, not a memory.
And the honest part no bot post mentions: the learner cannot tell you whether the edge exists. It tunes within the space you gave it. If the space is wrong, it will find a confident local optimum and defend it.
The bench, where ideas are supposed to die
Next to the live system sits an offline bench: same strategy code, deterministic backtest engine, cached candles, and a run manifest recording code revision, parameter hash, config hash and data hash. Two runs with the same manifest produce the same equity hash β asserted in the test suite, so reproducibility is a property, not a claim.
On top sits an anti-overfitting gate: a parameter set has to survive deflated Sharpe, probability of backtest overfitting and a permutation test. Labels come from a triple-barrier scheme β hit target, hit stop, or time out β not "was the next candle up". Regime detection is a separate unsupervised model with a change-point detector beside it, because the interesting moment is when the regime stops being what the model thinks.
The gap, plainly: the bench doesn't promote a winner into the live learner automatically β promotion means touching the live loop, and I'd rather have a manual step and a boring diff than an autopilot I can't explain.
The assistant is not a wrapper
A chat over the API took an evening. Deciding what it may assume took the rest.
Every answer is fetched live β positions, balance, risk state, journal β never recalled from conversation. If the service is down, the answer is "down", not the last known number dressed up as current. Read paths are free-form; write paths are narrow, explicit, and confirmed. And the assistant carries what a human would otherwise hold in their head: which symbols the bot watches, which order type gets rejected in which position mode, why a balance line and an equity figure disagree.
That last category is most of the product: quirks nobody wrote down until they bit me.
If you build one
- Put the money path behind a two-phase endpoint with explicit confirmation, and give the automated branch the same endpoint. Two paths that can place orders is two bugs.
- Make the LLM a filter, and make it fail closed. The moment it can cause a trade, your risk model lives in someone else's weights.
- Separate the learner from the ledger. Tag discretionary trades, exclude them, version every parameter change β otherwise "the bot got better" is folklore.
- Build the watchdog before the strategy. A bot that stopped trading doesn't announce itself.
It's not a fund: one machine, my money, and every postmortem is mine.
The bot, the code, and me
- Telegram bot: @Gridbolbot β the public side of the same engine: anyone can pull scans and signals; execution stays on my account.
- Source: github.com/poliakarmai/bybit-ws β AGPL-3.0
- Email: [email protected]
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.