分类: 股票

  • 大盘涨1.45%

    我的股票涨不太好,昨天挺好

  • 半个月以来股票账户首次翻红。

    这半个月以来,股票指数从3400涨到了3780。可是我的股票一直不涨,除了农业银行涨得不错以外,中国海油、冀中能源、中国电信,还有云南白药一直在原地踏步。也跌不多,涨也不动。今天大盘终于止步上升。我的股票开始涨了。

  • 股票每天一键跑python代码

    A股双周调仓:一键日常量化脚本(AkShare)

    你要的是能每天一键跑、两次/周调仓、风控中性的实用脚本。下面这套方案以沪深300成分为底层池(稳流动性,避免幸存者偏差),周二/周四收盘后出信号,次日开盘按目标权重成交,含成本、滑点与涨跌停成交约束,并输出订单与绩效图。


    策略与风控设计

    • 目标市场与频率: A股,周二/周四调仓;信号用当日收盘数据,次日开盘成交,避开“偷看未来”。
    • 标的池: 沪深300成分,辅以滚动流动性过滤(近60日日均成交量门槛)。
    • 选股逻辑(中风险,趋势+动量+波动约束):
    • 趋势: 20日均线在60日均线上方,收盘在MA20上方。
    • 动量: 126日动量为正;RSI处于温和区间(45–65)以降低追涨顶。
    • 波动/过热: 收盘低于布林带上轨,避免过度扩张。
    • 持仓构建: 从通过筛选的股票中选前 TopK(按动量排序),用波动率倒数配权,单票上限 12%,默认最多 15 只。
    • 权重公式:对第 (i) 只,计算 20 日年化波动 (\sigma_i),原始权重 (w_i’ = 1/\sigma_i),归一化后得到 (w_i),并进行上限裁剪与再归一化。
    • 交易与成本:
    • 成交价: 次日开盘价。
    • 费率: 佣金 0.0005(双边),卖出印花税 0.001(单边),滑点 0.0005(双边)。
    • 涨跌停: 若次日开盘相对昨收涨幅 ≥9.5%(买入)或跌幅 ≤-9.5%(卖出),视为无法成交,当天该笔订单跳过。
    • 绩效与图形: 组合净值曲线、最大回撤、持仓变动与当日调仓订单 CSV。

    一键日常脚本(直接可运行)

    先安装依赖:

    pip install akshare pandas numpy matplotlib
    # -*- coding: utf-8 -*-
    # A股双周调仓 一键日常研究与回测脚本(AkShare)
    # 运行环境:Python 3.9+;依赖:akshare, pandas, numpy, matplotlib
    
    import akshare as ak
    import pandas as pd
    import numpy as np
    import time
    from datetime import datetime, timedelta
    import matplotlib.pyplot as plt
    
    # 修改为Windows系统默认中文字体
    plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei", "SimSun"]
    plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题
    
    # -----------------------
    # 参数区(按需修改)
    # -----------------------
    START_DATE = "2018-01-01"
    END_DATE   = None  # None 表示到今日
    CASH_INIT  = 1_000_000
    MAX_POS    = 15           # 最多持仓数
    MAX_W      = 0.12         # 单票权重上限
    FEE_COMM   = 0.0005       # 佣金
    FEE_STAMP  = 0.001        # 印花税(仅卖出)
    SLIPPAGE   = 0.0005       # 滑点
    LIQ_VOL_TH = 1_000_000    # 近60日平均成交量门槛(手/股),可按需要调
    REBAL_WEEKDAYS = {1, 3}   # 周二(1)、周四(3) 调仓;Python: Mon=0
    
    # -----------------------
    # 工具函数:指标
    # -----------------------
    def sma(s, n):
        return s.rolling(n).mean()
    
    def rsi(close, n=14):
        delta = close.diff()
        up = np.where(delta > 0, delta, 0.0)
        dn = np.where(delta < 0, -delta, 0.0)
        up_ema = pd.Series(up, index=close.index).ewm(alpha=1/n, adjust=False).mean()
        dn_ema = pd.Series(dn, index=close.index).ewm(alpha=1/n, adjust=False).mean()
        rs = up_ema / dn_ema.replace(0, np.nan)
        return 100 - (100 / (1 + rs))
    
    def bbands(close, n=20, k=2):
        mid = close.rolling(n).mean()
        std = close.rolling(n).std(ddof=0)
        up = mid + k * std
        dn = mid - k * std
        return mid, up, dn
    
    def true_range(df):
        prev_close = df["close"].shift(1)
        tr = pd.concat([
            (df["high"] - df["low"]).abs(),
            (df["high"] - prev_close).abs(),
            (df["low"] - prev_close).abs()
        ], axis=1).max(axis=1)
        return tr
    
    def ann_vol(close, n=20):
        ret = close.pct_change()
        return ret.rolling(n).std() * np.sqrt(252)
    
    # -----------------------
    # 数据获取与基准日历
    # -----------------------
    def get_trade_calendar(start=START_DATE, end=END_DATE):
        cal = ak.tool_trade_date_hist_sina()
        cal["trade_date"] = pd.to_datetime(cal["trade_date"])
        if end is None:
            end = datetime.now().strftime("%Y-%m-%d")
        cal = cal[(cal["trade_date"] >= pd.to_datetime(start)) &
                  (cal["trade_date"] <= pd.to_datetime(end))]["trade_date"].sort_values()
        return cal.tolist()
    
    def get_hs300_symbols():
        df = ak.index_stock_cons(symbol="000300")
        # 列可能是 '品种代码' 或 '成分券代码'; 做兼容
        for col in ["品种代码", "成分券代码", "代码", "code"]:
            if col in df.columns:
                return sorted(df[col].astype(str).str.zfill(6).unique().tolist())
        # 兜底
        return sorted(df.iloc[:,0].astype(str).str.zfill(6).unique().tolist())
    
    def get_hist(code, start=START_DATE, end=END_DATE, adjust="qfq"):
        if end is None:
            end = datetime.now().strftime("%Y%m%d")
        df = ak.stock_zh_a_hist(symbol=code, period="daily",
                                start_date=start.replace("-",""),
                                end_date=end.replace("-",""),
                                adjust=adjust)
        # 兼容列名
        mapper = {"日期":"date","开盘":"open","收盘":"close","最高":"high","最低":"low","成交量":"volume","成交额":"amount"}
        df = df.rename(columns=mapper)
        df["date"] = pd.to_datetime(df["date"])
        cols = [c for c in ["date","open","high","low","close","volume","amount"] if c in df.columns]
        df = df[cols].set_index("date").sort_index()
        df = df.dropna()
        return df
    
    # -----------------------
    # 信号与筛选
    # -----------------------
    def compute_indicators(df):
        out = df.copy()
        out["MA20"] = sma(out["close"], 20)
        out["MA60"] = sma(out["close"], 60)
        out["RSI14"] = rsi(out["close"], 14)
        out["MOM126"] = out["close"] / out["close"].shift(126) - 1
        mid, up, dn = bbands(out["close"], 20, 2)
        out["BB_MID"], out["BB_UP"], out["BB_DN"] = mid, up, dn
        out["ANNVOL20"] = ann_vol(out["close"], 20)
        out["TR"] = true_range(out)
        out["ATR20"] = out["TR"].rolling(20).mean()
        return out
    
    def pass_screen(row):
        c1 = row["MA20"] > row["MA60"]
        c2 = row["close"] > row["MA20"]
        c3 = row["MOM126"] > 0
        c4 = 45 <= row["RSI14"] <= 65
        c5 = row["close"] < row["BB_UP"]
        return c1 and c2 and c3 and c4 and c5
    
    # -----------------------
    # 回测:两次/周调仓,次日开盘成交
    # -----------------------
    def backtest_portfolio(symbols, start=START_DATE, end=END_DATE, cash_init=CASH_INIT):
        # 下载数据
        data = {}
        for i, sym in enumerate(symbols, 1):
            try:
                df = get_hist(sym, start, end)
                data[sym] = compute_indicators(df)
            except Exception:
                pass
            time.sleep(0.2)  # 温和限速
        # 统一日历
        all_dates = sorted(set().union(*[df.index for df in data.values()]))
        cal = pd.DatetimeIndex(all_dates)
        # 选择调仓日(周二/周四且是交易日)
        rebal_days = [d for d in cal if d.weekday() in REBAL_WEEKDAYS]
        # 过滤:近60日均量
        def liquid_ok(df, dt):
            window = df.loc[:dt].tail(60)
            if "volume" not in window: 
                return True
            return window["volume"].mean() >= LIQ_VOL_TH
    
        # 状态
        cash = cash_init
        positions = {}  # sym -> shares
        nav_series = []
        dd_series = []
        equity = cash
        peak = equity
        last_prices = {}
    
        # 逐日仿真
        for i, d in enumerate(cal[:-1]):  # 至倒数第二天(因次日开盘成交)
            todays_vals = {}
            # 更新持仓市值
            for sym, df in data.items():
                if d in df.index:
                    last_prices[sym] = df.at[d, "close"]
                if sym in positions and sym in last_prices:
                    todays_vals[sym] = positions[sym] * last_prices[sym]
            equity = cash + sum(todays_vals.values())
            peak = max(peak, equity)
            drawdown = (equity / peak) - 1
            nav_series.append((d, equity))
            dd_series.append((d, drawdown))
    
            # 调仓信号(用今日收盘)
            if d in rebal_days:
                # 生成候选
                candidates = []
                for sym, df in data.items():
                    if d not in df.index: 
                        continue
                    if not liquid_ok(df, d):
                        continue
                    row = df.loc[d]
                    # 要求指标有效
                    if np.any(pd.isna(row[["MA20","MA60","RSI14","MOM126","BB_UP","ANNVOL20"]])):
                        continue
                    if pass_screen(row):
                        candidates.append((sym, row["MOM126"], row["ANNVOL20"]))
                # 排序与截断
                candidates.sort(key=lambda x: x[1], reverse=True)
                picks = candidates[:MAX_POS]
    
                # 计算目标权重(波动率倒数)
                if picks:
                    vols = np.array([max(1e-6, x[2]) for x in picks])
                    inv = 1.0 / vols
                    w_raw = inv / inv.sum()
                    # 单票上限
                    w_capped = np.minimum(w_raw, MAX_W)
                    w = w_capped / w_capped.sum()
                    target = {sym: w[j] for j, (sym, _, _) in enumerate(picks)}
                else:
                    target = {}
    
                # 次日开盘执行
                nd = cal[i+1]
                # 构建目标头寸价值
                target_value = {sym: equity * w for sym, w in target.items()}
    
                # 先卖出未在目标内或超配部分
                for sym in list(positions.keys()):
                    df = data.get(sym)
                    if df is None or nd not in df.index or d not in df.index:
                        continue
                    prev_close = df.at[d, "close"]
                    next_open = df.at[nd, "open"]
                    # 跌停无法卖出(近似)
                    if next_open <= prev_close * (1 - 0.095):
                        continue
                    price = next_open * (1 - SLIPPAGE)
                    cur_val = positions[sym] * price
                    tgt_val = target_value.get(sym, 0.0)
                    if cur_val > tgt_val + 1:  # 超配或不在目标
                        sell_val = cur_val - tgt_val
                        shares = int(sell_val // price)
                        if shares > 0:
                            proceeds = shares * price * (1 - FEE_COMM - FEE_STAMP)
                            positions[sym] -= shares
                            if positions[sym] <= 0:
                                positions.pop(sym, None)
                            cash += proceeds
    
                # 再买入不达标或新标的
                for sym, tgt_val in target_value.items():
                    df = data.get(sym)
                    if df is None or nd not in df.index or d not in df.index:
                        continue
                    prev_close = df.at[d, "close"]
                    next_open = df.at[nd, "open"]
                    # 涨停无法买入(近似)
                    if next_open >= prev_close * (1 + 0.095):
                        continue
                    price = next_open * (1 + SLIPPAGE)
                    cur_shares = positions.get(sym, 0)
                    cur_val = cur_shares * price
                    buy_val = max(0.0, tgt_val - cur_val)
                    shares = int(buy_val // price)
                    if shares > 0 and cash > shares * price * (1 + FEE_COMM):
                        cost = shares * price * (1 + FEE_COMM)
                        cash -= cost
                        positions[sym] = cur_shares + shares
    
        nav = pd.Series({d: v for d, v in nav_series}).sort_index()
        dd = pd.Series({d: v for d, v in dd_series}).sort_index()
        ret = nav.pct_change().fillna(0)
        stats = {
            "CAGR": (nav.iloc[-1] / nav.iloc[0]) ** (252/len(nav)) - 1,
            "Vol": ret.std() * np.sqrt(252),
            "Sharpe": (ret.mean() / (ret.std() + 1e-9)) * np.sqrt(252),
            "MaxDD": dd.min()
        }
        return nav, dd, positions, stats
    
    # -----------------------
    # 今日调仓计划(实用日常)
    # -----------------------
    def today_rebalance_plan():
        today = pd.Timestamp(datetime.now().date())
        # 若今天不是交易日或不是周二/周四,直接提示
        cal = get_trade_calendar((today - pd.Timedelta(days=10)).strftime("%Y-%m-%d"),
                                 today.strftime("%Y-%m-%d"))
        cal_idx = pd.DatetimeIndex(cal)
        if today not in cal_idx or today.weekday() not in REBAL_WEEKDAYS:
            print("今天不是计划调仓日(或非交易日)。")
            return
    
        syms = get_hs300_symbols()
        plan_rows = []
        for sym in syms:
            try:
                df = get_hist(sym, (today - pd.Timedelta(days=400)).strftime("%Y-%m-%d"),
                                   today.strftime("%Y-%m-%d"))
                df = compute_indicators(df)
                if len(df) < 200 or today not in df.index:
                    continue
                row = df.loc[today]
                # 流动性
                if "volume" in df:
                    if df.loc[:today].tail(60)["volume"].mean() < LIQ_VOL_TH:
                        continue
                if np.any(pd.isna(row[["MA20","MA60","RSI14","MOM126","BB_UP","ANNVOL20"]])):
                    continue
                if pass_screen(row):
                    plan_rows.append({
                        "code": sym,
                        "mom126": row["MOM126"],
                        "annvol20": row["ANNVOL20"],
                        "close": row["close"]
                    })
            except Exception:
                pass
            time.sleep(0.05)
    
        if not plan_rows:
            print("今日无标的通过筛选。")
            return
    
        dfp = pd.DataFrame(plan_rows).sort_values("mom126", ascending=False).head(MAX_POS)
        inv = 1.0 / np.maximum(1e-6, dfp["annvol20"].values)
        w_raw = inv / inv.sum()
        w_capped = np.minimum(w_raw, MAX_W)
        w = w_capped / w_capped.sum()
        dfp["target_weight"] = w
        dfp.to_csv(f"rebalance_plan_{today.strftime('%Y%m%d')}.csv", index=False, encoding="utf-8-sig")
        print("今日计划(次日开盘执行,权重已截顶):")
        print(dfp[["code","target_weight","mom126","annvol20","close"]])
    
    # -----------------------
    # 主函数:回测 + 图形 + 今日计划
    # -----------------------
    if __name__ == "__main__":
        print("获取沪深300成分...")
        symbols = get_hs300_symbols()
        print(f"成分股数量:{len(symbols)}")
    
        print("开始回测(这可能需要几分钟)...")
        nav, dd, positions, stats = backtest_portfolio(symbols, START_DATE, END_DATE, CASH_INIT)
        print("回测统计:")
        for k, v in stats.items():
            print(f"{k}: {v:.4f}")
    
        # 绘制净值与回撤
        fig, ax = plt.subplots(2, 1, figsize=(10, 6), sharex=True,
                               gridspec_kw={"height_ratios":[3,1]})
        nav_norm = nav / nav.iloc[0]
        ax[0].plot(nav_norm.index, nav_norm.values, label="Portfolio")
        ax[0].set_title("组合净值(归一)")
        ax[0].legend()
        ax[0].grid(True, alpha=0.3)
        ax[1].fill_between(dd.index, dd.values, 0, color="red", alpha=0.3)
        ax[1].set_title("回撤")
        ax[1].grid(True, alpha=0.3)
        plt.tight_layout()
        plt.savefig("backtest_nav_drawdown.png", dpi=150)
        plt.show()
    
        # 当日调仓计划(在 CST 下午 15:10 后运行更合适)
        today_rebalance_plan()

    如何每天一键跑

    • 时间点: 沧州本地时间建议在交易日 15:10 之后运行,保证当日收盘数据可用。
    • 命令行:
    • 运行: python run_quant.py
    • 输出: backtest_nav_drawdown.png、rebalance_plan_YYYYMMDD.csv,并在控制台打印统计与当日计划。
    • 定时任务:
    • Linux crontab: 10 15 * * 1-5 /usr/bin/python3 /path/run_quant.py >> /path/log.txt 2>&1
    • Windows 任务计划: 设为工作日 15:10 触发。

    可调参数与扩展

    • 可调参数:
    • MAX_POS/MAX_W: 控制集中度与风险。
    • LIQ_VOL_TH: 提高门槛可进一步降低流动性风险。
    • 费率/滑点: 按你的券商费率与成交体验微调。
    • 风控增强:
    • 止损/跟踪止盈: 以 ATR 为单位,如价格跌破 MA60 或 3×ATR 跌幅平仓。
    • 涨跌停细化: 科创/创业板 20% 规则可按股票板块调整阈值。
    • 基准比较: 叠加沪深300指数净值,计算超额与信息比率。
    • 研究维度:
    • 横截面多因子: 以动量、波动、估值(PE/PB,需额外数据)做打分,周二/周四同频调仓。
    • 再平衡鲁棒性: 改为“阈值再平衡”(偏离>25%才调仓)降低换手。

    如果你想把标的池改为中证500或加入行业中性约束,或者把调仓日改成“每周最近的周二与周四,如遇节假日顺延”,我可以把上述脚本再细化成模块化的研究框架,并加上性能剖析与结果缓存来加速日常跑批。

    以下是程序运行结果的详细解释:

    1. 数据获取与回测概况
      沪深300成分股数量:282:成功获取了当前沪深300指数的282只成分股
      回测耗时提示:策略回测需要一定计算时间,符合预期
    2. 核心回测指标解读
      | 指标 | 数值 | 含义解释 | 策略评估 |
      |——–|———|———————————–|—————————|
      | CAGR | -0.0900 | 复合年化增长率:-9.00% | 策略整体年化亏损9% |
      | Vol | 0.2348 | 波动率:23.48% | 收益波动较大 |
      | Sharpe | -0.2843 | 夏普比率:-0.28 | 风险调整后收益为负,表现弱于无风险资产 |
      | MaxDD | -0.3505 | 最大回撤:-35.05% | 策略历史最大亏损幅度为35% |
    3. 今日投资计划(次日开盘执行)
      | 代码 | 目标权重 | 126天动量(mom126) | 20天年化波动率(annvol20) | 收盘价 |
      |——–|———-|——————-|————————–|——–|
      | 300394 | 0.2 | 0.6719 | 0.7191 | 107.87 |
      | 603799 | 0.2 | 0.4765 | 0.4454 | 44.25 |
      | 002463 | 0.2 | 0.4701 | 0.5822 | 55.35 |
      | 002074 | 0.2 | 0.3858 | 0.2900 | 30.46 |
      | 000617 | 0.2 | 0.3205 | 0.4965 | 8.90 |

    计划参数说明:
    target_weight=0.2:采用等权重分配策略,每只股票配置20%仓位
    mom126:126天动量指标(越高表示近期趋势越强)
    annvol20:20天年化波动率(衡量短期风险,数值越低风险相对越小)

    1. 策略表现评估
      风险收益特征:当前策略呈现负收益、高波动特征,夏普比率为负表明策略未能有效创造超额收益
      最大回撤风险:35.05%的最大回撤需要警惕,可能超出多数投资者的风险承受能力
      持仓策略:选择了5只动量特征较强的股票进行等权重配置,兼顾了动量因子和波动率控制
  • 开源量化金融openBB

    OpenBB 是一个 免费且完全开源的金融分析平台 ,旨在为投资者、分析师、研究人员和开发者提供透明、灵活且易于使用的金融与宏观经济数据访问接口。它支持股票、期权、加密货币、外汇、宏观经济指标、固定收益等多种资产类别,并提供了丰富的扩展功能,以满足不同用户的需求。

    核心特点:

    • 多市场数据支持 :涵盖股票、期权、加密货币、外汇、宏观经济等。
    • 模块化设计 :支持插件扩展,便于个性化定制和功能增强。
    • Python 集成 :提供 Python SDK 和命令行工具(CLI),方便开发者进行自动化处理和策略开发。
    • AI 辅助分析 :内置 AI 金融分析师代理,支持自然语言查询和智能推荐。
    • 可视化界面 :通过 OpenBB Workspace 提供图形化操作界面,支持图表展示、仪表盘定制等功能。
    • 开源与社区驱动 :基于 AGPLv3 协议,代码完全开放,用户可自由修改和分发。

    安装方式:

    1. 使用 pip 安装:
      pip install openbb
    2. 或者克隆 GitHub 仓库:
      git clone https://github.com/OpenBB-finance/OpenBB.git

    应用场景:

    • 市场分析与技术指标计算
    • 投资组合风险管理
    • 量化交易策略回测与执行
    • 教育与研究中的金融数据分析

    如需进一步了解其使用方法或具体案例,可以参考其GitHub 页面或官方文档。

  • 密码保护:八月开局股票涨了

    此内容受密码保护。如需查阅,请在下方输入密码。

  • 股票基本特征计算绘图

    import akshare as ak
    from numba.core.event import end_event
    import pandas as pd
    import numpy as np
    import time
    import os
    
    # 尝试获取数据,添加错误处理
    max_retries = 3
    retry_count = 0
    stock_data = None
    
    data_file = 'stock_data.csv'
    
    # 检查是否已有数据文件
    if os.path.exists(data_file):
        print(f"从本地文件 {data_file} 加载数据...")
        try:
            stock_data = pd.read_csv(data_file, encoding='utf-8')
            print("数据加载成功!")
        except Exception as e:
            print(f"加载本地数据失败: {e}")
            stock_data = None
    
    # 如果没有本地数据,尝试从akshare获取
    while retry_count < max_retries and stock_data is None:
        try:
            print(f"尝试第 {retry_count+1}/{max_retries} 次获取股票数据...")
            stock_data = ak.stock_zh_a_hist(
                symbol="000937",  # 股票代码
                start_date="20230101",  # 开始日期
                end_date="20250801",  # 结束日期
                adjust="qfq"  # 前复权
            )
            # 保存数据到本地,以便下次使用
            stock_data.to_csv(data_file, index=False, encoding='utf-8')
            print("数据获取并保存成功!")
        except Exception as e:
            print(f"获取数据失败: {e}")
            retry_count += 1
            if retry_count < max_retries:
                print(f"{5}秒后重试...")
                time.sleep(5)
    
    if stock_data is None:
        print("多次尝试获取数据失败,请检查网络连接后再试。")
        exit(1)
    
    # 移除缺失值
    stock_data = stock_data.dropna()
    print(stock_data.head())
    
    # 暂时注释掉实时行情获取,避免额外的网络请求
    # # 获取A股所有股票实时行情
    # real_time_data = ak.stock_zh_a_spot_em()
    # 
    # # 筛选特定股票
    # stock_code = "000937"
    # filtered_data = real_time_data[real_time_data["代码"] == stock_code]
    # print(filtered_data.head())
    # 计算移动平均线(MA)
    stock_data['MA5'] = stock_data['收盘'].rolling(window=5).mean()  # 5日均线
    stock_data['MA10'] = stock_data['收盘'].rolling(window=10).mean()  # 10日均线
    stock_data['MA20'] = stock_data['收盘'].rolling(window=20).mean()  # 20日均线
    
    # 计算MACD指标
    stock_data['EMA12'] = stock_data['收盘'].ewm(span=12, adjust=False).mean()
    stock_data['EMA26'] = stock_data['收盘'].ewm(span=26, adjust=False).mean()
    stock_data['DIF'] = stock_data['EMA12'] - stock_data['EMA26']
    stock_data['DEA'] = stock_data['DIF'].ewm(span=9, adjust=False).mean()
    stock_data['MACD'] = (stock_data['DIF'] - stock_data['DEA']) * 2
    
    # 计算相对强弱指数(RSI)
    delta = stock_data['收盘'].diff(1)
    gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
    stock_data['RSI'] = 100 - (100 / (1 + gain / loss))
    
    # 计算布林带
    stock_data['BB_MID'] = stock_data['收盘'].rolling(window=20).mean()
    stock_data['BB_UP'] = stock_data['BB_MID'] + 2 * stock_data['收盘'].rolling(window=20).std()
    stock_data['BB_LOW'] = stock_data['BB_MID'] - 2 * stock_data['收盘'].rolling(window=20).std()
    
    #. 波动率特征
    # 计算日收益率
    stock_data['return'] = stock_data['收盘'].pct_change()
    
    # 计算波动率(标准差)
    stock_data['volatility_5d'] = stock_data['return'].rolling(window=5).std() * np.sqrt(5)
    stock_data['volatility_20d'] = stock_data['return'].rolling(window=20).std() * np.sqrt(20)
    ## . 量价关系特征
    # 计算量比
    stock_data['volume_ratio'] = stock_data['成交量'] / stock_data['成交量'].rolling(window=5).mean()
    # 计算成交额
    stock_data['amount'] = stock_data['收盘'] * stock_data['成交量']
    # 计算资金流向(简易版)
    stock_data['money_flow'] = (stock_data['收盘'] - stock_data['开盘']) * stock_data['成交量']
    # 基本面特征
    # 4. 可视化特征指标
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    from matplotlib.ticker import MaxNLocator
    
    # 设置中文显示
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
    
    # 创建画布和子图
    fig, axes = plt.subplots(4, 1, figsize=(12, 16), sharex=True)
    
    # 1. 绘制价格和移动平均线
    ax1 = axes[0]
    ax1.plot(stock_data['日期'], stock_data['收盘'], label='收盘价', color='blue')
    ax1.plot(stock_data['日期'], stock_data['MA5'], label='MA5', color='red')
    ax1.plot(stock_data['日期'], stock_data['MA10'], label='MA10', color='green')
    ax1.plot(stock_data['日期'], stock_data['MA20'], label='MA20', color='orange')
    ax1.set_title('股票价格与移动平均线')
    ax1.set_ylabel('价格')
    ax1.legend()
    ax1.grid(True)
    
    # 2. 绘制MACD
    ax2 = axes[1]
    ax2.plot(stock_data['日期'], stock_data['DIF'], label='DIF', color='blue')
    ax2.plot(stock_data['日期'], stock_data['DEA'], label='DEA', color='red')
    ax2.bar(stock_data['日期'], stock_data['MACD'], label='MACD', color='green', alpha=0.5)
    ax2.set_title('MACD指标')
    ax2.set_ylabel('值')
    ax2.legend()
    ax2.grid(True)
    
    # 3. 绘制RSI
    ax3 = axes[2]
    ax3.plot(stock_data['日期'], stock_data['RSI'], label='RSI', color='purple')
    ax3.axhline(y=70, color='red', linestyle='--', label='超买线(70)')
    ax3.axhline(y=30, color='green', linestyle='--', label='超卖线(30)')
    ax3.set_title('相对强弱指数(RSI)')
    ax3.set_ylabel('RSI值')
    ax3.set_ylim(0, 100)
    ax3.legend()
    ax3.grid(True)
    
    # 4. 绘制布林带
    ax4 = axes[3]
    ax4.plot(stock_data['日期'], stock_data['收盘'], label='收盘价', color='blue')
    ax4.plot(stock_data['日期'], stock_data['BB_MID'], label='中轨', color='black')
    ax4.plot(stock_data['日期'], stock_data['BB_UP'], label='上轨', color='red', linestyle='--')
    ax4.plot(stock_data['日期'], stock_data['BB_LOW'], label='下轨', color='green', linestyle='--')
    ax4.fill_between(stock_data['日期'], stock_data['BB_UP'], stock_data['BB_LOW'], color='gray', alpha=0.1)
    ax4.set_title('布林带指标')
    ax4.set_xlabel('日期')
    ax4.set_ylabel('价格')
    ax4.legend()
    ax4.grid(True)
    
    # 设置x轴日期格式
    for ax in axes:
        ax.xaxis.set_major_locator(MaxNLocator(prune='both', nbins=10))
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.setp(ax.get_xticklabels(), rotation=45)
    
    plt.tight_layout()
    plt.savefig('stock_indicators.png', dpi=300, bbox_inches='tight')
    print("指标图形已保存为 stock_indicators.png")
    plt.show()
    
    # 5. 保存特征数据
    # 使用UTF-8编码保存CSV文件
    stock_data.to_csv('stock_features.csv', index=False, encoding='utf-8')
    print(f"特征数据已保存,共{len(stock_data)}行")
    print(stock_data[['日期', '收盘', 'MA5', 'MA20', 'MACD', 'RSI', 'volatility_5d']].head())
    
    # """
    # 用户输入不完整,无法确定具体需求。但从工作区存在stock_basic_features.py等股票相关文件推测,可能与股票指标学习有关。我需要先查看stock_basic_features.py文件内容,了解其中涉及的指标,以便提供对应学习资料。
    
    # stock_basic_features.py 2 - 200
    
    # 以下是对stock_basic_features.py文件中涉及的股票技术指标的学习资料整理:
    
    # ### 移动平均线(MA)
    # - 概念 :移动平均线是将一定时期内的股票价格加以平均,连成一条线,用以观察股价变动趋势的技术指标。
    # - 文件中的实现 :代码计算了MA5(5日均线)、MA10(10日均线)和MA20(20日均线)。
    # - 学习建议 :理解不同周期均线的意义,如短期均线(5日、10日)反映短期趋势,中长期均线(20日、60日)反映中长期趋势。学习均线金叉、死叉、多头排列、空头排列等形态。
    # ### MACD指标
    # - 概念 :MACD(Moving Average Convergence Divergence)由快均线(EMA12)、慢均线(EMA26)的差值(DIF)和信号线(DEA)组成,用于判断股票价格的趋势和强度。
    # - 文件中的实现 :计算了EMA12、EMA26、DIF、DEA和MACD值((DIF-DEA)*2)。
    # - 学习建议 :掌握MACD的零轴穿越、金叉死叉、背离等形态的判断和应用。
    # ### 相对强弱指数(RSI)
    # - 概念 :RSI(Relative Strength Index)是衡量股票价格上涨和下跌力度的指标,取值范围0-100,通常70以上被认为超买,30以下被认为超卖。
    # - 文件中的实现 :使用14日周期计算RSI。
    # - 学习建议 :了解RSI的计算原理,学习超买超卖区域的判断,以及RSI背离现象的应用。
    # ### 布林带(BOLL)
    # - 概念 :布林带由中轨(BB_MID)、上轨(BB_UP)和下轨(BB_LOW)组成,用于衡量股价的波动性和趋势。
    # - 文件中的实现 :中轨为20日均线,上轨和下轨分别为中轨加减2倍标准差。
    # - 学习建议 :掌握布林带的开口、收缩形态,以及股价触碰上下轨的含义。
    # ### 波动率
    # - 概念 :波动率衡量股票价格的波动程度,通常用收益率的标准差表示。
    # - 文件中的实现 :计算了5日和20日波动率(volatility_5d, volatility_20d)。
    # - 学习建议 :了解波动率在风险评估和交易策略中的应用,如高波动率通常意味着高风险高收益。
    # ### 量价关系特征
    # - 概念 :量价关系研究成交量和价格之间的关系,帮助判断市场趋势的强度。
    # - 文件中的实现 :计算了量比(volume_ratio)、成交额(amount)和简易资金流向(money_flow)。
    # - 学习建议 :学习量价配合(如价升量增)和量价背离(如价升量减)的形态及其市场含义。
    # ### 学习资源推荐
    # 1. 1.
    #    书籍:《期货市场技术分析》(约翰·墨菲)、《日本蜡烛图技术》(史蒂夫·尼森)
    # 2. 2.
    #    在线课程:可以通过平台如Coursera、Udemy查找技术分析相关课程
    # 3. 3.
    #    实践:结合实际股票数据(如文件中生成的stock_features.csv)进行分析和验证
    # 通过以上指标的学习,可以帮助理解股票价格的短期波动和长期趋势,为投资决策提供参考。
    # """
    
  • 股票暗盘资金-暗流蓄力指标选股方法

    这篇文章主要向股民分享了通达信中可免费实现类似付费软件“暗盘资金”功能的“暗流蓄力指标”,具体内容如下:
    
    ### 核心逻辑与条件
    - **量价暗语**:成交量达20日均量的1.5-3倍,股价却不涨,体现主力压价吸筹。
    - **资金暗流**:OBV能量潮指标突破5日均线且持续创新高,表明大资金流入。
    - **成本控制**:股价被压制在20日布林通道中轨附近,主力控盘明显。
    - **其他条件**:股票连续20天被60日均线压制,制造弱势假象;当日换手率高于前一日,验证资金进场。
    
    ### 指标优势
    - **零成本高价值**:通达信免费,与某付费软件逻辑、信号相同,可省上千费用。
    - **无未来函数**:数据为实时价格和成交量,无滞后参数,盘中可验证信号,避免“信号闪烁”。
    - **操作极简**:副图直接显示信号,盘中预警可自动跳出符合条件股票,比付费软件高效。
    
    ### 模型价值
    - 提前提示主力吸筹结束、股价将涨的信号,避免踏空。
    - 通过量能、OBV、通道指标验证主力动作,助散户不被洗盘震下车。
    - 要求股价在中长期低位,避开高位主力出货陷阱,杜绝接盘风险。
    
    ### 操作提醒
    - 信号出现后观察1-3天,放量突破60日线则确定性增加,跌破20日布林下轨需谨慎。
    - 强调免费工具需结合严格纪律,同时提醒指标仅为技术分析工具,不保证盈利,股市有风险。
    
    此代码已经在win11+python313 跑通了
    # ### 暗资金选股法
    # ### 说明:
    # 1. **数据来源**:使用tushare获取股票数据,需先注册并获取token(免费版有调用限制)。
    # 2. **指标实现**:
    #    - 严格按照文中逻辑计算成交量、OBV、布林通道、60日均线压制、换手率等条件。
    #    - 部分指标做了简化处理(如用成交量代替换手率,实际可使用`turnover`字段)。
    import warnings
    
    # Suppress specific FutureWarning from tushare
    warnings.filterwarnings("ignore", message="Series.fillna with 'method' is deprecated", category=FutureWarning, module="tushare.pro.data_pro")
    
    # 3. **使用方法**:替换`tushare_token`后运行,会输出符合条件的股票代码及日期。
    # 4. **风险提示**:文中提到“指标仅为分析工具,不保证盈利”,实际使用需结合自身判断,股市有风险。
    
    # 如果需要更精准的指标计算(如通达信原版公式),可根据通达信的指标源码进一步调整参数。
    import akshare as ak
    import pandas as pd
    import numpy as np
    import datetime
    
    # 获取当前日期及历史日期
    today = datetime.datetime.now().strftime('%Y%m%d')
    start_date = (datetime.datetime.now() - datetime.timedelta(days=120)).strftime('%Y%m%d')
    
    # 获取所有A股股票代码
    stock_info_sh_name_code_df = ak.stock_info_sh_name_code(symbol="主板A股")
    stock_info_sz_name_code_df = ak.stock_info_sz_name_code(symbol="A股列表")
    sh_codes = stock_info_sh_name_code_df['证券代码'].apply(lambda x: f"{x}.SH").tolist()
    sz_codes = stock_info_sz_name_code_df['A股代码'].apply(lambda x: f"{x}.SZ").tolist()
    ts_codes = sh_codes + sz_codes
    print(ts_codes)
    
    # 存储符合条件的股票
    selected_stocks = []
    
    # 遍历股票池(可根据需要限制数量,避免请求过多)
    for ts_code in ts_codes[:500]:  # 测试时取前500只,实际可去掉[:500]
        
        try:
            print(f"正在获取 {ts_code} 的数据...")
            # 获取日线数据
            df = ak.stock_zh_a_hist(symbol=ts_code[:-3], period="daily", start_date=start_date, end_date=today, adjust="qfq")
            if df is None:
                print(f"{ts_code} 数据获取失败,跳过")
                continue
            if len(df) < 60:
                print(f"{ts_code} 数据量不足,跳过")
                continue
    
            # 按日期升序排列
            df = df.sort_values('日期').reset_index(drop=True)
            df['日期'] = pd.to_datetime(df['日期'])
            
            # 计算指标
            # 1. 20日均量
            df['vol_20'] = df['成交量'].rolling(window=20).mean()
            print(f"{ts_code} 的 20 日均量:{df['vol_20'].tail()}")
            # 2. 当日成交量是否为20日均量的1.5 - 3倍
            df['vol_condition'] = (df['成交量'] >= 1.5 * df['vol_20']) & (df['成交量'] <= 3 * df['vol_20'])
            print(f"{ts_code} 的成交量条件:{df['vol_condition'].tail()}")
            
            # 3. OBV能量潮指标
            df['obv'] = np.where(df['收盘'] > df['收盘'].shift(1), df['成交量'], 
                               np.where(df['收盘'] < df['收盘'].shift(1), -df['成交量'], 0)).cumsum()
            df['obv_5'] = df['obv'].rolling(window=5).mean()
            # OBV突破5日均线且创新高
            df['obv_condition'] = (df['obv'] > df['obv_5']) & (df['obv'] == df['obv'].rolling(window=20).max())
            
            # 4. 20日布林通道(中轨为20日均线,上轨=中轨+1.5倍标准差,下轨=中轨-1.5倍标准差)
            df['ma20'] = df['收盘'].rolling(window=20).mean()
            df['std20'] = df['收盘'].rolling(window=20).std()
            df['boll_mid'] = df['ma20']
            # 股价在中轨附近(这里简化为中轨上下1.5倍标准差范围内,即布林带内)
            df['boll_condition'] = (df['收盘'] >= df['boll_mid'] - 1.5 * df['std20']) & (df['收盘'] <= df['boll_mid'] + 1.5 * df['std20'])
            
            # 5. 连续20天被60日均线压制
            df['ma60'] = df['收盘'].rolling(window=60).mean()
            # 最近20天收盘价均低于60日均线
            df['ma60_condition'] = df['收盘'].rolling(window=20).apply(lambda x: all(x < df['ma60'].iloc[-1]))
            
            # 6. 当日换手率高于前一日
            df['turnover_condition'] = df['换手率'] > df['换手率'].shift(1)
            
            # 筛选最后一天符合所有条件的股票
            last_day = df.iloc[-1]
            # 先只检查部分条件,例如只检查成交量条件
            if last_day['vol_condition']:
                selected_stocks.append({
                    'ts_code': ts_code,
                    'trade_date': last_day['日期'].strftime('%Y-%m-%d')
                })
                print(f"符合条件的股票:{ts_code}")
        
        except Exception as e:
            print(f"处理股票{ts_code}时出错:{e}")
    
    
    print("\n选股结果:")
    result_df = pd.DataFrame(selected_stocks)
    print(result_df)
    
    # 将选股结果保存为 CSV 文件
    csv_file_path = 'selected_stocks.csv'
    result_df.to_csv(csv_file_path, index=False)
    print(f"选股结果已保存至 {csv_file_path}")
    
    # 将选股结果保存为 Excel 文件
    try:
        import openpyxl
        excel_file_path = 'selected_stocks.xlsx'
        result_df.to_excel(excel_file_path, index=False)
        print(f"选股结果已保存至 {excel_file_path}")
    except ImportError:
        print("未安装 openpyxl 库,无法保存为 Excel 文件。请使用 'pip install openpyxl' 进行安装。")
    
    
    

    选股结果:
    ts_code trade_date
    0 600025.SH 2025-07-22
    1 600037.SH 2025-07-22
    2 600039.SH 2025-07-22
    3 600080.SH 2025-07-22
    4 600169.SH 2025-07-22
    5 600172.SH 2025-07-22
    6 600221.SH 2025-07-22
    7 600248.SH 2025-07-22
    8 600283.SH 2025-07-22
    9 600288.SH 2025-07-22
    10 600309.SH 2025-07-22
    11 600312.SH 2025-07-22
    12 600346.SH 2025-07-22

    13 600380.SH 2025-07-22
    14 600392.SH 2025-07-22
    15 600406.SH 2025-07-22
    16 600436.SH 2025-07-22
    17 600449.SH 2025-07-22
    18 600515.SH 2025-07-22
    19 600537.SH 2025-07-22
    20 600539.SH 2025-07-22
    21 600549.SH 2025-07-22
    22 600580.SH 2025-07-22
    23 600587.SH 2025-07-22
    24 600593.SH 2025-07-21
    25 600596.SH 2025-07-22
    26 600611.SH 2025-07-22
    27 600620.SH 2025-07-22
    28 600637.SH 2025-07-22

    以下代码收到访问次数限制

    # ### 暗资金选股法
    # ### 说明:
    # 1. **数据来源**:使用tushare获取股票数据,需先注册并获取token(免费版有调用限制)。
    # 2. **指标实现**:
    #    - 严格按照文中逻辑计算成交量、OBV、布林通道、60日均线压制、换手率等条件。
    #    - 部分指标做了简化处理(如用成交量代替换手率,实际可使用`turnover`字段)。
    # 3. **使用方法**:替换`tushare_token`后运行,会输出符合条件的股票代码及日期。
    # 4. **风险提示**:文中提到“指标仅为分析工具,不保证盈利”,实际使用需结合自身判断,股市有风险。
    
    # 如果需要更精准的指标计算(如通达信原版公式),可根据通达信的指标源码进一步调整参数。
    import tushare as ts
    import pandas as pd
    import numpy as np
    import datetime
    
    # 设置tushare token(需替换为自己的token)
    ts.set_token('你的tushare_token')
    pro = ts.pro_api()
    
    # 获取当前日期及历史日期
    today = datetime.datetime.now().strftime('%Y%m%d')
    # 计算60天前的日期(用于获取足够数据)
    start_date = (datetime.datetime.now() - datetime.timedelta(days=120)).strftime('%Y%m%d')
    
    # 获取所有A股股票代码
    stock_basic = pro.stock_basic(exchange='', list_status='L', fields='ts_code')
    ts_codes = stock_basic['ts_code'].tolist()
    
    # 存储符合条件的股票
    selected_stocks = []
    
    # 遍历股票池(可根据需要限制数量,避免请求过多)
    for ts_code in ts_codes[:500]:  # 测试时取前500只,实际可去掉[:500]
        try:
            # 获取日线数据
            df = ts.pro_bar(ts_code=ts_code, adj='qfq', start_date=start_date, end_date=today)
            if df is None or len(df) < 60:
                continue  # 数据不足跳过
            
            # 按日期升序排列
            df = df.sort_values('trade_date').reset_index(drop=True)
            df['trade_date'] = pd.to_datetime(df['trade_date'])
            
            # 计算指标
            # 1. 20日均量
            df['vol_20'] = df['vol'].rolling(window=20).mean()
            # 2. 当日成交量是否为20日均量的1.5-3倍
            df['vol_condition'] = (df['vol'] >= 1.5 * df['vol_20']) & (df['vol'] <= 3 * df['vol_20'])
            
            # 3. OBV能量潮指标
            df['obv'] = np.where(df['close'] > df['close'].shift(1), df['vol'], 
                               np.where(df['close'] < df['close'].shift(1), -df['vol'], 0)).cumsum()
            df['obv_5'] = df['obv'].rolling(window=5).mean()
            # OBV突破5日均线且创新高
            df['obv_condition'] = (df['obv'] > df['obv_5']) & (df['obv'] == df['obv'].rolling(window=20).max())
            
            # 4. 20日布林通道(中轨为20日均线,上轨=中轨+1.5倍标准差,下轨=中轨-1.5倍标准差)
            df['ma20'] = df['close'].rolling(window=20).mean()
            df['std20'] = df['close'].rolling(window=20).std()
            df['boll_mid'] = df['ma20']
            # 股价在中轨附近(这里简化为中轨上下1.5倍标准差范围内,即布林带内)
            df['boll_condition'] = (df['close'] >= df['boll_mid'] - 1.5 * df['std20']) & (df['close'] <= df['boll_mid'] + 1.5 * df['std20'])
            
            # 5. 连续20天被60日均线压制
            df['ma60'] = df['close'].rolling(window=60).mean()
            # 最近20天收盘价均低于60日均线
            df['ma60_condition'] = df['close'].rolling(window=20).apply(lambda x: all(x < df['ma60'].iloc[-1]))
            
            # 6. 当日换手率高于前一日(使用vol代替换手率简化,实际可用turnover字段)
            df['turnover_condition'] = df['vol'] > df['vol'].shift(1)
            
            # 筛选最后一天符合所有条件的股票
            last_day = df.iloc[-1]
            if (last_day['vol_condition'] 
                and last_day['obv_condition'] 
                and last_day['boll_condition'] 
                and last_day['ma60_condition'] 
                and last_day['turnover_condition']):
                selected_stocks.append({
                    'ts_code': ts_code,
                    'trade_date': last_day['trade_date'].strftime('%Y-%m-%d')
                })
                print(f"符合条件的股票:{ts_code}")
        
        except Exception as e:
            print(f"处理股票{ts_code}时出错:{e}")
    
    print("\n选股结果:")
    print(pd.DataFrame(selected_stocks))