diff --git a/factors.py b/factors.py index 3bd5f8b..926f14f 100644 --- a/factors.py +++ b/factors.py @@ -93,24 +93,20 @@ def build_upside(start, end): cons = cons[cons["k"].isin(set(price["k"]))] if cons.empty: return _EMPTY - cons["asof_date"] = pd.to_datetime(cons["asof_date"]) - price["trade_date"] = pd.to_datetime(price["trade_date"]) - out = [] - cons_sorted = cons.sort_values("asof_date") - for k, pg in price.groupby("k"): - cg = cons_sorted[cons_sorted["k"] == k] - if cg.empty: - continue - m = pd.merge_asof(pg.sort_values("trade_date"), - cg[["asof_date", "target_mid_avg"]], - left_on="trade_date", right_on="asof_date", direction="backward") - m = m.dropna(subset=["target_mid_avg"]) - if m.empty: - continue - m["factor_value"] = m["target_mid_avg"].astype(float) / m["close"] - 1.0 - m["stock_code"] = k - out.append(m[["trade_date", "stock_code", "factor_value"]]) - return pd.concat(out) if out else _EMPTY + # 强制两侧键同分辨率 datetime64[ns](pandas 2.x 不同来源可能 us/ns 混, + # merge_asof 会报 incompatible merge keys) + cons["asof_date"] = pd.to_datetime(cons["asof_date"]).astype("datetime64[ns]") + price["trade_date"] = pd.to_datetime(price["trade_date"]).astype("datetime64[ns]") + left = price[["trade_date", "k", "close"]].sort_values("trade_date") + right = cons[["asof_date", "k", "target_mid_avg"]].sort_values("asof_date") + m = pd.merge_asof(left, right, left_on="trade_date", right_on="asof_date", + by="k", direction="backward") # 每股取 asof<=当日最新目标价 + m = m.dropna(subset=["target_mid_avg", "close"]) + if m.empty: + return _EMPTY + m["factor_value"] = m["target_mid_avg"].astype(float) / m["close"] - 1.0 + m = m.rename(columns={"k": "stock_code"}) + return m[["trade_date", "stock_code", "factor_value"]] # ---------------------------------------------------------------- 事件 @@ -132,6 +128,8 @@ def build_event(start, end): ev = ev[ev["ts_code"].isin(uni)].copy() if ev.empty: return _EMPTY + ev["event_type"] = ev["event_type"].fillna("") + ev["direction"] = ev["direction"].fillna("") # 多数事件无 direction(NULL→NaN),先填空防 .strip 崩 ev["pol"] = [_polarity(t, d) for t, d in zip(ev["event_type"], ev["direction"])] ev = ev[ev["pol"] != 0.0] if ev.empty: