haha
This commit is contained in:
@@ -49,6 +49,14 @@ class BBConfig:
|
||||
rebate_pct: float = 0.0 # e.g. 0.70 = 70% rebate
|
||||
rebate_hour_utc: int = 0 # hour in UTC when rebate arrives (0 = 8am UTC+8)
|
||||
|
||||
# Pyramid (加仓): add to position on repeated same-direction BB touch
|
||||
pyramid_enabled: bool = False
|
||||
pyramid_decay: float = 0.99 # each add uses margin * decay^n (n = add count)
|
||||
pyramid_max: int = 10 # max number of adds (0 = unlimited)
|
||||
# Increment mode: each add uses equity * (margin_pct + pyramid_step * n)
|
||||
# e.g. step=0.01 → 1st open=1%, 1st add=2%, 2nd add=3% ...
|
||||
pyramid_step: float = 0.0 # 0 = use decay mode; >0 = use increment mode
|
||||
|
||||
|
||||
@dataclass
|
||||
class BBTrade:
|
||||
@@ -105,6 +113,8 @@ def run_bb_backtest(df: pd.DataFrame, cfg: BBConfig) -> BBResult:
|
||||
entry_time = None
|
||||
entry_margin = 0.0
|
||||
entry_qty = 0.0
|
||||
pyramid_count = 0 # number of adds so far
|
||||
last_add_margin = 0.0 # margin used in last open/add
|
||||
|
||||
trades: List[BBTrade] = []
|
||||
total_fee = 0.0
|
||||
@@ -136,6 +146,7 @@ def run_bb_backtest(df: pd.DataFrame, cfg: BBConfig) -> BBResult:
|
||||
def close_position(exit_price, exit_idx):
|
||||
nonlocal balance, position, entry_price, entry_time, entry_margin, entry_qty
|
||||
nonlocal total_fee, total_rebate, day_pnl, today_fees
|
||||
nonlocal pyramid_count, last_add_margin
|
||||
|
||||
if position == 0:
|
||||
return
|
||||
@@ -174,12 +185,23 @@ def run_bb_backtest(df: pd.DataFrame, cfg: BBConfig) -> BBResult:
|
||||
entry_time = None
|
||||
entry_margin = 0.0
|
||||
entry_qty = 0.0
|
||||
pyramid_count = 0
|
||||
last_add_margin = 0.0
|
||||
|
||||
def open_position(side, price, idx):
|
||||
def open_position(side, price, idx, is_add=False):
|
||||
nonlocal position, entry_price, entry_time, entry_margin, entry_qty
|
||||
nonlocal balance, total_fee, day_pnl, today_fees
|
||||
nonlocal pyramid_count, last_add_margin
|
||||
|
||||
if cfg.margin_pct > 0:
|
||||
if is_add and cfg.pyramid_step > 0:
|
||||
# 递增加仓: margin = equity * (margin_pct + step * (count+1))
|
||||
equity = balance + unrealised(price)
|
||||
pct = cfg.margin_pct + cfg.pyramid_step * (pyramid_count + 1)
|
||||
margin = equity * pct
|
||||
elif is_add:
|
||||
# 衰减加仓: margin = last_add_margin * decay
|
||||
margin = last_add_margin * cfg.pyramid_decay
|
||||
elif cfg.margin_pct > 0:
|
||||
equity = balance + unrealised(price) if position != 0 else balance
|
||||
margin = equity * cfg.margin_pct
|
||||
else:
|
||||
@@ -196,11 +218,24 @@ def run_bb_backtest(df: pd.DataFrame, cfg: BBConfig) -> BBResult:
|
||||
today_fees += fee
|
||||
day_pnl -= fee
|
||||
|
||||
position = 1 if side == "long" else -1
|
||||
entry_price = price
|
||||
entry_time = ts_index[idx]
|
||||
entry_margin = margin
|
||||
entry_qty = qty
|
||||
if is_add and position != 0:
|
||||
# 加仓: weighted average entry price
|
||||
old_notional = entry_qty * entry_price
|
||||
new_notional = qty * price
|
||||
entry_qty += qty
|
||||
entry_price = (old_notional + new_notional) / entry_qty
|
||||
entry_margin += margin
|
||||
pyramid_count += 1
|
||||
last_add_margin = margin
|
||||
else:
|
||||
# 新开仓
|
||||
position = 1 if side == "long" else -1
|
||||
entry_price = price
|
||||
entry_time = ts_index[idx]
|
||||
entry_margin = margin
|
||||
entry_qty = qty
|
||||
pyramid_count = 0
|
||||
last_add_margin = margin
|
||||
|
||||
# Main loop
|
||||
for i in range(n):
|
||||
@@ -268,17 +303,27 @@ def run_bb_backtest(df: pd.DataFrame, cfg: BBConfig) -> BBResult:
|
||||
if position == 1:
|
||||
# Close long at upper BB price
|
||||
close_position(arr_upper[i], i)
|
||||
if position != -1:
|
||||
if position == 0:
|
||||
# Open short
|
||||
open_position("short", arr_upper[i], i)
|
||||
elif position == -1 and cfg.pyramid_enabled:
|
||||
# Already short → add to short (pyramid)
|
||||
can_add = cfg.pyramid_max <= 0 or pyramid_count < cfg.pyramid_max
|
||||
if can_add:
|
||||
open_position("short", arr_upper[i], i, is_add=True)
|
||||
elif touched_lower:
|
||||
# Price touched lower BB → go long
|
||||
if position == -1:
|
||||
# Close short at lower BB price
|
||||
close_position(arr_lower[i], i)
|
||||
if position != 1:
|
||||
if position == 0:
|
||||
# Open long
|
||||
open_position("long", arr_lower[i], i)
|
||||
elif position == 1 and cfg.pyramid_enabled:
|
||||
# Already long → add to long (pyramid)
|
||||
can_add = cfg.pyramid_max <= 0 or pyramid_count < cfg.pyramid_max
|
||||
if can_add:
|
||||
open_position("long", arr_lower[i], i, is_add=True)
|
||||
|
||||
# Record equity
|
||||
out_equity[i] = balance + unrealised(arr_close[i])
|
||||
|
||||
Reference in New Issue
Block a user