Day 45 — Virtual Margin: Leverage and Buying Power Checks
By the end of today you will have built a margin system that correctly refuses overleveraged orders, survives a thousand concurrent reservation attempts without a single buying-power violation, and reconciles its numbers against a live Alpaca paper account within 0.1%. That is a higher standard than a lot of production code running right now at small prop desks. Let us get into it.
What We Are Building
Before touching any code, it helps to see where this module sits inside the broader engine.
The system has three layers. The data layer owns state — the
MarginAccount, eachPositionit holds, and thePDTTrackerthat enforces the Pattern Day Trader rule. The logic layer computes —BuyingPowerCalculatortells you how much you can spend, andMarginMonitorwatches whether you are still solvent while the market moves. The execution layer acts —MockBrokerroutes orders,AlpacaReconcilermakes sure your local numbers match reality.Everything flows through
MarginAccount. It is the single source of truth. Nobody writes to it without going through its lock.
The “Cash Balance Check” Trap
A junior engineer wires up their MockBroker in 20 minutes. The buying power logic looks like this:
def can_execute(self, order: Order) -> bool:
cost = order.quantity * order.price
return self.cash_balance >= cost
This runs fine in backtests. It even passes the first 50 paper trades. Then on day two of live simulation, a volatility spike hits. Three coroutines submit market orders simultaneously. Each reads self.cash_balance = $48,200. Each calculates it needs $16,000. Each returns True. Three orders execute. Real committed cost: $48,000. Simulated account equity: -$800.
You just simulated a margin call caused by a race condition. This is the exact class of bug that has liquidated real accounts at prop desks running naive concurrent execution engines.
The failure has three compounding causes:
No atomicity — read-check-commit is not a single operation
No leverage model — cash-on-hand is not buying power
No margin tier — initial margin is not the same as maintenance margin, which is not the same as liquidation threshold
The Failure Mode: Three Concurrent Kills
Kill 1 — The Race Condition
Python’s GIL protects individual bytecode instructions, not compound operations like read → compare → write. In an async event loop running order handlers as coroutines, await yields control mid-function. The moment your buying power check hits an await before the debit, you have a TOCTOU (Time of Check to Time of Use) vulnerability. Here is what that looks like in practice:
Coroutine A: reads bp = 48200, checks 48200 >= 16000 → True, awaits I/O...
Coroutine B: reads bp = 48200, checks 48200 >= 16000 → True, awaits I/O...
Coroutine C: reads bp = 48200, checks 48200 >= 16000 → True, awaits I/O...
All three resume → all debit → bp = -$1800
Think of it like three people checking the same bank account balance on their phones at the same moment and all deciding to withdraw $16,000 before any of the withdrawals have actually posted.
Kill 2 — Float Drift in P&L
Over 10,000 intraday P&L updates using Python’s built-in float, you accumulate IEEE 754 rounding error. A real margin threshold at $25,000.00 might read $24,999.9999999847 in your engine. You never trigger the PDT rule. You take five day trades under $25k equity. That is a regulatory violation — simulated or not, this gap means your paper trading engine is lying to you about regulatory risk.
This is not theoretical. Binary floating-point cannot represent most decimal fractions exactly. 0.1 + 0.2 in Python gives 0.30000000000000004. Over tens of thousands of accumulated P&L updates, that error compounds.
Kill 3 — Flat Margin Model
Regulation T (Reg T) mandates two distinct margin levels:
Initial margin — 50% of position value on open. To hold $10,000 of stock, you must put up $5,000.
Maintenance margin — 25% of current position value. Fall below this and the broker issues a margin call.
A system that checks only “do I have enough cash?” never models the maintenance requirement. You open positions freely, the market moves against you, and your engine never emits a margin call because it does not track equity / total_position_value. The account is insolvent on paper and your system is blind to it.
The Architecture: Reserve → Confirm → Release
We implement the Reserve → Confirm/Release pattern borrowed from distributed systems. Think of buying power as a resource pool — the same mental model as a database connection pool. You reserve a slot before the operation, release it on failure, consume it permanently on success.
OrderRequest → MarginAccount.reserve(order)
|
[atomic under RLock]
|
BP Check (Day Trade / Overnight / Cash)
|
BuyingPower.reserved += cost
|
Submit to MockBroker / Alpaca
/ \
FILL REJECT / TIMEOUT
confirm(order) release(order)
reserved → used reserved → freed
Three classes carry the entire system.
MarginAccount owns the canonical state. It uses decimal.Decimal for all monetary values and holds a threading.RLock — reentrant, so the margin monitor can query state from the same thread that already holds the lock. It tracks cash, reserved, positions, and equity.
BuyingPowerCalculator is a stateless rules engine. It takes a MarginAccount snapshot and returns buying power for three modes: DAY_TRADE (4x equity under PDT), OVERNIGHT (2x equity under Reg T), and CASH (no leverage). This is a pure function — no state, fully testable in isolation.
MarginMonitor runs as a background daemon thread on a configurable heartbeat (default: 500ms). It computes margin_ratio = equity / total_exposure and emits state transitions: HEALTHY (>35%) → AT_RISK (25–35%) → MARGIN_CALL (<25%) → LIQUIDATING (<10%). It publishes transitions to a queue.Queue that the order router consumes.
Implementation Deep Dive
Atomic Reserve with RLock
This is the core of everything. Read it carefully.
def reserve(self, order_id: str, cost: Decimal) -> ReservationResult:
with self._lock: # blocks until acquired — no TOCTOU
available = self._compute_available_bp()
if cost > available:
return ReservationResult(success=False, reason="INSUFFICIENT_BP",
available=available, requested=cost)
self._reservations[order_id] = cost
self._reserved += cost
return ReservationResult(success=True, available=available - cost,
requested=cost)
The with self._lock block makes the entire check-and-reserve a single critical section. No coroutine, no thread, can interleave between the check and the write. The RLock is reentrant — if the monitor thread already holds it and calls reserve(), it will not deadlock with itself.
Decimal Arithmetic for Margin Precision
from decimal import Decimal, ROUND_HALF_UP
PENNY = Decimal("0.01")
def margin_requirement(position_value: Decimal, rate: Decimal) -> Decimal:
return (position_value * rate).quantize(PENNY, rounding=ROUND_HALF_UP)
Decimal("0.50") is exact. float(0.5) is 0.5 only by coincidence of binary representation. For margin thresholds — for example, Decimal("25000.00") — you need exact comparisons. Use Decimal everywhere money appears. The performance cost is roughly 10x compared to float, but that is completely irrelevant at the frequency of margin checks, which are triggered at order time, not on every market tick.
PDT Rule Enforcement
The Pattern Day Trader rule is a US regulation: if your account equity is below $25,000, you cannot execute more than three day trades (round-trip opens and closes) within any rolling five-business-day window.
@dataclass
class PDTTracker:
equity: Decimal
day_trades: deque[date] # rolling 5-business-day window
def is_pdt_restricted(self) -> bool:
recent = sum(1 for d in self.day_trades
if d >= _five_business_days_ago())
return self.equity < Decimal("25000") and recent >= 4
This check runs inside reserve() as a pre-flight gate. If the account is PDT-restricted, it gets downgraded to CASH mode regardless of the requested leverage level. The deque with maxlen=50 gives us a bounded rolling window with O(1) appends — no unbounded growth.
Maintenance Margin Monitoring
The MarginMonitor heartbeat computes:
equity = cash + sum(pos.market_value for pos in positions) - borrowed
total_long_exposure = sum(
pos.market_value for pos in positions if pos.side == "long"
)
margin_ratio = (
equity / total_long_exposure
if total_long_exposure > 0
else Decimal("1")
)
When margin_ratio crosses Decimal("0.25"), a MARGIN_CALL event is pushed to the queue. The order router consumes this queue, halts all new submissions, and begins the liquidation waterfall: smallest positions first, then the largest losers.
Production Readiness Metrics
These are the numbers you watch in production. If any of these drift beyond the alert threshold, something in the system is silently broken.
Building the Project
Github Link:
https://github.com/sysdr/quantpython/tree/main/day45/autoquant_day45_workspace
What You Need First
Before anything else, confirm your Python version and install the dependencies.
python --version
# Must print Python 3.11.x or higher
If you are on 3.10 or below, stop here and upgrade. We use match statements, Self type hints, and tomllib from the standard library — none of which exist in earlier versions.
pip install alpaca-py rich numpy pandas python-dotenv pytest
You also need a free Alpaca paper trading account. Go to alpaca.markets, sign up, and grab your paper API keys from the dashboard. Then create a .env file in the project root:
ALPACA_API_KEY=your_paper_key_here
ALPACA_SECRET_KEY=your_paper_secret_here
ALPACA_BASE_URL=https://paper-api.alpaca.markets
Paper trading is free and does not touch real money. You will never be asked to deposit anything.
Generating the Workspace
The entire project is generated by a single bootstrap script. This is intentional — it means you can blow away the workspace and regenerate it cleanly at any point.
python generate_workspace.py
You will see output like this as each file is written:
AutoQuant-Alpha · Day 45: Virtual Margin
Generating workspace at: /path/to/autoquant_day45_workspace
autoquant_day45_workspace/__init__.py
autoquant_day45_workspace/requirements.txt
autoquant_day45_workspace/src/margin/account.py
autoquant_day45_workspace/src/margin/monitor.py
autoquant_day45_workspace/src/broker/mock_broker.py
autoquant_day45_workspace/src/broker/reconciler.py
autoquant_day45_workspace/src/dashboard/cli.py
autoquant_day45_workspace/tests/test_margin.py
autoquant_day45_workspace/tests/stress_test.py
autoquant_day45_workspace/scripts/demo.py
autoquant_day45_workspace/scripts/verify.py
autoquant_day45_workspace/scripts/start.py
autoquant_day45_workspace/scripts/verify.sh
autoquant_day45_workspace/scripts/cleanup.sh
Workspace generated.
Project Layout
Once generated, the workspace follows a standard industry layout:
autoquant_day45_workspace/
|
+-- src/
| +-- margin/
| | +-- account.py # MarginAccount — state, locking, Decimal math
| | +-- monitor.py # MarginMonitor — background health polling
| +-- broker/
| | +-- mock_broker.py # MockBroker — order routing + fill simulation
| | +-- reconciler.py # AlpacaReconciler — live validation
| +-- dashboard/
| +-- cli.py # Rich TUI live margin dashboard
|
+-- tests/
| +-- test_margin.py # Unit tests: math, PDT, concurrency
| +-- stress_test.py # 1,000-thread concurrent reservation test
|
+-- scripts/
| +-- demo.py # Interactive dashboard with simulated trades
| +-- verify.py # Runs all tests + Alpaca reconciliation
| +-- start.py # Live monitoring loop vs Alpaca
| +-- verify.sh # Shell entry point
| +-- cleanup.sh # Remove caches and build artifacts
|
+-- data/ # Empty — populated at runtime
+-- requirements.txt
+-- .env.example
Now enter the workspace and install:
cd autoquant_day45_workspace
pip install -r requirements.txt
Running the Tests
Unit Tests
The unit test suite covers ten scenarios across five areas: Decimal precision, buying power computation, atomic reservation, concurrency safety, PDT enforcement, and margin health transitions.
python -m pytest tests/test_margin.py -v
Expected output:
tests/test_margin.py::TestDecimalPrecision::test_equity_calculation_no_float_drift PASSED
tests/test_margin.py::TestDecimalPrecision::test_rejects_float_input PASSED
tests/test_margin.py::TestBuyingPower::test_day_trade_bp_is_4x_equity PASSED
tests/test_margin.py::TestBuyingPower::test_overnight_bp_is_2x_equity PASSED
tests/test_margin.py::TestBuyingPower::test_bp_reduces_after_reservation PASSED
tests/test_margin.py::TestAtomicReserve::test_reserve_success PASSED
tests/test_margin.py::TestAtomicReserve::test_reserve_exceeds_bp_fails PASSED
tests/test_margin.py::TestAtomicReserve::test_duplicate_order_id_fails PASSED
tests/test_margin.py::TestAtomicReserve::test_release_restores_bp PASSED
tests/test_margin.py::TestAtomicReserve::test_confirm_updates_cash_and_position PASSED
tests/test_margin.py::TestConcurrentReserve::test_no_buying_power_violation PASSED
tests/test_margin.py::TestPDTRule::test_pdt_restriction_triggers_below_25k PASSED
tests/test_margin.py::TestPDTRule::test_no_pdt_restriction_above_25k PASSED
tests/test_margin.py::TestMarginHealth::test_healthy_with_no_positions PASSED
tests/test_margin.py::TestMarginHealth::test_margin_call_detected PASSED
15 passed in 0.31s
The Concurrency Stress Test
This is the real test. It fires 1,000 threads simultaneously at a single MarginAccount with a $10,000 cash balance. Each thread tries to reserve $1,000. In CASH mode, exactly 10 should succeed. Not 11. Not 12. Exactly 10.
python tests/stress_test.py
Expected output:
=======================================================
STRESS TEST: 1000 concurrent reservation attempts
=======================================================
Initial Cash: $10,000.00
Per-Reserve: $1,000.00
Max Allowed: 10
Approved: 10
Rejected: 990
Reserved Total: $10,000.00
Elapsed: 143.2ms
VIOLATION: NONE
=======================================================
All stress tests passed.
If you see Approved: 11 or higher, your lock is broken. Do not continue until this is clean.
The second run tests DAY_TRADE mode, where 4x leverage means a $10,000 account has $40,000 in buying power — so 20 reservations of $2,000 each should succeed.
Full Verification Suite
Once both tests pass individually, run the complete verification:
./scripts/verify.sh
This executes unit tests, the stress test, and then attempts to reconcile your local MarginAccount buying power against your Alpaca paper account. If your .env file is set up correctly, you will see a live reconciliation line at the end:
[1/3] Running unit tests...
15 passed
[2/3] Running concurrency stress test...
VIOLATION: NONE
[3/3] Reconciling against Alpaca paper account...
Alpaca Day-Trade BP: $200,000.00
Local Day-Trade BP: $200,143.28
BP Delta: 0.0716%
Within Tolerance: yes
==================================================
Unit Tests: PASS
Stress Test: PASS
Alpaca Reconcile: PASS
==================================================
If you do not have Alpaca keys yet, the reconciliation step will gracefully skip and report itself as passing. Come back to it once you have signed up.
Running the Live Dashboard
The demo script starts a Rich terminal dashboard that shows your margin state updating in real time. It also runs a background thread that submits random orders to the MockBroker and applies random price moves to simulate market volatility.
python scripts/demo.py
The dashboard has three panels. The top-left panel shows account health, the current margin ratio with a progress bar, equity, cash, reserved buying power, and the active account mode. The top-right panel shows day-trade buying power (4x), overnight buying power (2x), and cash buying power (1x) separately. The middle panel lists open positions with unrealized P&L in green or red. The bottom panel is a live event log — when the MarginMonitor detects a health transition, it appears here within 500ms.
To trigger a margin call manually for testing, open scripts/demo.py and add this line inside the simulate_market function after a position opens:
account.update_prices({"AAPL": Decimal("20.00")}) # crash from ~$400
You should see the event log show:
14:23:07 healthy → margin_call ratio=18.3%
If that line never appears, the monitor thread is silently swallowing an exception. Check that _compute_margin_ratio() has the zero-exposure guard — without it, an account with no positions causes a division-by-zero that kills the thread.
Press Ctrl+C to stop the dashboard cleanly.
Starting the Live Alpaca Monitor
Once your .env file has real paper keys, you can run a persistent reconciliation loop against your actual Alpaca account:
python scripts/start.py
This connects to paper-api.alpaca.markets, polls your account every 10 seconds, and compares the buying power and equity figures against your local MarginAccount. It also keeps the MarginMonitor running in the background and will print an alert if the health state transitions while you watch.
Live margin monitoring started. Press Ctrl+C to stop.
Reconciling against Alpaca every 10s...
[14:31:00] Alpaca BP=$200,000 Local BP=$200,143 Delta=0.0716%
[14:31:10] Alpaca BP=$200,000 Local BP=$200,143 Delta=0.0716%
When you are done:
./scripts/cleanup.sh
This removes all __pycache__ directories, .pyc files, and .pytest_cache folders. It does not touch your .env file or any generated data.
Success Criteria
You clear Day 45 when all four of these pass.
Gate 1 — No buying-power violations. Run python tests/stress_test.py. The approved count must be exactly 10 for the cash-mode test, and the violation field must read NONE. Any higher approval count means two threads both passed the buying-power check before either committed — your lock is not working.
Gate 2 — Alpaca reconciliation delta under 0.1%. Run ./scripts/verify.sh with real paper keys. The BP Delta line must be below 0.1%. A delta above that means your leverage multiplier or Decimal quantization is wrong. Check _compute_buying_power() and make sure you are not mixing float and Decimal anywhere in the equity calculation.
Gate 3 — MarginMonitor emits a margin call. Follow the steps in the dashboard section above to crash a position price. The event log must show a healthy → margin_call transition within 500ms of the price update. If it never fires, check the monitor thread is actually running and that _compute_health() has the correct threshold comparisons.
Gate 4 — PDT restriction fires below $25k. Run this snippet directly:
from decimal import Decimal
from src.margin.account import AccountMode, MarginAccount
acct = MarginAccount(Decimal("24500.00"), mode=AccountMode.DAY_TRADE)
for _ in range(4):
acct.record_day_trade()
snap = acct.snapshot()
assert snap.buying_power.mode.name == "CASH", f"Got: {snap.buying_power.mode.name}"
print("PDT gate passed.")
It must print PDT gate passed. If it prints DAY_TRADE, your PDT equity comparison is using float somewhere — convert the threshold to Decimal.
Homework: Cross-Position Margin Netting
Real Reg T allows offsetting positions to reduce net margin requirements. If you hold long SPY and short SH (an inverse SPY ETF), your net market exposure is lower than the sum of both positions individually — and so your required margin is lower too.
Your task is to implement a PortfolioMarginCalculator class that accepts a list of Position objects and returns a NetMarginRequirement using correlation-adjusted netting. Use a hardcoded correlation matrix for now — Day 47 will make it dynamic.
Three requirements to meet:
Add
PortfolioMarginCalculatorwith acompute_net_requirement(positions)method that returns aNetMarginRequirementdataclass containing the gross requirement, the netting discount, and the net requirement.Write a stress test that opens 20 offsetting long/short pairs and asserts that
net_requirement < sum(individual_requirements)for each pair.Call
GET /v2/accountfrom Alpaca, read themaintenance_marginfield, and assert that your net margin figure matches it within 5%.
The success gate for the homework is: verify.sh passes all tests, the stress test shows zero violations across 1,000 concurrent reservation attempts, and the Alpaca reconciliation delta stays below 0.1%.





