知行多空线指标接入选股与事件回测;大盘总览走 quicksync 统一入口;candles 批量 upsert 分批防 asyncpg 参数超限
This commit is contained in:
@@ -60,10 +60,10 @@ def _d(v) -> str | None:
|
|||||||
def _get_pro():
|
def _get_pro():
|
||||||
if not settings.tushare_token:
|
if not settings.tushare_token:
|
||||||
raise MarketOverviewError("未配置 TUSHARE_TOKEN,无法获取大盘行情(backend/.env)")
|
raise MarketOverviewError("未配置 TUSHARE_TOKEN,无法获取大盘行情(backend/.env)")
|
||||||
import tushare as ts
|
# 走统一入口:15000 积分档 token 只认 quicksync 镜像(直连 api.tushare.pro 会 40101)
|
||||||
|
from .tushare_provider import get_pro
|
||||||
|
|
||||||
ts.set_token(settings.tushare_token)
|
return get_pro()
|
||||||
return ts.pro_api()
|
|
||||||
|
|
||||||
|
|
||||||
def _fetch_index_sync(pro, ts_code: str) -> pd.DataFrame:
|
def _fetch_index_sync(pro, ts_code: str) -> pd.DataFrame:
|
||||||
|
|||||||
@@ -64,3 +64,13 @@ def ema2(close: pd.Series, span: int = 10) -> pd.Series:
|
|||||||
def avg_ma(close: pd.Series, periods: tuple[int, ...] = (10, 20, 30, 60)) -> pd.Series:
|
def avg_ma(close: pd.Series, periods: tuple[int, ...] = (10, 20, 30, 60)) -> pd.Series:
|
||||||
"""知行多空线:(MA(M1)+MA(M2)+MA(M3)+MA(M4))/4。"""
|
"""知行多空线:(MA(M1)+MA(M2)+MA(M3)+MA(M4))/4。"""
|
||||||
return sum(ma(close, p) for p in periods) / len(periods)
|
return sum(ma(close, p) for p in periods) / len(periods)
|
||||||
|
|
||||||
|
|
||||||
|
def zhixing_dkx(close: pd.Series, m1: int = 14, m2: int = 28, m3: int = 57, m4: int = 114) -> pd.Series:
|
||||||
|
"""知行多空线(通达信口径):(MA(M1)+MA(M2)+MA(M3)+MA(M4))/4,M1~M4 缺省 14/28/57/114。
|
||||||
|
|
||||||
|
MA 为严格窗口(窗口不满为 NaN,任一为空则多空线为空),与前端 zhixing.ts /
|
||||||
|
通达信原公式一致(前 m4-1 根无值);区别于 avg_ma 的 min_periods=1 渐进口径。
|
||||||
|
"""
|
||||||
|
mas = [close.rolling(p, min_periods=p).mean() for p in (m1, m2, m3, m4)]
|
||||||
|
return sum(mas) / len(mas)
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ def _boll(df: pd.DataFrame, p: dict) -> dict[str, pd.Series]:
|
|||||||
return {"boll_upper": out["upper"], "boll_mid": out["mid"], "boll_lower": out["lower"]}
|
return {"boll_upper": out["upper"], "boll_mid": out["mid"], "boll_lower": out["lower"]}
|
||||||
|
|
||||||
|
|
||||||
|
def _zhixing(df: pd.DataFrame, p: dict) -> dict[str, pd.Series]:
|
||||||
|
dk = ind.zhixing_dkx(df["close"], m1=int(p["m1"]), m2=int(p["m2"]),
|
||||||
|
m3=int(p["m3"]), m4=int(p["m4"]))
|
||||||
|
return {"zhixing_dkx": dk, "zhixing_trend": ind.ema2(df["close"])}
|
||||||
|
|
||||||
|
|
||||||
def _close(df: pd.DataFrame, p: dict) -> dict[str, pd.Series]:
|
def _close(df: pd.DataFrame, p: dict) -> dict[str, pd.Series]:
|
||||||
return {"close": df["close"]}
|
return {"close": df["close"]}
|
||||||
|
|
||||||
@@ -82,6 +88,8 @@ FAMILIES: dict[str, FamilyDef] = {
|
|||||||
"rsi": FamilyDef("RSI", ("period",), {"period": 14}, 25, _rsi),
|
"rsi": FamilyDef("RSI", ("period",), {"period": 14}, 25, _rsi),
|
||||||
"ma": FamilyDef("MA", ("period",), {"period": 20}, 25, _ma),
|
"ma": FamilyDef("MA", ("period",), {"period": 20}, 25, _ma),
|
||||||
"boll": FamilyDef("BOLL", ("period", "std"), {"period": 20, "std": 2}, 25, _boll),
|
"boll": FamilyDef("BOLL", ("period", "std"), {"period": 20, "std": 2}, 25, _boll),
|
||||||
|
"zhixing": FamilyDef("知行", ("m1", "m2", "m3", "m4"),
|
||||||
|
{"m1": 14, "m2": 28, "m3": 57, "m4": 114}, 114, _zhixing),
|
||||||
"close": FamilyDef("收盘价", (), {}, 1, _close),
|
"close": FamilyDef("收盘价", (), {}, 1, _close),
|
||||||
"pct_chg": FamilyDef("日涨跌幅", (), {}, 1, _pct_chg),
|
"pct_chg": FamilyDef("日涨跌幅", (), {}, 1, _pct_chg),
|
||||||
}
|
}
|
||||||
@@ -91,12 +99,14 @@ INDICATOR_FAMILY: dict[str, str] = {
|
|||||||
"macd_dif": "macd", "macd_dea": "macd", "macd_hist": "macd",
|
"macd_dif": "macd", "macd_dea": "macd", "macd_hist": "macd",
|
||||||
"rsi": "rsi", "ma": "ma",
|
"rsi": "rsi", "ma": "ma",
|
||||||
"boll_upper": "boll", "boll_mid": "boll", "boll_lower": "boll",
|
"boll_upper": "boll", "boll_mid": "boll", "boll_lower": "boll",
|
||||||
|
"zhixing_dkx": "zhixing", "zhixing_trend": "zhixing",
|
||||||
"close": "close", "pct_chg": "pct_chg",
|
"close": "close", "pct_chg": "pct_chg",
|
||||||
}
|
}
|
||||||
|
|
||||||
_IND_SUFFIX = {"kdj_k": "K", "kdj_d": "D", "kdj_j": "J",
|
_IND_SUFFIX = {"kdj_k": "K", "kdj_d": "D", "kdj_j": "J",
|
||||||
"macd_dif": "DIF", "macd_dea": "DEA", "macd_hist": "柱",
|
"macd_dif": "DIF", "macd_dea": "DEA", "macd_hist": "柱",
|
||||||
"boll_upper": "上轨", "boll_mid": "中轨", "boll_lower": "下轨"}
|
"boll_upper": "上轨", "boll_mid": "中轨", "boll_lower": "下轨",
|
||||||
|
"zhixing_dkx": "多空线", "zhixing_trend": "趋势线"}
|
||||||
|
|
||||||
|
|
||||||
def _family_of(indicator: str) -> str:
|
def _family_of(indicator: str) -> str:
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ SYSTEM_PROMPT = """你是 A 股选股条件解析器。把用户的自然语言
|
|||||||
(indicator 与 snapshot 至少一个非空;用户没有提到的条件不要编造)
|
(indicator 与 snapshot 至少一个非空;用户没有提到的条件不要编造)
|
||||||
|
|
||||||
【indicator 数组】技术指标条件,元素字段:
|
【indicator 数组】技术指标条件,元素字段:
|
||||||
- "indicator": 指标名,白名单:kdj_k / kdj_d / kdj_j(KDJ 的 K/D/J 值)、rsi、macd_dif / macd_dea / macd_hist(MACD 的 DIF/DEA/柱)、ma(收盘价均线)、boll_upper / boll_mid / boll_lower(布林轨道)、close(收盘价)、pct_chg(日涨跌幅%)
|
- "indicator": 指标名,白名单:kdj_k / kdj_d / kdj_j(KDJ 的 K/D/J 值)、rsi、macd_dif / macd_dea / macd_hist(MACD 的 DIF/DEA/柱)、ma(收盘价均线)、boll_upper / boll_mid / boll_lower(布林轨道)、zhixing_dkx(知行多空线,四条收盘价均线的均值)、zhixing_trend(知行短期趋势线)、close(收盘价)、pct_chg(日涨跌幅%)
|
||||||
- "params": 指标参数(可选),默认:KDJ {"n":9,"m1":3,"m2":3};RSI {"period":14};MACD {"fast":12,"slow":26,"signal":9};MA {"period":20};BOLL {"period":20,"std":2}
|
- "params": 指标参数(可选),默认:KDJ {"n":9,"m1":3,"m2":3};RSI {"period":14};MACD {"fast":12,"slow":26,"signal":9};MA {"period":20};BOLL {"period":20,"std":2};知行多空线 {"m1":14,"m2":28,"m3":57,"m4":114}
|
||||||
- "op": "gt" | "ge" | "lt" | "le" | "between"
|
- "op": "gt" | "ge" | "lt" | "le" | "between"
|
||||||
- "value": 比较数值(between 时为下界),"value2": between 上界
|
- "value": 比较数值(between 时为下界),"value2": between 上界
|
||||||
- "value_indicator": 可选。指标与指标比较时填另一指标名(同白名单),如 "DIF大于DEA" -> indicator=macd_dif, op=gt, value_indicator=macd_dea, value=0;"股价在布林带下轨之下" -> indicator=close, op=lt, value_indicator=boll_lower, value=0
|
- "value_indicator": 可选。指标与指标比较时填另一指标名(同白名单),如 "DIF大于DEA" -> indicator=macd_dif, op=gt, value_indicator=macd_dea, value=0;"股价在布林带下轨之下" -> indicator=close, op=lt, value_indicator=boll_lower, value=0
|
||||||
@@ -53,7 +53,11 @@ SYSTEM_PROMPT = """你是 A 股选股条件解析器。把用户的自然语言
|
|||||||
|
|
||||||
示例3:
|
示例3:
|
||||||
输入:近 5 天曾经 MACD 金叉(DIF 上穿 DEA),换手率大于 5%,流通市值小于 100 亿
|
输入:近 5 天曾经 MACD 金叉(DIF 上穿 DEA),换手率大于 5%,流通市值小于 100 亿
|
||||||
输出:{"indicator":[{"indicator":"macd_dif","params":{"fast":12,"slow":26,"signal":9},"op":"gt","value":0,"value_indicator":"macd_dea","lookback":5,"match":"any"}],"snapshot":[{"field":"turnover_rate","op":"gt","value":5},{"field":"circ_mv","op":"lt","value":100}],"exclude_st":true,"exclude_delisted":true,"exclude_bj":true}"""
|
输出:{"indicator":[{"indicator":"macd_dif","params":{"fast":12,"slow":26,"signal":9},"op":"gt","value":0,"value_indicator":"macd_dea","lookback":5,"match":"any"}],"snapshot":[{"field":"turnover_rate","op":"gt","value":5},{"field":"circ_mv","op":"lt","value":100}],"exclude_st":true,"exclude_delisted":true,"exclude_bj":true}
|
||||||
|
|
||||||
|
示例4:
|
||||||
|
输入:近一个月股价曾经站上知行多空线的股票
|
||||||
|
输出:{"indicator":[{"indicator":"close","op":"gt","value":0,"value_indicator":"zhixing_dkx","params":{"m1":14,"m2":28,"m3":57,"m4":114},"lookback":20,"match":"any"}],"exclude_st":true,"exclude_delisted":true,"exclude_bj":true}"""
|
||||||
|
|
||||||
|
|
||||||
class ScreenerError(RuntimeError):
|
class ScreenerError(RuntimeError):
|
||||||
@@ -159,8 +163,8 @@ EVENT_SYSTEM_PROMPT = """你是 A 股事件回测参数解析器。用户描述
|
|||||||
{"entry": {"indicator": [...], "snapshot": [], "exclude_st": true, "exclude_delisted": true, "exclude_bj": true}, "entry_timing": "next_open", "holding_days": 3, "exit_timing": "close"}
|
{"entry": {"indicator": [...], "snapshot": [], "exclude_st": true, "exclude_delisted": true, "exclude_bj": true}, "entry_timing": "next_open", "holding_days": 3, "exit_timing": "close"}
|
||||||
|
|
||||||
【entry.indicator 数组】入场信号条件(必填,至少 1 条),元素字段与白名单:
|
【entry.indicator 数组】入场信号条件(必填,至少 1 条),元素字段与白名单:
|
||||||
- "indicator": kdj_k / kdj_d / kdj_j(KDJ 的 K/D/J 值)、rsi、macd_dif / macd_dea / macd_hist(MACD 的 DIF/DEA/柱)、ma(收盘价均线)、boll_upper / boll_mid / boll_lower(布林轨道)、close(收盘价)、pct_chg(日涨跌幅%)
|
- "indicator": kdj_k / kdj_d / kdj_j(KDJ 的 K/D/J 值)、rsi、macd_dif / macd_dea / macd_hist(MACD 的 DIF/DEA/柱)、ma(收盘价均线)、boll_upper / boll_mid / boll_lower(布林轨道)、zhixing_dkx(知行多空线)、zhixing_trend(知行短期趋势线)、close(收盘价)、pct_chg(日涨跌幅%)
|
||||||
- "params": 指标参数(可选),默认:KDJ {"n":9,"m1":3,"m2":3};RSI {"period":14};MACD {"fast":12,"slow":26,"signal":9};MA {"period":20};BOLL {"period":20,"std":2}
|
- "params": 指标参数(可选),默认:KDJ {"n":9,"m1":3,"m2":3};RSI {"period":14};MACD {"fast":12,"slow":26,"signal":9};MA {"period":20};BOLL {"period":20,"std":2};知行多空线 {"m1":14,"m2":28,"m3":57,"m4":114}
|
||||||
- "op": "gt" | "ge" | "lt" | "le" | "between";"value"(between 时为下界)、"value2"(上界)
|
- "op": "gt" | "ge" | "lt" | "le" | "between";"value"(between 时为下界)、"value2"(上界)
|
||||||
- "value_indicator": 指标与指标比较时填另一指标名(同白名单),如 "DIF 大于 DEA" -> indicator=macd_dif, op=gt, value_indicator=macd_dea, value=0
|
- "value_indicator": 指标与指标比较时填另一指标名(同白名单),如 "DIF 大于 DEA" -> indicator=macd_dif, op=gt, value_indicator=macd_dea, value=0
|
||||||
- "value_params": 比较对象指标参数不同时指定,如 "MA5 上穿 MA20" -> indicator=ma, params={"period":5}, op=gt, value_indicator=ma, value_params={"period":20}, value=0
|
- "value_params": 比较对象指标参数不同时指定,如 "MA5 上穿 MA20" -> indicator=ma, params={"period":5}, op=gt, value_indicator=ma, value_params={"period":20}, value=0
|
||||||
|
|||||||
@@ -245,18 +245,21 @@ async def _upsert_candle_day(session: AsyncSession, rows: list[dict], listed: se
|
|||||||
]
|
]
|
||||||
if not batch:
|
if not batch:
|
||||||
return
|
return
|
||||||
stmt = pg_insert(Candle).values(batch)
|
# on_conflict 语句整批渲染为占位符(非 executemany),asyncpg 单语句参数上限 32766,
|
||||||
stmt = stmt.on_conflict_do_update(
|
# 10 列 x 3000 行 = 30000 参数留出余量
|
||||||
index_elements=["symbol", "timeframe", "ts"],
|
for i in range(0, len(batch), 3000):
|
||||||
set_={
|
stmt = pg_insert(Candle).values(batch[i : i + 3000])
|
||||||
"open": stmt.excluded.open, "high": stmt.excluded.high,
|
stmt = stmt.on_conflict_do_update(
|
||||||
"low": stmt.excluded.low, "close": stmt.excluded.close,
|
index_elements=["symbol", "timeframe", "ts"],
|
||||||
"volume": stmt.excluded.volume,
|
set_={
|
||||||
"amount": func.coalesce(Candle.amount, stmt.excluded.amount),
|
"open": stmt.excluded.open, "high": stmt.excluded.high,
|
||||||
},
|
"low": stmt.excluded.low, "close": stmt.excluded.close,
|
||||||
)
|
"volume": stmt.excluded.volume,
|
||||||
await session.execute(stmt)
|
"amount": func.coalesce(Candle.amount, stmt.excluded.amount),
|
||||||
await session.commit()
|
},
|
||||||
|
)
|
||||||
|
await session.execute(stmt)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def _run_sync(days: int, force: bool) -> None:
|
async def _run_sync(days: int, force: bool) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user