Skip to content

Binance Futures Testnet To Mainnet Promotion

binance-futures-testnet-to-mainnet-promotionsource

Use when a Binance Futures strategy moves from testnet to mainnet, where the same code pointed at a different host reaches real leveraged capital; binds each base URL to its environment, separates credentials and fails closed on a mismatch.

Version
2.0.0
Reading
6 min
Hands off to
8
Handed off from
4
License
Apache-2.0
CoversBinance USDⓈ-M Futures APIBinance COIN-M Futures API

Binance Futures Testnet to Mainnet Promotion

When to Use

Invoke this when a strategy that has been running on the Binance Futures testnet is about to route orders against a mainnet base URL. The promotion step itself is the hazard: the same code, pointed at a different host with different credentials, moves from fake balances to real leveraged capital with liquidation risk. This skill supplies the gate that must pass before an order router is handed a mainnet ExchangeConfig, plus the account-level reconciliation steps that testnet cannot exercise.

Use it for both USDⓈ-M (fapi) and COIN-M (dapi) futures.

When NOT to Use

  • Spot or Margin promotion: Binance spot uses different hosts (api.binance.com) and a different testnet; the host allowlist and the leverage/position-mode checks here do not apply.
  • Brokers with a single endpoint for both environments: If environments are distinguished only by credentials, the host-binding logic is inapplicable — see alpaca-paper-live-key-separation for the credential-prefix variant of this pattern.
  • Backtesting or offline simulation: No live endpoint is involved, so environment segregation is irrelevant. Use demo-account-realism-gap-assessment to judge whether the testnet record is meaningful at all.
  • As a substitute for a general go-live decision: This gate checks environment wiring and configured risk limits. It does not judge whether the strategy's performance justifies live capital — that is paper-to-live-promotion-checklist.
  • As a runtime risk control: This runs once at promotion. Continuous drawdown and exposure enforcement belongs to kill-switch-and-drawdown-circuit-breakers.

Prerequisites

  • Python 3.10+ (standard library only; this module performs no network I/O).
  • Separate Binance Futures testnet and mainnet API keys, held in distinctly named environment variables. Testnet keys are issued from a separate registration flow and are not valid on mainnet — if the same value appears in both configs, one leg is wrong.
  • Mainnet key with Futures trading permission enabled and, where the account allows it, an IP allowlist. Binance's API-key permission and expiry rules have changed more than once; confirm the current rules in Binance's API management docs rather than assuming.
  • A testnet track record produced against a testnet host (verify this — it is the first thing the gate checks).

Workflow

  1. Build both configurations: Construct ExchangeConfig for testnet and mainnet. MainnetPromotionManager.__init__ rejects, as ValueError, any config whose environment enum is wrong, any pair that shares an api_key or api_secret, and any nonsensical risk ceiling (e.g. max_capital_risk_pct=2, the percent-vs-fraction slip).

  2. Bind each URL to its environment: verify_api_connectivity requires HTTPS and an exact hostname match against the allowlist for the declared environment. Both legs are checked — a TESTNET-labelled config pointing at fapi.binance.com means the "paper" track record was produced with real orders, so it invalidates the promotion rather than merely warning. Exact matching is deliberate: a startswith/endswith comparison accepts https://fapi.binance.com.attacker.example.

  3. Validate risk parameters, failing closed: validate_risk_parameters rejects a missing key rather than defaulting it, rejects NaN/Inf, rejects non-integer leverage (Binance accepts integer leverage only), and requires hard_stop_loss_enabled to be the boolean True — not any truthy value, because a config loader yielding the string "false" is truthy.

  4. Require explicit authorization: allow_live_promotion defaults to False. Wire it from an operator-controlled deployment flag at the call site (allow_live_promotion=os.environ.get("BINANCE_ALLOW_MAINNET_PROMOTION") == "true"). The module deliberately does not read the environment itself, so the decision stays explicit and the gate stays deterministic under test.

  5. Reconcile mainnet account state before the first order — this is the part testnet cannot cover, because these are per-account, per-environment settings that do not travel with your code:

    • Position mode: GET /fapi/v1/positionSide/dual. If it disagrees with testnet, change it before opening anything — POST /fapi/v1/positionSide/dual is rejected with -4067 when open orders exist and -4068 when a position exists.
    • Multi-assets margin mode (/fapi/v1/multiAssetsMargin) and per-symbol margin type (/fapi/v1/marginType, which returns -4046 when already set to the requested value).
    • Leverage: set it with POST /fapi/v1/leverage and read the response back. Check the permitted brackets via GET /fapi/v1/leverageBracket; the leverage your testnet config assumed may exceed what this account and notional tier allow, and Binance has applied lower caps to newly opened futures accounts.
    • Symbol filters: re-read GET /fapi/v1/exchangeInfo on mainnet. LOT_SIZE (stepSize, minQty), MIN_NOTIONAL, PRICE_FILTER (tickSize) and symbol availability are not guaranteed to match testnet, so quantities that were accepted on testnet can be rejected live.
  6. Promote: Call promote_to_mainnet(strategy_params). Every call re-runs the full pre-flight sequence; a prior success never short-circuits a later parameter set.

  7. Pilot, then scale: Run minimum-notional size first and compare realized slippage, funding, and fees against the testnet assumptions before increasing allocation. See incremental-capital-deployment-for-new-strategies.

