Audits › Controls › Maximum order size
Size and exposure · control 1 of 21
Maximum order size: does your trading bot have it?
Is there a hard cap per order, and in which unit is it validated: quote currency, base asset, or percent of balance?
Why it matters
Without a cap, or with the cap checked in the wrong unit, an order meant to be 100 USD can go out as 1,000 USD. That exact bug is documented in CloddsBot issue #126.
How to check it in the code
- Search the code for max_order, max_notional, max_position, order_size, notional.
- Find the line that compares the order against the cap and confirm the unit on both sides matches (price × size versus size).
- Check that market orders use a live price for the comparison, not a caller-supplied one.
This is what the audit does for every bot in the list, on public code, without installing or running anything. Verdict values: present, partial, absent, not applicable, not verified. See the method.
Results across audited bots
Across 10 audited bots: 4 present, 5 partial, 1 absent, 0 not applicable, 0 not verified.
| Bot | Verdict | Evidence | Note |
|---|---|---|---|
| HKUDS/Vibe-Trading | Present | raw.githubusercontent.com/HKUDS/Vibe-Trading/main/agent/src/live/enforcement.py check_mandate() step 4 'Single-order notional' compares against caps.max_order_notional_usd; agent/src/live/order_guard.py _normalize_intent_notional() enforces MAX(explicit notional, quantity*live_price) before the cap check | Unit is USD notional; a quantity-only order is priced via a live quote and fail-closed DENIED if no quote is obtainable. |
| hummingbot/hummingbot | Partial | hummingbot/connector/budget_checker.py (class BudgetChecker, methods adjust_candidate / adjust_candidate_and_lock_available_collateral) | Order size comes from the user's own static strategy config (order_amount / total_amount_quote); BudgetChecker only shrinks it if available balance is insufficient, it never enforces an independent absolute ceiling regardless of config or calculation bugs. |
| Drakkar-Software/OctoBot | Absent | packages/tentacles/Trading/Mode/index_trading_mode/index_trading.py: only 'min_order_size_margin' (Decimal('2'), a MINIMUM order size margin) and 'rebalance_trigger_min_ratio' (0.05, a MINIMUM % move to trigger) exist. No maximum per-order size cap (currency or % of balance) was found in this mode nor in the AI distribution layer (ai_index_distribution.py). | Found a minimum-size guard, never a maximum. Could not check the other 12 trading-mode tentacles (grid, dca, staggered_orders, daily, arbitrage, dip_analyser, dsl, script) individually due to time budget; each implements its own sizing independently. |
| freqtrade/freqtrade | Present | docs/configuration.md (Parameters table): stake_amount, max_open_trades, tradable_balance_ratio -- https://raw.githubusercontent.com/freqtrade/freqtrade/3774521e7028fa666770e7c7a8da323af5c4cefa/docs/configuration.md | Per-trade size is capped by stake_amount (in quote currency) and total exposure by max_open_trades x tradable_balance_ratio; both are mandatory config fields. |
| jesse-ai/jesse | Partial | jesse/models/FuturesExchange.py (on_order_submission: raises InsufficientMargin if effective_order_size > available_margin, but the function starts with 'if jh.is_livetrading(): return', skipping the check entirely in real live trading) + jesse/services/broker.py (_validate_qty only checks qty != 0, no maximum) | A margin-based order-size cap exists but only runs in backtest/paper simulation; in real live trading no cap is enforced anywhere in this public repo. |
| chrisleekr/binance-trading-bot | Present | packages/strategy/trailing-trade/src/feasibility.ts (checkTTOrderFeasibility) | Every grid level's maxPurchaseAmount or the single fixed entry amount (quote-currency budgets set by the operator) is validated against Binance's live minNotional/minQty at save time via the same finalise()/parseFilters() sizing the live tick uses, so a config that passes cannot silently drift; percentOfAccount orders are risk-sized against equity/stop-loss and are explicitly left unchecked by this pre-check per the file's own comment. |
| alsk1992/CloddsBot | Partial | src/trading/risk.ts enforceMaxOrderSize() checks notional (USD) vs maxOrderSize, wired in src/risk/engine.ts validateTrade() step 3; BUT GitHub issue #126 (OPEN, filed at this exact commit c930628) proves MARKET orders defeat the cap because resolveOrderPrice() trusts a caller-supplied `price` (src/execution/futures.ts#L1408) while MARKET submission never forwards it (src/execution/futures.ts#L261; same pattern src/trading/futures/index.ts#L5376). https://github.com/alsk1992/CloddsBot/issues/126 | The cap exists and is enforced for the guarded limit-order path, but is provably bypassable for MARKET orders at the audited commit. |
| Lumiwealth/lumibot | Partial | docs/ENV_VARS.md: 'POLYMARKET_MAX_MARKET_ORDER_NOTIONAL: hard cap for market BUY dollar amount; defaults to 5' (raw.githubusercontent.com/Lumiwealth/lumibot/dev/docs/ENV_VARS.md, read 2026-09-17) | |
| YizhiSong/FriesTrader | Present | risk_rules.json position_sizing.max_position_pct_of_account=0.20 (% of account total_value, not a raw $ figure); enforced in scripts/position_sizing.py -- new-entry branch checks conviction_pct > max_position_pct, held/top-up branch caps via ceiling_room = max_position_pct*total_value - current_position_value. https://raw.githubusercontent.com/YizhiSong/FriesTrader/main/scripts/position_sizing.py | Unit is correctly % of account equity, not raw currency or base units, so the CloddsBot #126-style unit mismatch is avoided. |
| c9s/bbgo | Partial | pkg/risk/riskcontrol/position.go (PositionRiskControl.hardLimit, sliceQuantity) | A cap exists (max base-currency position + max order slice) but it is an opt-in library component a strategy must instantiate explicitly, not a default. |
If your bot does not have it
The watchdog enforces daily loss, drawdown, position size and heartbeat limits from outside the bot, with a read-only key, whatever the bot's own code does.