From 352be501078d9d5abdf00fb047fde5d3fcac3f7c Mon Sep 17 00:00:00 2001 From: mianyang <784310601@qq.com> Date: Mon, 27 Apr 2026 21:50:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cal_date.py | 4 +-- daily.py | 4 +-- limit_up.py | 70 +++++++++++++++++++++++++++++------------------------ 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/cal_date.py b/cal_date.py index 9317a88..8d594d0 100644 --- a/cal_date.py +++ b/cal_date.py @@ -132,8 +132,8 @@ def run(start_date, end_date, token): # 5. 程序入口(只改这里) # ======================= if __name__ == "__main__": - START_DATE = "20240424" - END_DATE = "20260424" + START_DATE = "20240427" + END_DATE = "20260427" TUSHARE_TOKEN = "0fad3cf498757089e2630028455d5bbe1637475788bcdaa1f2175e93" diff --git a/daily.py b/daily.py index feb2a3f..72a5158 100644 --- a/daily.py +++ b/daily.py @@ -144,7 +144,7 @@ def run(start_date, end_date): # ===== 输入参数 ===== if __name__ == "__main__": - START_DATE = "20260424" - END_DATE = "20260424" + START_DATE = "20260427" + END_DATE = "20260427" run(START_DATE, END_DATE) \ No newline at end of file diff --git a/limit_up.py b/limit_up.py index c36fbda..1df5fb2 100644 --- a/limit_up.py +++ b/limit_up.py @@ -1,16 +1,11 @@ import pandas as pd +import numpy as np from sqlalchemy import create_engine, text -# --- 数据库配置 --- -# 请根据实际情况修改数据库连接信息 -# DB_CONN_STR = 'mysql+pymysql://root:password@127.0.0.1:3306/stock_data' - def init_db(engine): """ 初始化数据库表结构 - 1. 增加 symbol 列,位于 trade_date 之后 - 2. 建立 UNIQUE KEY 防止物理重复写入 """ create_table_sql = """ CREATE TABLE IF NOT EXISTS `stock_limit_data` ( @@ -34,6 +29,7 @@ def analyze_and_save_limits(start_date, end_date): """ 计算并在数据库中落地涨停、炸板详情 """ + # 数据库连接配置 engine = create_engine( "mysql+pymysql://yangfan@8.159.129.156:10836/level?charset=utf8mb4", connect_args={ @@ -42,40 +38,61 @@ def analyze_and_save_limits(start_date, end_date): pool_pre_ping=True ) - # 0. 初始化检查表结构 init_db(engine) - # 1. 获取基础数据和行情数据 + # 1. 获取行情数据 query = f""" SELECT d.trade_date, d.symbol, d.ts_code, b.name, d.high, d.close, d.pre_close, b.market FROM stock_daily d JOIN stock_basic b ON d.ts_code = b.ts_code - WHERE d.trade_date > '{start_date}' + WHERE d.trade_date >= '{start_date}' AND d.trade_date <= '{end_date}' + AND d.pre_close IS NOT NULL ORDER BY d.trade_date ASC """ try: df = pd.read_sql(query, engine) if df.empty: - print(f"⚠️ 在指定区间 ({start_date} -> {end_date}] 内未获取到数据。") + print(f"⚠️ 区间 [{start_date} , {end_date}] 内未获取到数据。") return - # 2. 涨停价计算规则 + # 强制类型转换 + df['pre_close'] = pd.to_numeric(df['pre_close'], errors='coerce') + df['high'] = pd.to_numeric(df['high'], errors='coerce') + df['close'] = pd.to_numeric(df['close'], errors='coerce') + + # 2. 涨停价计算规则 (已取消 ST 判定) def calc_limit(row): - m, p = str(row['market']), float(row['pre_close']) - # 主板10%,创业板/科创板20%,北交所30% - ratio = 1.2 if ('创业板' in m or '科创板' in m) else (1.3 if '北交所' in m else 1.1) + m = str(row['market']) + p = row['pre_close'] + + if pd.isna(p) or p <= 0: return 0.0 + + # 仅保留板块判定 + if '创业板' in m or '科创板' in m: + ratio = 1.2 + elif '北交所' in m: + ratio = 1.3 + else: + ratio = 1.1 # 普通主板(含ST股现在也按10%计算) + + # 采用标准四舍五入 return round(p * ratio, 2) df['limit_price'] = df.apply(calc_limit, axis=1) - # 3. 筛选涨停(1)和炸板(2) - df['is_limit'] = (df['high'] == df['limit_price']) & (df['close'] == df['limit_price']) - df['is_broken'] = (df['high'] == df['limit_price']) & (df['close'] < df['limit_price']) + # 3. 筛选逻辑:利用 abs 差值判断,解决浮点数精度问题 + # 只要最高价触及理论涨停价(误差0.01以内)即视为触板 + is_at_limit = (df['high'] - df['limit_price']).abs() < 0.011 + is_close_at_limit = (df['close'] - df['limit_price']).abs() < 0.011 + df['is_limit'] = is_at_limit & is_close_at_limit + df['is_broken'] = is_at_limit & (~is_close_at_limit) + + # 分离结果 limit_df = df[df['is_limit']].copy() limit_df['status'] = 1 @@ -85,27 +102,23 @@ def analyze_and_save_limits(start_date, end_date): save_df = pd.concat([limit_df, broken_df]) if not save_df.empty: - # 4. 字段清洗与对齐 final_df = save_df[[ 'trade_date', 'symbol', 'ts_code', 'name', 'status', 'limit_price', 'close', 'market' ]] - # 5. 【核心防重逻辑】:开启事务,先删后插 + # 4. 事务入库:先删后插 with engine.begin() as conn: - # 获取待写入的所有交易日期 target_dates = final_df['trade_date'].astype(str).unique() date_list = ", ".join([f"'{d}'" for d in target_dates]) - # 第一层逻辑防重:删除数据库中已存在的相同日期数据 delete_query = f"DELETE FROM stock_limit_data WHERE trade_date IN ({date_list})" conn.execute(text(delete_query)) - # 第二层物理防重:通过 to_sql 写入,若触发 Unique Key 冲突会抛出异常回滚 final_df.to_sql('stock_limit_data', conn, if_exists='append', index=False) print(f"✅ 处理成功!涉及交易日: {len(target_dates)} 天") - print(f"📊 总计入库: {len(final_df)} 条记录(已自动去重并覆盖历史数据)。") + print(f"📊 记录详情: 总计 {len(final_df)} 条 (涨停: {len(limit_df)} / 炸板: {len(broken_df)})") else: print("ℹ️ 该时段内全市场无涨停或炸板记录。") @@ -113,14 +126,9 @@ def analyze_and_save_limits(start_date, end_date): print(f"❌ 运行失败: {e}") -# ============================================================ -# 用户配置区 -# ============================================================ if __name__ == "__main__": - # 开始日期:计算从该日期的下一个交易日开始 - START_DATE = '2026-04-23' - - # 结束日期 - END_DATE = '2026-04-24' + # 配置日期区间 + START_DATE = '2024-09-23' + END_DATE = '2026-04-27' analyze_and_save_limits(START_DATE, END_DATE) \ No newline at end of file