burneh
Trading

It only trades when the spread beats the costs.

A price gap across two venues isn't profit until you subtract the fees, the slippage, and the cost of settling. We build the bot to do that math first and sit on its hands otherwise. Real arbitrage is thin, brief, and contested, so most of the time the right move is not to trade, and this one knows it.

The cost check, in real code.

No black box. A price gap isn't profit until two taker fees, slippage on both legs, and the cost of settling come out first. That sum is the hurdle, and the bot only acts when the spread clears the whole thing. Most quotes don't, so most of the time it waits.

arb.pypython
1# arb.py — act on a spread only when it beats every cost
2from burneh.arb import Arb, both_legs
3
4class CostedArb(Arb):
5 fee_a = 0.030 # taker fee, venue A
6 fee_b = 0.030 # taker fee, venue B
7 slippage = 0.020 # expected, both legs
8 transfer = 0.010 # move / settle / gas
9
10 @property
11 def hurdle(self):
12 # the spread has to clear all of this before a cent is real
13 return self.fee_a + self.fee_b + self.slippage + self.transfer # 0.090
14
15 def on_quote(self, a, b):
16 spread = abs(a.bid - b.ask)
17 net = spread - self.hurdle
18 if net <= 0:
19 return self.wait() # most of the time, this
20
21 if self.quote_is_stale(a, b):
22 return self.skip("quote aged out") # never send a stale leg
23
24 both_legs(a, b, edge=net) # fire both sides, or neither
spread · Venue A ↔ Venue B
waiting
venue A
100.045
venue B
99.955
spread 0.090edge +0.000

the bar has to pass the amber line (costs) before a trade makes sense

costs
0.090
fires
0

It watches the spread against the costs.

The bar is the live spread across two venues; the amber line is what it costs to capture it. Watch how rarely the bar clears the line. Every time it doesn't, the right move is to do nothing, and the fires counter barely moves.

A bot that trades often is ignoring costs.

Same spreads, two rules. Count every cost and it mostly waits, taking only the fills that truly clear and losing some races to faster shops. Ignore costs and it fires on every gap, which looks busy and loses money on nearly all of them. Toggle the rule and watch the net.

decisionsspread 0.000
watching the spread…
watched
0
cleared
0
won
0
time acting0%
net, after real costs
+$0
disciplined · sits on its hands until the spread beats every cost, and loses some races

When it does act, it acts carefully.

The failure mode isn't a bad trade, it's a half-done one. So execution is built around never being left holding one leg when the other doesn't fill.

Both legs, or neither

The two sides are placed together with fills confirmed. A one-legged arb is just an open position.

Inventory balanced

Holdings are kept in range across venues, so you're not stranded with all of one asset on one side.

Latency budget

If the quote is too old by the time it would fill, the trade is abandoned rather than sent stale.

Kill switch

One command halts new attempts and squares up, for when a venue lags or a feed goes bad.

Mostly, it waits. That's the job, not a fault in it.

Real arbitrage windows are thin, brief, and crowded with faster players colocated next to the exchange. Most gaps are smaller than the cost of capturing them; the ones that clear close in moments, and some of those you'll lose to lower-latency shops. We don't sell you a money-printer that trades all day. We build the discipline to sit still until the numbers actually work, and to accept the races it can't win.

It only trades when the spread beats the costs.

Both taker fees, slippage, and transfer subtracted first, disciplined restraint the rest of the time, and both legs placed together so you're never left holding one.