Record the Input and Decision
The first program can establish whether it observes and records the market correctly before it has any ability to submit an order. A saved observation also gives later tests an input that can be replayed instead of fetched again.
This example polls one Bybit public ticker three times. Its decision records only whether the price string changed. It deliberately produces no buy or sell command; the point is to establish the observation and decision boundary before adding a strategy or executor.
Install bybit-api in a small Node.js project and save this as observe.mjs:
observe.mjs
import { appendFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
import { setTimeout as delay } from 'node:timers/promises';
import { performance } from 'node:perf_hooks';
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5();
let previousPrice;
for (let attempt = 0; attempt < 3; attempt++) {
const started = performance.now();
const response = await client.getTickers({ category: 'linear', symbol: 'BTCUSDT' });
if (response.retCode !== 0) throw new Error(response.retMsg);
const ticker = response.result.list.find((item) => item.symbol === 'BTCUSDT');
if (
!ticker ||
typeof ticker.lastPrice !== 'string' ||
!Number.isFinite(Number(ticker.lastPrice)) ||
Number(ticker.lastPrice) <= 0
) {
throw new Error('Valid BTCUSDT price was not returned');
}
const observation = {
id: randomUUID(),
symbol: ticker.symbol,
price: ticker.lastPrice,
receivedAt: new Date().toISOString(),
requestMs: performance.now() - started,
};
const decision = {
observationId: observation.id,
changed: previousPrice !== undefined && previousPrice !== observation.price,
orderSent: false,
reason: observation.requestMs > 5_000 ? 'slow-response' : 'observation-only',
};
await appendFile('observations.ndjson', JSON.stringify({ observation, decision }) + '\n');
console.log(decision);
previousPrice = observation.price;
if (attempt < 2) await delay(1_000);
}
The changed field compares the returned strings, not normalized numerical equivalence. That choice is sufficient for an observation fixture and should be changed deliberately if the later application treats different decimal formats as equal.
Replay the Same Observations
Read the saved lines and recompute the decision using the preceding observation. Compare the result with the stored decision. No request is required during replay, so an exchange outage cannot alter the fixture.
The slow-response reason records one application policy. It does not measure exchange event age or validate a trading price. A real calculation should identify the timestamps and fields it requires rather than interpreting any recent HTTP response as sufficient market state.
Add One Boundary at a Time
For continuous data, the public WebSocket example adds snapshots, partial updates, and reconnect invalidation. The durable collector adds storage and explicit gaps. The local paper executor adds stated fill assumptions.
Private Account State is another boundary with different initialization and recovery requirements. Adding it should not silently turn a public observation program into an order-capable process.
Before any executor is connected, test missing data, rejected requests, interrupted writes, and replayed observations. Pre-trade checks and halt handling then govern dispatch independently of the decision function.
Related articles
Continue from here