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.
1# arb.py — act on a spread only when it beats every cost2from burneh.arb import Arb, both_legs34class CostedArb(Arb):5 fee_a = 0.030 # taker fee, venue A6 fee_b = 0.030 # taker fee, venue B7 slippage = 0.020 # expected, both legs8 transfer = 0.010 # move / settle / gas910 @property11 def hurdle(self):12 # the spread has to clear all of this before a cent is real13 return self.fee_a + self.fee_b + self.slippage + self.transfer # 0.0901415 def on_quote(self, a, b):16 spread = abs(a.bid - b.ask)17 net = spread - self.hurdle18 if net <= 0:19 return self.wait() # most of the time, this2021 if self.quote_is_stale(a, b):22 return self.skip("quote aged out") # never send a stale leg2324 both_legs(a, b, edge=net) # fire both sides, or neitherthe bar has to pass the amber line (costs) before a trade makes sense
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.
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.
The two sides are placed together with fills confirmed. A one-legged arb is just an open position.
Holdings are kept in range across venues, so you're not stranded with all of one asset on one side.
If the quote is too old by the time it would fill, the trade is abandoned rather than sent stale.
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.