Account Policies¶
The accounts and constraints tutorial compares accepted orders, structured rejections, and resulting portfolio state under each setting.
Account policy determines what the broker is allowed to do with cash, leverage, and short sale proceeds. Use this page when you need to decide whether your strategy should behave like a long-only cash account, a short-enabled crypto-style account, or a Reg T margin account.
The configuration is intentionally simple: instead of switching between account "types", you set the policy flags directly and let the broker enforce the resulting buying-power rules.
Run the linked accounts tutorial for complete order and portfolio-state comparisons under each policy.
Quick Example¶
from ml4t.backtest import BacktestConfig
config = BacktestConfig(
initial_cash=100_000,
allow_short_selling=True,
allow_leverage=True,
initial_margin=0.5,
long_maintenance_margin=0.25,
short_maintenance_margin=0.30,
)
Use this pattern when you want realistic shorting and leverage constraints instead of long-only cash-account behavior.
When to Use Which Policy¶
- use a cash account for long-only equity strategies with no borrowing
- use a crypto-style account when you want shorting but no leverage
- use a margin account when leverage, short maintenance, and buying-power checks matter
Account Types¶
ml4t-backtest uses a unified configuration model with two main flags:
| Flag | Description |
|---|---|
allow_short_selling |
Whether short positions are allowed |
allow_leverage |
Whether margin leverage is allowed |
These flags map to traditional account types:
| Account Type | allow_short_selling |
allow_leverage |
|---|---|---|
| Cash | False |
False |
| Crypto | True |
False |
| Margin | True |
True |
Cash Account (Default)¶
Use the default cash-account policy for long-only strategies where proceeds from sales must settle back into cash before they can be reused:
from ml4t.backtest import BacktestConfig, Engine
config = BacktestConfig(
initial_cash=100_000,
allow_short_selling=False, # Default
allow_leverage=False, # Default
)
# Or equivalently, just use defaults:
config = BacktestConfig(initial_cash=100_000)
Crypto Account¶
Use this combination when shorting is allowed but leverage is not:
Margin Account¶
Use a margin account when you need borrowing capacity, leverage, and maintenance constraints:
config = BacktestConfig(
initial_cash=100_000,
allow_short_selling=True,
allow_leverage=True,
initial_margin=0.5, # 50% initial margin (2x leverage)
long_maintenance_margin=0.25, # 25% maintenance for longs
short_maintenance_margin=0.30, # 30% maintenance for shorts
)
Common margin configurations:
| Use Case | initial_margin |
Max Leverage |
|---|---|---|
| Standard margin | 0.50 | 2x |
| Day trading | 0.25 | 4x |
| Futures-style | 0.10 | 10x |
Using Engine Directly¶
Pass the account policy to Engine through BacktestConfig. This complete example
submits a short sale on the first bar and checks its next-bar fill:
from datetime import datetime
import polars as pl
from ml4t.backtest import BacktestConfig, DataFeed, Engine, OrderSide, Strategy
class SellOnce(Strategy):
def __init__(self):
self.submitted = False
def on_data(self, timestamp, data, context, broker):
if not self.submitted:
broker.submit_order("AAPL", 10, OrderSide.SELL)
self.submitted = True
prices = pl.DataFrame({
"timestamp": [datetime(2024, 1, 2), datetime(2024, 1, 3)],
"asset": ["AAPL", "AAPL"],
"open": [100.0, 100.0],
"high": [100.0, 100.0],
"low": [100.0, 100.0],
"close": [100.0, 100.0],
"volume": [10_000.0, 10_000.0],
})
config = BacktestConfig(
initial_cash=100_000,
allow_short_selling=True,
allow_leverage=True,
initial_margin=0.5,
)
engine = Engine(feed=DataFeed(prices_df=prices), strategy=SellOnce(), config=config)
result = engine.run()
assert [(fill.side.value, fill.quantity) for fill in result.fills] == [("sell", 10.0)]
print("short sale filled: 10 AAPL")
Using Broker.from_config()¶
For advanced workflows, create the broker from a resolved config:
from ml4t.backtest import Broker, BacktestConfig
config = BacktestConfig(
initial_cash=100_000,
allow_short_selling=True,
allow_leverage=True,
)
broker = Broker.from_config(config)
Transaction Costs¶
Account policy often interacts with trading costs, especially when leverage or high turnover magnifies drag:
from ml4t.backtest import BacktestConfig
from ml4t.backtest.config import CommissionType, SlippageType
config = BacktestConfig(
initial_cash=100_000,
commission_type=CommissionType.PERCENTAGE,
commission_rate=0.001, # 0.1% per trade
slippage_type=SlippageType.PERCENTAGE,
slippage_rate=0.0005, # 0.05% slippage
)
Presets¶
Presets are useful when you want framework-style account and execution behavior without configuring each knob by hand:
from ml4t.backtest import BacktestConfig
# Sensible defaults for general use
config = BacktestConfig.from_preset("default")
# Fast iteration (no costs, simplified execution)
config = BacktestConfig.from_preset("fast")
# Backtrader-compatible settings
config = BacktestConfig.from_preset("backtrader")
# VectorBT-compatible settings
config = BacktestConfig.from_preset("vectorbt")
# Zipline-compatible settings
config = BacktestConfig.from_preset("zipline")
# QuantConnect LEAN-compatible settings
config = BacktestConfig.from_preset("lean")
# Conservative production settings
config = BacktestConfig.from_preset("realistic")
Each preset configures the measured behavior used by its comparison protocol. The native defaults, explicit overrides, and adapter-emulated behavior are listed in the validation methodology. Strict aliases are also available for the retained comparison commands.
Insufficient Funds¶
Use Gatekeeper directly when you need to validate an order before execution.
It requires an account state, a commission model, and the expected fill price:
from ml4t.backtest import Order, OrderSide
from ml4t.backtest.accounting import AccountState, Gatekeeper, UnifiedAccountPolicy
from ml4t.backtest.models import NoCommission
account = AccountState(initial_cash=100_000, policy=UnifiedAccountPolicy())
gatekeeper = Gatekeeper(account, NoCommission())
order = Order(asset="AAPL", side=OrderSide.BUY, quantity=1_500)
is_valid, reason = gatekeeper.validate_order(order, price=100.0)
assert not is_valid
print(reason)
Migration from the beta account_type keyword¶
The beta-only Broker(account_type=...) keyword was removed before 0.1 and is not part of the
stable compatibility boundary. A single string selected several independent shorting and leverage
policies, so preserving it could silently change buying-power and liquidation behavior. Migrate to
the explicit policy flags:
broker = Broker(allow_short_selling=True, allow_leverage=True)
# Or with config
config = BacktestConfig(allow_short_selling=True, allow_leverage=True)
broker = Broker.from_config(config)
The reviewed 0.1 compatibility snapshot is tests/compatibility/snapshots/v0.1.json. Run
uv run python validation/generate_compatibility_snapshot.py to check it. An intentional API or
schema change requires --write and review of the resulting snapshot diff.
In the book¶
Chapter 17, Section 17.4, Conformal position sizing turns prediction uncertainty into position sizes. The account tutorial here shows how buying power and share precision affect those sizes.
Next Steps¶
- Book Guide -- chapter and case-study map for account and portfolio workflows
- Configuration -- full account, margin, and cash-management parameter reference
- Risk Management -- position rules and portfolio limits that interact with buying power
- Rebalancing -- portfolio-weight execution under explicit account constraints
- Results & Analysis -- inspect turnover, fills, and portfolio-state effects of account policy