A simple mean-reversion idea: buy when the price dips to a "cheap" level, sell when it climbs to a "rich" level, and never hold too much either way. Before trusting it, you replay it over a recorded price tape. That's a backtest: a loop over time that applies the strategy's rules and keeps position and cash up to date.
Implement band_trader(prices, low, high, lot, max_pos, fee) -> tuple[int, int, int, int].
You start flat (position 0, cash 0). For each price p in prices, in order, apply at most one rule:
- If
p <= low: buymin(lot, max_pos - position)shares atp, if that's more than 0. - Otherwise, if
p >= high: sellmin(lot, max_pos + position)shares atp, if that's more than 0. Selling below zero is allowed (going short) down to-max_pos.
Every trade (not every share) pays a flat fee out of cash. A buy of q shares costs q·p + fee; a sell of q shares brings in q·p - fee. A skipped trade (size 0) pays nothing.
Return (position, cash, pnl, trades) where pnl = cash + position · last_price (the last price on the tape; pnl = cash if the tape is empty) and trades is how many trades happened.
band_trader([100, 97, 96, 99, 104, 105, 103], low=97, high=104, lot=2, max_pos=3, fee=1)
# 97: buy 2 pos 2 cash -195
# 96: buy 1 (limit) pos 3 cash -292
# 104: sell 2 pos 1 cash -85
# 105: sell 2 pos -1 cash 124
# pnl = 124 + (-1)·103 = 21
# -> (-1, 124, 21, 4)
band_trader([], 10, 20, 1, 1, 0) # (0, 0, 0, 0)
Constraints: 0 <= len(prices) <= 10^5; every price is in 1..10^6; 1 <= low < high <= 10^6; 1 <= lot, max_pos <= 10^6; 0 <= fee <= 10^6; all integers. Cash and PnL can exceed 32 bits (they are 64-bit integers in C++/Java).
Show hint
Keep position, cash and trades as running totals. At each tick work out the allowed size first (min(lot, room)), and only trade and charge the fee when that size is positive.