I found the fee assumption in an old bot's config sitting in a constant near the top of the file. One number, taker rate, applied to every fill. It had been that way for months and nobody touched it because it worked. The bot made trades, they cleared, the PnL looked plausible. What bothered me was that the number was wrong in both directions at once. Some fills were cheaper than the constant, because the account had climbed a volume tier. Some were more expensive, because the bot was crossing the spread on orders that could have rested. The constant was an average of two mistakes, and averages of mistakes quietly eat a mid-frequency strategy alive.
Fees feel like the boring part of execution, and for a slow strategy they mostly are. But the moment your bot places more than a handful of orders a day, the fee model stops being a rounding error and becomes a variable you can optimize. Most of the work is reading, not building. Your exchange already publishes everything you need.
Pull the live schedule, do not guess it
Almost every major venue exposes your current fee tier through the API. On Binance and similar exchanges there is an account endpoint that returns your maker and taker rates as they actually apply to you right now, after your 30-day volume, after any token-balance discount, after any promotional carve-out. That is the number your bot should be using, and it should refresh it on a schedule rather than trusting a value someone typed in during setup.
The reason to pull it rather than hardcode it is that your tier is not static. Volume rolls on a trailing window, so a busy stretch bumps you up a level and a quiet one drops you back. Discount tokens like BNB get spent, and if the balance runs dry the discount silently stops applying. A bot doing break-even math against a stale rate keeps making decisions that were correct last month and are wrong today. I refresh the schedule at least daily, and again after any burst of activity that might have crossed a threshold.
A minimal fee-awareness layer looks like this:
- On startup and on a timer, call the account fee endpoint and cache maker rate, taker rate, and whether the token discount is currently active.
- Store the per-symbol rates, not one global pair, because some venues price certain markets differently.
- Track your own trailing volume locally as well, so you can see how close you are to the next tier without waiting for the exchange to tell you.
- Treat maker and taker as two separate costs in every expected-value calculation, never a single blended fee.
Make the maker-versus-taker choice per order
Here is where the constant really hurts you. A taker order pays the spread and the taker fee and fills immediately. A maker order rests on the book, pays the lower maker fee, and on some venues actually pays you a rebate, but it carries the risk that it never fills or fills late while the price walks away. Whether you should post or cross is not a setting. It is a decision that depends on the specific order in front of you.
The way I frame it is a small expected-value comparison. Crossing costs you the taker fee plus roughly half the spread in immediate slippage, and you get certainty. Posting saves you the fee difference, possibly earns a rebate, but exposes you to two costs that are easy to forget. One is adverse selection: the orders that fill against your resting bid are disproportionately the ones you would rather not have taken, because the market is moving through your price. The other is the opportunity cost of the fills that never happen, where your signal was right and you sat on the sidelines because your limit was a tick too greedy.
So the per-order logic runs something like this. If the order is urgent, meaning the signal decays fast or you are managing risk, cross and pay the taker fee, because a rebate you might not collect is worthless against a fill you actually need. If the order is patient and the spread is wide enough that the maker savings plus any rebate clearly beat your estimated fill-failure cost, post it and give it a defined time to work before you give up and cross. The threshold between those two cases is exactly the maker-taker fee gap, which is why you needed the live schedule. On a rebate venue that gap can exceed the raw spread on liquid pairs, which flips a lot of orders a naive bot would have crossed.
The break-even math for chasing the next tier
The other thing a live fee model lets you do is answer a question every active trader eventually asks, which is whether it is worth pushing volume to reach the next tier. This one is easy to get emotionally wrong, because reaching a tier feels like an achievement, and the math is usually less flattering than the feeling.
The break-even is a straight comparison. The benefit of the next tier is the fee reduction per dollar traded, applied to the volume you were going to trade anyway. The cost of chasing it is the extra fees, spread, and slippage on the incremental volume you would not otherwise have done, plus the risk that those forced trades lose money on their own merits. Chasing a tier only pays if your natural volume already sits close to the threshold, so the discount lands mostly on trades you were making regardless. If you have to roughly double your activity to get there, the wash trades you add almost always cost more than the discount returns, and you have quietly turned a fee optimization into a losing strategy that happens to unlock a cheaper fee. The honest version of this treats added volume as a real cost, not a free lever.
Measuring whether any of this helped
None of this is worth doing if you cannot see the effect, and fee optimization shows up as a small persistent drift rather than a dramatic line. The way to measure it is to log, for every fill, the fee you actually paid, the fee you would have paid under the naive constant, and whether the order posted or crossed. Sum those over a few thousand fills and you get a clean number for how much the fee logic saved, separate from the strategy's own PnL. I also track a maker fill-failure rate, because if too many resting orders time out and re-cross, your clever posting logic is just adding latency and paying the taker fee anyway.
For a mid-frequency strategy the honest expectation is modest. Fee optimization will not rescue a bad signal, and it will not typically move returns by a large margin. What it does is stop a working strategy from leaking, and on something that trades enough for fees to matter, that leak compounds. When I am reviewing execution quality inside Blockcircle, the fee-attribution column is one of the first things I look at, because it tells me whether the bot is thinking about cost at the order level or just crossing the spread and hoping.
If you only change one thing, make it the constant. Replace the hardcoded fee with a value your bot reads from the exchange and refreshes on a timer, split into maker and taker, and let every order decision see the real number. Everything else builds on that, and the day your tier changes, your bot will already know.