You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 08:09:04