Full step-by-step procedure with endpoint-level detail: see references/workflows.md. Cited Binance API surface for this skill: see references/standards.md. Printable sign-off checklist: see assets/checklist.md.

Common Pitfalls

  • Treating any https:// URL as safe: HTTPS says nothing about which venue you reached. Bind the host to the declared environment and compare hostnames exactly.
  • Reusing one credential pair across both configs: If the shared value is the mainnet key, the "testnet" phase was live trading. If it is the testnet key, mainnet auth simply fails — the harmless direction, which is why the dangerous direction goes unnoticed.
  • Defaulting a missing risk limit: params.get("leverage", 0) turns a typo'd key into a pass. On a promotion gate, absent means reject.
  • Comparing against NaN: float("nan") > max_leverage is False, so a NaN risk parameter passes a naive bounds check. Test finiteness explicitly.
  • Truthiness checks on safety flags: the string "false" from an env var or YAML loader is truthy and will silently disable a stop-loss requirement.
  • Treating "already promoted" as idempotent: returning the mainnet config on a repeat call without re-validating lets a later, over-leveraged parameter set inherit an earlier approval.
  • Assuming account settings carry over: position mode, multi-assets mode, margin type and leverage are per-account and per-environment. Changing position mode after you already hold a position or open order fails (-4067/-4068), so reconcile before the first order.
  • Assuming testnet symbol filters match mainnet: differing stepSize/minNotional produce live -2010 rejections for sizes that worked on testnet.
  • Logging config objects: a plain dataclass repr prints api_secret verbatim into logs and tracebacks. ExchangeConfig here redacts it; do the same for any config you add.
  • Trusting the testnet fill model: Binance testnet order books are thin and synthetic. Slippage, partial fills, and funding observed there are not evidence about mainnet.
  • Retrying an ambiguous order on the first live orders: a timed-out POST /fapi/v1/order may already have been accepted. Send a client-supplied newClientOrderId (^[\.A-Z\:/a-z0-9_-]{1,36}$) and reconcile before resubmitting — see order-placement-idempotency.

Verification

  • Run the unit suite: python -m unittest discover -s skills/binance-futures-testnet-to-mainnet-promotion/scripts.
  • Point a TESTNET-labelled config at https://fapi.binance.com and confirm run_pre_flight_checks returns False.
  • Set base_url to https://fapi.binance.com.attacker.example and confirm rejection.
  • Construct a manager with the same api_key in both configs and confirm ValueError.
  • Omit leverage from strategy_params and confirm rejection; repeat with capital_risk_pct=float("nan") and with hard_stop_loss_enabled="false".
  • Leave allow_live_promotion at its default and confirm promote_to_mainnet raises PromotionError.
  • Promote successfully, then call again with leverage=50 and confirm PromotionError.
  • Confirm repr(config) and str(config) contain no secret material.
  • Against the live account: confirm POST /fapi/v1/leverage echoes back the leverage you requested, and that GET /fapi/v1/exchangeInfo filters for every traded symbol match the quantities your sizing logic emits.

Verify it, from the repository root

python -m unittest discover -s skills/binance-futures-testnet-to-mainnet-promotion/scripts

Hands off to 8

Skills this document names, usually in When NOT to Use, as the owner of a case it excludes.

Handed off from 4

Skills that name this one as the place a case belongs. The reverse edges of the graph.