Files
python/limit_up.py
T
2026-04-23 23:04:11 +08:00

126 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)