5.3 KiB
Plan: Move volume computation from executor to fused_engine
Goal
fused_engine computes per-leg order parameters (funds/size), precision-rounded.
Executor becomes a thin dispatcher — validate+simulate (paper) or place+record (live).
New signal schema
{
"type": "signal",
"live": false,
"legs": [
{"pair": "BTC-USDT", "side": "buy", "funds": "5.01", "base_increment": "0.00001", "quote_increment": "0.01", "base_min": "0.0001"},
{"pair": "ETH-BTC", "side": "sell", "size": "0.0495", "base_increment": "0.001", "quote_increment": "0.01", "base_min": "0.001"},
{"pair": "ETH-USDT", "side": "sell", "size": "0.0498", "base_increment": "0.001", "quote_increment": "0.01", "base_min": "0.001"}
],
"starting_volume": "5.01",
"predicted_bps": 12.5,
"books": [{"symbol": "BTC-USDT", "bids": [...], "asks": [...]}, ...],
"ts_ms": 1779400000000,
"book_ts_ms": 1779400000000,
"t_arrive_ms": 1779400000000,
"t_eval_ms": 1779400000000,
"triangle_key": ["USDT","BTC","ETH"],
"correlation_id": "abc123"
}
live=true: nobooks, nosize/funds— executor places leg 0 withstarting_volume, KuCoin fills drive the chainlive=false:bookspresent for simulation,funds/sizepre-computed, executor passes them straight toorder_test()
fused_engine changes
1. Store symbol precision metadata (symbols_api.c)
Already fetches /api/v2/symbols. Add to trading_pair_t:
char base_increment[32];
char quote_increment[32];
char base_min_size[32];
Parse from baseIncrement, quoteIncrement, baseMinSize fields in the API response.
2. Per-leg volume computation (evaluate.c)
After computing max_volume in evaluate_symbol, add a new function that runs the executor's current volume logic in C:
compute_leg_volumes(
triangle, books, max_volume, fee_rates,
out_leg_funds_or_size[3],
out_leg_side[3]
)
Steps (mirrors executor.py:550-608):
starting = cost_to_precision(max_volume, leg0.quote_increment)- For leg 0 (buy):
net = starting * (1 - fee),base = floor(net / ask, base_inc), recomputefunds = cost_precision(base * ask, quote_inc) - For leg 1:
input = leg0.base_output. Same buy/sell logic - For leg 2: same
- Store
fundsfor buys,sizefor sells
The computation is pure arithmetic (no I/O): Decimal-style operations using strtod/sprintf with integer math or floating point with epsilon guards. Or use a lightweight bignum lib.
3. Signal JSON format (evaluate.c and events.c)
Update both format_signal_json (evaluate.c, unused today — keep for debugging) and send_signal_to_executor (events.c) to emit the new schema, including per-leg funds/size and increment fields.
Remove fee_rate and exchange_rate from leg JSON — executor no longer needs them.
4. Live vs paper mode selection
Config: add live_mode: bool to config.yaml (default false). fused_engine reads it and conditionalizes signal content.
Executor changes (executor.py)
1. Remove dead code
get_symbol_meta()andSymbolMeta(kucoin_api.py:226-228 + dataclass)- Volume propagation (executor.py:550-558)
- Precision rounding per leg (executor.py:564-608)
_precheck_volume()→ simplify to just checkstarting_volume >= min_sizeper leg using thebase_min/base_incrementfrom the signal- Fee computation in paper fill simulation (executor.py:823) → use fee_currency from signal leg
legs[i - 1].get("fee_rate")→ executor no longer knows fee rates, use from signal
2. New signal parsing
Extract live, legs with pre-computed funds/size, increment fields, starting_volume.
3. live=true path
- Leg 0:
order_place(funds=starting_volume), await fill - Subsequent legs:
order_place(funds=actual_propagated)— no, the actual fill drives the next leg. Keep current propagation (actual fill.filled_volume feeds next leg input) — this stays because KuCoin returns real fills.
4. live=false path (paper)
- Each leg:
order_test(funds=leg.funds)ororder_test(size=leg.size)— pass through - Paper fill simulation: use signal
books+ legbase_increment/quote_incrementto computedeal_funds← this is the only volume math remaining in the executor - Simulate: apply fee to base_or_quote, round to precision, compute effective deal_funds
- profit =
fills[2].deal_funds - fills[0].deal_funds
5. Paper fill simulation (what stays)
The executor must still:
- Read
books[i].asks[0].price/bids[0].price - For buys: compute how much base you'd get for
fundsat that top-of-book price, after fee, rounded tobase_increment - For sells: compute how much quote you'd get for
sizeat that top-of-book price, after fee, rounded toquote_increment - This is ~20 lines per leg, no external dependencies
Migration order
- Add precision fields to
trading_pair_tand parse from API - Add volume computation in
evaluate.c - Update signal JSON format (both functions)
- Strip executor down
- Test paper mode, then live mode
Risk: Decimal precision in C
The executor uses Python Decimal for exact precision. In C, we can:
- Use
doublewithceil()/floor()and1e-10epsilon guards (simple, adequate for crypto tick sizes) - Or use integer math: multiply by 10^decimals, do integer rounding, convert back
For crypto tick sizes (0.00001, 0.01, 0.1), doubles are precise enough. No need for bignum.