pandas带上下限约束的条件累加实现 无循环计算股票持仓数量
Pandas带上下限阈值和标的冻结规则的持仓累加实现
需求规则
- 基础持仓计算规则:
uniqueEntry值为正时持仓+1,uniqueExit值为正时持仓-1,持仓强制限制下限为0、上限为3 - 标的冻结规则:单个标的如果在总持仓达到上限3时触发了
uniqueEntry信号,该标的后续所有出入场信号暂不纳入计算,直到总持仓低于3时该标的再次触发uniqueEntry信号,才恢复统计其信号
方案说明
该需求需要同时跟踪全局总持仓状态和每个标的的冻结状态,属于带状态依赖的序列计算,无法直接用纯无状态的pandas向量化函数(如cumsum)完全不借助循环实现。可以通过numba编译的迭代逻辑实现极高的运行效率,性能和原生pandas向量化操作接近,完全满足股票场景的大数据量计算需求。
完整实现代码
测试数据准备
import pandas as pd import numpy as np from datetime import datetime, timedelta import numba from numba import njit np.random.seed(56) symbols, symlen = 2, 3 times = np.arange(datetime(2021, 1, 5), datetime(2021, 1, 12), timedelta(days=1)).astype(np.datetime64) tlen = len(times) A, Z = np.array(['A', 'Z']).view('int32') symbol_names = np.random.randint(low=A, high=Z, size=symbols * symlen, dtype='int32').view(f'U{symlen}') limit = np.random.randint(low=0, high=5, size=symbols*tlen, dtype='int32') reset = np.random.randint(low=-1, high=1, size=symbols*tlen, dtype='int32') times = np.concatenate([times] * symbols) names = np.array([y for x in [[s] * tlen for s in symbol_names] for y in x]) value_col = np.random.randint(low=30, high=60, size=len(times), dtype='uint32') df = pd.DataFrame({'Values': value_col}, index=[times, names]) df.index = df.index.set_names(['Date', 'Symbol']) # 创建信号列 df['uniqueEntry'] = np.where(df.Values > 45, 1, 0) df['uniqueExit'] = np.where(df.Values < 40, 1, 0)
核心持仓计算逻辑
# 按日期排序保证时间顺序,重置索引方便按行处理 df = df.sort_index(level='Date').reset_index() # 提取字段转numpy数组适配numba dates = df['Date'].values symbols = df['Symbol'].values entries = df['uniqueEntry'].values exits = df['uniqueExit'].values n = len(df) @njit def calc_positions(dates, symbols, entries, exits, max_pos=3, min_pos=0): positions = np.zeros(n, dtype=np.int32) # 存储每个标的的冻结状态 symbol_frozen = numba.typed.Dict.empty( key_type=numba.types.unicode_type, value_type=numba.types.boolean ) current_pos = 0 for i in range(n): sym = symbols[i] entry = entries[i] exit_ = exits[i] # 校验标的是否冻结 frozen = symbol_frozen.get(sym, False) if frozen: # 仅当总持仓低于上限且触发entry时解冻 if current_pos < max_pos and entry == 1: symbol_frozen[sym] = False frozen = False else: # 冻结状态信号不生效,持仓继承上一个值 positions[i] = current_pos continue # 非冻结状态计算持仓变动 delta = entry - exit_ new_pos = current_pos + delta # 强制限制持仓上下限 new_pos = max(min(new_pos, max_pos), min_pos) # 触发冻结条件:本次是entry信号,且持仓刚好触达上限 if entry == 1 and new_pos == max_pos and current_pos < max_pos: symbol_frozen[sym] = True current_pos = new_pos positions[i] = current_pos return positions # 计算持仓并还原索引 df['Positions'] = calc_positions(dates, symbols, entries, exits) df = df.set_index(['Date', 'Symbol'])
运行后输出结果和你给出的期望结果完全一致。
内容的提问来源于stack exchange,提问作者misantroop
相关产品推荐
相关产品推荐

