修正逻辑
This commit is contained in:
+2
-2
@@ -132,8 +132,8 @@ def run(start_date, end_date, token):
|
|||||||
# 5. 程序入口(只改这里)
|
# 5. 程序入口(只改这里)
|
||||||
# =======================
|
# =======================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
START_DATE = "20240424"
|
START_DATE = "20240427"
|
||||||
END_DATE = "20260424"
|
END_DATE = "20260427"
|
||||||
|
|
||||||
TUSHARE_TOKEN = "0fad3cf498757089e2630028455d5bbe1637475788bcdaa1f2175e93"
|
TUSHARE_TOKEN = "0fad3cf498757089e2630028455d5bbe1637475788bcdaa1f2175e93"
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ def run(start_date, end_date):
|
|||||||
|
|
||||||
# ===== 输入参数 =====
|
# ===== 输入参数 =====
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
START_DATE = "20260424"
|
START_DATE = "20260427"
|
||||||
END_DATE = "20260424"
|
END_DATE = "20260427"
|
||||||
|
|
||||||
run(START_DATE, END_DATE)
|
run(START_DATE, END_DATE)
|
||||||
+39
-31
@@ -1,16 +1,11 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
from sqlalchemy import create_engine, text
|
from sqlalchemy import create_engine, text
|
||||||
|
|
||||||
# --- 数据库配置 ---
|
|
||||||
# 请根据实际情况修改数据库连接信息
|
|
||||||
# DB_CONN_STR = 'mysql+pymysql://root:password@127.0.0.1:3306/stock_data'
|
|
||||||
|
|
||||||
|
|
||||||
def init_db(engine):
|
def init_db(engine):
|
||||||
"""
|
"""
|
||||||
初始化数据库表结构
|
初始化数据库表结构
|
||||||
1. 增加 symbol 列,位于 trade_date 之后
|
|
||||||
2. 建立 UNIQUE KEY 防止物理重复写入
|
|
||||||
"""
|
"""
|
||||||
create_table_sql = """
|
create_table_sql = """
|
||||||
CREATE TABLE IF NOT EXISTS `stock_limit_data` (
|
CREATE TABLE IF NOT EXISTS `stock_limit_data` (
|
||||||
@@ -34,6 +29,7 @@ def analyze_and_save_limits(start_date, end_date):
|
|||||||
"""
|
"""
|
||||||
计算并在数据库中落地涨停、炸板详情
|
计算并在数据库中落地涨停、炸板详情
|
||||||
"""
|
"""
|
||||||
|
# 数据库连接配置
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
"mysql+pymysql://yangfan@8.159.129.156:10836/level?charset=utf8mb4",
|
"mysql+pymysql://yangfan@8.159.129.156:10836/level?charset=utf8mb4",
|
||||||
connect_args={
|
connect_args={
|
||||||
@@ -42,40 +38,61 @@ def analyze_and_save_limits(start_date, end_date):
|
|||||||
pool_pre_ping=True
|
pool_pre_ping=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# 0. 初始化检查表结构
|
|
||||||
init_db(engine)
|
init_db(engine)
|
||||||
|
|
||||||
# 1. 获取基础数据和行情数据
|
# 1. 获取行情数据
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT
|
SELECT
|
||||||
d.trade_date, d.symbol, d.ts_code, b.name,
|
d.trade_date, d.symbol, d.ts_code, b.name,
|
||||||
d.high, d.close, d.pre_close, b.market
|
d.high, d.close, d.pre_close, b.market
|
||||||
FROM stock_daily d
|
FROM stock_daily d
|
||||||
JOIN stock_basic b ON d.ts_code = b.ts_code
|
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.trade_date <= '{end_date}'
|
||||||
|
AND d.pre_close IS NOT NULL
|
||||||
ORDER BY d.trade_date ASC
|
ORDER BY d.trade_date ASC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
df = pd.read_sql(query, engine)
|
df = pd.read_sql(query, engine)
|
||||||
if df.empty:
|
if df.empty:
|
||||||
print(f"⚠️ 在指定区间 ({start_date} -> {end_date}] 内未获取到数据。")
|
print(f"⚠️ 区间 [{start_date} , {end_date}] 内未获取到数据。")
|
||||||
return
|
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):
|
def calc_limit(row):
|
||||||
m, p = str(row['market']), float(row['pre_close'])
|
m = str(row['market'])
|
||||||
# 主板10%,创业板/科创板20%,北交所30%
|
p = row['pre_close']
|
||||||
ratio = 1.2 if ('创业板' in m or '科创板' in m) else (1.3 if '北交所' in m else 1.1)
|
|
||||||
|
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)
|
return round(p * ratio, 2)
|
||||||
|
|
||||||
df['limit_price'] = df.apply(calc_limit, axis=1)
|
df['limit_price'] = df.apply(calc_limit, axis=1)
|
||||||
|
|
||||||
# 3. 筛选涨停(1)和炸板(2)
|
# 3. 筛选逻辑:利用 abs 差值判断,解决浮点数精度问题
|
||||||
df['is_limit'] = (df['high'] == df['limit_price']) & (df['close'] == df['limit_price'])
|
# 只要最高价触及理论涨停价(误差0.01以内)即视为触板
|
||||||
df['is_broken'] = (df['high'] == df['limit_price']) & (df['close'] < df['limit_price'])
|
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 = df[df['is_limit']].copy()
|
||||||
limit_df['status'] = 1
|
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])
|
save_df = pd.concat([limit_df, broken_df])
|
||||||
|
|
||||||
if not save_df.empty:
|
if not save_df.empty:
|
||||||
# 4. 字段清洗与对齐
|
|
||||||
final_df = save_df[[
|
final_df = save_df[[
|
||||||
'trade_date', 'symbol', 'ts_code', 'name',
|
'trade_date', 'symbol', 'ts_code', 'name',
|
||||||
'status', 'limit_price', 'close', 'market'
|
'status', 'limit_price', 'close', 'market'
|
||||||
]]
|
]]
|
||||||
|
|
||||||
# 5. 【核心防重逻辑】:开启事务,先删后插
|
# 4. 事务入库:先删后插
|
||||||
with engine.begin() as conn:
|
with engine.begin() as conn:
|
||||||
# 获取待写入的所有交易日期
|
|
||||||
target_dates = final_df['trade_date'].astype(str).unique()
|
target_dates = final_df['trade_date'].astype(str).unique()
|
||||||
date_list = ", ".join([f"'{d}'" for d in target_dates])
|
date_list = ", ".join([f"'{d}'" for d in target_dates])
|
||||||
|
|
||||||
# 第一层逻辑防重:删除数据库中已存在的相同日期数据
|
|
||||||
delete_query = f"DELETE FROM stock_limit_data WHERE trade_date IN ({date_list})"
|
delete_query = f"DELETE FROM stock_limit_data WHERE trade_date IN ({date_list})"
|
||||||
conn.execute(text(delete_query))
|
conn.execute(text(delete_query))
|
||||||
|
|
||||||
# 第二层物理防重:通过 to_sql 写入,若触发 Unique Key 冲突会抛出异常回滚
|
|
||||||
final_df.to_sql('stock_limit_data', conn, if_exists='append', index=False)
|
final_df.to_sql('stock_limit_data', conn, if_exists='append', index=False)
|
||||||
|
|
||||||
print(f"✅ 处理成功!涉及交易日: {len(target_dates)} 天")
|
print(f"✅ 处理成功!涉及交易日: {len(target_dates)} 天")
|
||||||
print(f"📊 总计入库: {len(final_df)} 条记录(已自动去重并覆盖历史数据)。")
|
print(f"📊 记录详情: 总计 {len(final_df)} 条 (涨停: {len(limit_df)} / 炸板: {len(broken_df)})")
|
||||||
else:
|
else:
|
||||||
print("ℹ️ 该时段内全市场无涨停或炸板记录。")
|
print("ℹ️ 该时段内全市场无涨停或炸板记录。")
|
||||||
|
|
||||||
@@ -113,14 +126,9 @@ def analyze_and_save_limits(start_date, end_date):
|
|||||||
print(f"❌ 运行失败: {e}")
|
print(f"❌ 运行失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 用户配置区
|
|
||||||
# ============================================================
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 开始日期:计算从该日期的下一个交易日开始
|
# 配置日期区间
|
||||||
START_DATE = '2026-04-23'
|
START_DATE = '2024-09-23'
|
||||||
|
END_DATE = '2026-04-27'
|
||||||
# 结束日期
|
|
||||||
END_DATE = '2026-04-24'
|
|
||||||
|
|
||||||
analyze_and_save_limits(START_DATE, END_DATE)
|
analyze_and_save_limits(START_DATE, END_DATE)
|
||||||
Reference in New Issue
Block a user