Skip to content

autoconvert: route from Kraken's pair table; make the second leg fill - #4

Merged
sethforprivacy merged 1 commit into
mainfrom
cake/convert-routes
Oct 20, 2026
Merged

sethforprivacy merged 1 commit into
mainfrom
cake/convert-routes

Conversation

@sethforprivacy

Copy link
Copy Markdown

ZEC has no USDT market on Kraken (only ZEC/USD and ZEC/EUR), so every ZEC sale goes through USD. That path had three problems, all fixed here.

  1. It paged every pass. The route was found by requesting ZECUSDT and catching the failure. Each failed call printed two error lines every 30-90s, so prod's Autoforwarder: errors in the autoconvert log trigger opened at 19:35 UTC once ZEC was in AUTOCONVERT_ASSETS. Routes now come from a single AssetPairs listing, so a missing market is just a lookup miss. If there is no route at all, that is still raised as an error.
  2. The second leg could never fill. Buys used the sell formula, so the USD→USDT IOC buy was limited at mid × (1 − slippage), below the book, and would be cancelled with nothing filled. Every ZEC sale would have ended up sitting in USD. A buy is now limited above mid and sized at the ask, with a fee margin (KRAKEN_FEE_MARGIN_PERCENT, default 0.5).
  3. The second leg spent the whole USD balance, not just this sale's proceeds. It now spends only the increase since the first leg (polled for up to 30s). If no proceeds show up, it raises an error saying what to convert by hand.

In a dry run, the second leg is now validated using the first leg's expected proceeds. Before this, a dry run skipped the second leg entirely, which is why the staging run of T13 never caught problem 2.

Checked against live Kraken data (private calls stubbed, nothing sent):

  • 1366 pairs loaded; XBT, LTC and XMR route directly, ZEC routes via USD.
  • The ZEC sale would sell 1.74715603 ZEC at a limit of 1191.39 (bid 1197.23).
  • It would then buy 2053.05 USDT at a limit of 1.00379 (ask 0.99880).

Tests:

  • The Kraken fake is now a small market. A live IOC order fills only when its limit crosses the book, and unknown pairs are rejected the way Kraken rejects them.
  • A ZEC sale is added to DryRunNeverMovesFunds.
  • IndirectSale checks two things: the proceeds reach USDT while USD the account already held is untouched, and routing around the missing market logs nothing Zabbix counts as an error.
  • Mutation-checked: each of these makes a test fail:
    • a buy limited below mid;
    • spending the whole USD balance;
    • the old probe-and-catch routing;
    • a dry run that places the second leg.

Follow-up in cake-tech/ansible: re-pin autoforward_version to this PR's merge commit.

🤖 Generated with Claude Code

ZEC has no USDT market on Kraken (only ZEC/USD and ZEC/EUR), so every
ZEC sale takes the intermediary route. Three things were wrong with it.

The route was found by asking for ZECUSDT and catching the failure.
kraken_request prints every failed call, so each pass (every 30-90s)
logged "request to kraken ... failed" and "prepare_and_make_trade
error", and the Zabbix item af.convert.errors (any error/failed line
in 30m) paged on prod as soon as ZEC was in AUTOCONVERT_ASSETS. Routes
now come from one /0/public/AssetPairs listing, indexed by (base,
quote) altname and loaded with the asset names, so a missing market
is a lookup miss. No route at all is still an error, raised once.

The second leg could never fill. The limit was mid * (1 - slippage)
for buys as well as sells, and an IOC buy limited below the book is
cancelled unfilled, which would leave every ZEC sale sitting in USD.
Buys are now limited above mid and sized at the ask with a fee margin
(KRAKEN_FEE_MARGIN_PERCENT, 0.5), since Kraken's volume is the bought
asset and a buy's fee is charged in the currency spent. A leg goes in
whichever direction the market exists (sell on A/B, buy on B/A).

The second leg spent the intermediary's whole balance, not this
sale's proceeds: any USD the account held for another reason would
have been converted too. It now spends the increase over the balance
before the first leg, polled for up to 30s after the fill; proceeds
that never show up are raised as an error naming what to convert by
hand. In a dry run the second leg is validated with the first leg's
expected proceeds, so both orders are now checked by Kraken (before,
the second was skipped).

Checked locally against Kraken's live pairs and books with private
calls stubbed: 1366 pairs, routes XBT/LTC/XMR direct and ZEC via USD;
selling 1.74715603 ZEC at limit 1191.39 (bid 1197.23), then buying
2053.05 USDT at limit 1.00379 (ask 0.99880).

Tests: the Kraken fake is now a small market that fills a live IOC
order only when its limit crosses the book and rejects unknown pairs
the way Kraken does. A ZEC sale is a DryRunNeverMovesFunds scenario,
and IndirectSale checks that the proceeds reach USDT while USD the
account already held is untouched, and that routing around the missing
market logs nothing Zabbix counts as an error. Mutation-checked: a
buy limited below mid, spending the whole intermediary balance,
probing the direct pair and catching the failure, and a dry run that
places the second leg each fail a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sethforprivacy
sethforprivacy merged commit 100d709 into main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant