涨停板、炸板

This commit is contained in:
2026-04-23 23:04:11 +08:00
parent 6a33d49bc6
commit 8827ba8cd6
+126
View File
@@ -0,0 +1,126 @@
import pandas as pd
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` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`trade_date` DATE NOT NULL COMMENT '交易日期',
`symbol` VARCHAR(10) NOT NULL COMMENT '股票代码',
`ts_code` VARCHAR(20) NOT NULL COMMENT 'TS股票代码',
`name` VARCHAR(50) NOT NULL COMMENT '股票名称',
`status` TINYINT NOT NULL COMMENT '状态:1-涨停,2-炸板',
`limit_price` DECIMAL(16, 4) COMMENT '计算出的理论涨停价',
`close` DECIMAL(16, 4) COMMENT '实际收盘价',
`market` VARCHAR(20) COMMENT '所属板块',
UNIQUE KEY `uniq_date_code` (`trade_date`, `ts_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
with engine.begin() as conn:
conn.execute(text(create_table_sql))
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={
"password": "aA%8023321088"
},
pool_pre_ping=True
)
# 0. 初始化检查表结构
init_db(engine)
# 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}'
AND d.trade_date <= '{end_date}'
ORDER BY d.trade_date ASC
"""
try:
df = pd.read_sql(query, engine)
if df.empty:
print(f"⚠️ 在指定区间 ({start_date} -> {end_date}] 内未获取到数据。")
return
# 2. 涨停价计算规则
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)
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'])
limit_df = df[df['is_limit']].copy()
limit_df['status'] = 1
broken_df = df[df['is_broken']].copy()
broken_df['status'] = 2
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. 【核心防重逻辑】:开启事务,先删后插
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)} 条记录(已自动去重并覆盖历史数据)。")
else:
print("ℹ️ 该时段内全市场无涨停或炸板记录。")
except Exception as e:
print(f"❌ 运行失败: {e}")
# ============================================================
# 用户配置区
# ============================================================
if __name__ == "__main__":
# 开始日期:计算从该日期的下一个交易日开始
START_DATE = '2026-04-01'
# 结束日期
END_DATE = '2026-04-22'
analyze_and_save_limits(START_DATE, END_DATE)