etf-flow 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
etf_flow/__init__.py ADDED
@@ -0,0 +1,172 @@
1
+ """ETF Flow - 高性能 ETF 资金流动跟踪、成分股被动买盘测算与利好选股推荐量化库.
2
+
3
+ 统一顶层模块导出 (Python Library Public API).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ # 1. 标的池与分类 (Universe)
9
+ from etf_flow.universe import (
10
+ ETFMeta,
11
+ TRACKED_ETFS,
12
+ ETF_MAP,
13
+ get_tracked_codes,
14
+ get_dynamic_etf_universe,
15
+ get_sector_etf_mapping,
16
+ classify_etf,
17
+ )
18
+
19
+ # 2. 每日 PCF 100% 成分股穿透 (PCF)
20
+ from etf_flow.pcf import (
21
+ PCFConstituent,
22
+ fetch_and_cache_etf_pcf,
23
+ get_pcf_stock_weights,
24
+ build_full_pcf_index,
25
+ )
26
+
27
+ # 3. 沪深双交易所官方定盘份额对账 (Scale)
28
+ from etf_flow.scale import (
29
+ fetch_sse_etf_shares,
30
+ fetch_szse_etf_shares,
31
+ get_dual_exchange_etf_shares,
32
+ get_etf_share_deltas,
33
+ )
34
+
35
+ # 4. 法定季报与持仓时序穿透 (Holdings)
36
+ from etf_flow.holdings import (
37
+ StockHoldingInETF,
38
+ StockETFProfile,
39
+ fetch_etf_portfolio_holdings,
40
+ build_stock_etf_index,
41
+ )
42
+
43
+ # 5. 日K定盘折溢价时序与一二级套利沉淀 (Arbitrage)
44
+ from etf_flow.arbitrage import (
45
+ SettledDailyPremium,
46
+ ArbitrageSettlementSignal,
47
+ get_settled_etf_nav_and_close,
48
+ fetch_official_etf_nav_history,
49
+ get_settled_premium_history,
50
+ analyze_settled_arbitrage_flow,
51
+ scan_market_arbitrage_settlement,
52
+ )
53
+
54
+ # 6. 流动性冲击度与承接天数模型 (Impact)
55
+ from etf_flow.impact import (
56
+ StockImpactMetrics,
57
+ get_stock_adv20,
58
+ calculate_stock_liquidity_impact,
59
+ format_kdata_symbol,
60
+ )
61
+
62
+ # 7. 指数定期调样“前置预判”引擎 (Rebalance)
63
+ from etf_flow.rebalance import (
64
+ RebalanceCandidate,
65
+ IndexRebalanceForecast,
66
+ calculate_rebalance_countdown,
67
+ predict_index_rebalance,
68
+ render_rebalance_markdown,
69
+ )
70
+
71
+ # 8. 资金性质归因与踩踏反噬哨兵 (Sentinel)
72
+ from etf_flow.sentinel import (
73
+ CapitalAttribution,
74
+ LiquidityTrapAlert,
75
+ attribute_etf_capital_flow,
76
+ detect_liquidity_trap,
77
+ )
78
+
79
+ # 9. 核心选股推荐与多因子评分 (Recommender)
80
+ from etf_flow.recommender import (
81
+ StockRecommendation,
82
+ calculate_etf_flow_delta,
83
+ scan_and_recommend_stocks,
84
+ render_recommendations_markdown,
85
+ )
86
+
87
+ # 10. 事件研究回测引擎 (Backtest)
88
+ from etf_flow.backtest import (
89
+ EventStudySample,
90
+ CapacityDecayPoint,
91
+ EventStudyResult,
92
+ EventStudyEngine,
93
+ calculate_real_event_car,
94
+ render_event_study_markdown,
95
+ )
96
+
97
+ # 11. 腾讯高速行情通道 (Tencent)
98
+ from etf_flow.tencent import (
99
+ TencentQuote,
100
+ get_tencent_quotes,
101
+ to_tencent_symbol,
102
+ )
103
+
104
+ __version__ = "2.1.0"
105
+
106
+ __all__ = [
107
+ # Universe
108
+ "ETFMeta",
109
+ "TRACKED_ETFS",
110
+ "ETF_MAP",
111
+ "get_tracked_codes",
112
+ "get_dynamic_etf_universe",
113
+ "get_sector_etf_mapping",
114
+ "classify_etf",
115
+ # PCF
116
+ "PCFConstituent",
117
+ "fetch_and_cache_etf_pcf",
118
+ "get_pcf_stock_weights",
119
+ "build_full_pcf_index",
120
+ # Scale
121
+ "fetch_sse_etf_shares",
122
+ "fetch_szse_etf_shares",
123
+ "get_dual_exchange_etf_shares",
124
+ "get_etf_share_deltas",
125
+ # Holdings
126
+ "StockHoldingInETF",
127
+ "StockETFProfile",
128
+ "fetch_etf_portfolio_holdings",
129
+ "build_stock_etf_index",
130
+ # Arbitrage
131
+ "SettledDailyPremium",
132
+ "ArbitrageSettlementSignal",
133
+ "get_settled_etf_nav_and_close",
134
+ "fetch_official_etf_nav_history",
135
+ "get_settled_premium_history",
136
+ "analyze_settled_arbitrage_flow",
137
+ "scan_market_arbitrage_settlement",
138
+ # Impact
139
+ "StockImpactMetrics",
140
+ "get_stock_adv20",
141
+ "calculate_stock_liquidity_impact",
142
+ "format_kdata_symbol",
143
+ # Rebalance
144
+ "RebalanceCandidate",
145
+ "IndexRebalanceForecast",
146
+ "calculate_rebalance_countdown",
147
+ "predict_index_rebalance",
148
+ "render_rebalance_markdown",
149
+ # Sentinel
150
+ "CapitalAttribution",
151
+ "LiquidityTrapAlert",
152
+ "attribute_etf_capital_flow",
153
+ "detect_liquidity_trap",
154
+ # Recommender
155
+ "StockRecommendation",
156
+ "calculate_etf_flow_delta",
157
+ "scan_and_recommend_stocks",
158
+ "render_recommendations_markdown",
159
+ # Backtest
160
+ "EventStudySample",
161
+ "CapacityDecayPoint",
162
+ "EventStudyResult",
163
+ "EventStudyEngine",
164
+ "calculate_real_event_car",
165
+ "render_event_study_markdown",
166
+ # Tencent
167
+ "TencentQuote",
168
+ "get_tencent_quotes",
169
+ "to_tencent_symbol",
170
+ # Version
171
+ "__version__",
172
+ ]
etf_flow/api.py ADDED
@@ -0,0 +1,251 @@
1
+ """ETF Tracker - REST API & OpenAPI 微服务网关 (PRD F5.3).
2
+
3
+ 提供基于 FastAPI 的高性能微服务接口:
4
+ 1. GET /api/v1/health: 服务健康检查与 18:30 官方定盘时钟状态;
5
+ 2. GET /api/v1/universe: 获取全市场动态/核心 ETF 标的池;
6
+ 3. GET /api/v1/recommendations: 获取最新推荐池 (支持 top_n, scenario 过滤);
7
+ 4. GET /api/v1/stock/{code}/profile: 获取指定个股的完整 ETF 挂钩穿透画像;
8
+ 5. GET /api/v1/etf/{code}/flow: 获取单只 ETF 的份额变动与买盘传导;
9
+ 6. GET /api/v1/arbitrage/{code}: 获取单只 ETF 的日K定盘折溢价时序与套利沉淀分析;
10
+ 7. GET /api/v1/backtest/event-study: 运行事件研究回测并返回 CAR 时序与容量衰减指标。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from datetime import date, datetime
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from fastapi import FastAPI, HTTPException, Query
20
+ from pydantic import BaseModel
21
+
22
+ import kdata
23
+ from etf_flow.arbitrage import (
24
+ analyze_settled_arbitrage_flow,
25
+ scan_market_arbitrage_settlement,
26
+ )
27
+ from etf_flow.backtest import EventStudyEngine
28
+ from etf_flow.holdings import build_stock_etf_index
29
+ from etf_flow.recommender import scan_and_recommend_stocks
30
+ from etf_flow.scale import get_etf_share_deltas
31
+ from etf_flow.universe import (
32
+ TRACKED_ETFS,
33
+ get_dynamic_etf_universe,
34
+ get_tracked_codes,
35
+ )
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ app = FastAPI(
40
+ title="ETF Tracker 量化接口网关",
41
+ description="全市场 ETF 资金流动穿透、成分股被动买盘测算、折溢价套利沉淀与事件研究回测 API",
42
+ version="2.1.0",
43
+ )
44
+
45
+
46
+ @app.get("/")
47
+ def root() -> Dict[str, Any]:
48
+ """API 根路径导航."""
49
+ return {
50
+ "service": "ETF Tracker REST API Gateway",
51
+ "version": "2.1.0",
52
+ "docs_url": "/docs",
53
+ "settled_bar_principle": "立足每日 18:30 官方定盘日K及以上周期,杜绝盘中未结算噪音",
54
+ }
55
+
56
+
57
+ @app.get("/api/v1/health")
58
+ def get_health() -> Dict[str, Any]:
59
+ """系统健康检查与交易定盘时钟状态."""
60
+ today_str = date.today().isoformat()
61
+ is_trade = kdata.is_trade_date(today_str)
62
+ now_hour = datetime.now().hour
63
+ now_minute = datetime.now().minute
64
+ is_settled = (now_hour > 18) or (now_hour == 18 and now_minute >= 30)
65
+
66
+ return {
67
+ "status": "healthy",
68
+ "date": today_str,
69
+ "is_trade_date": is_trade,
70
+ "current_time": datetime.now().strftime("%H:%M:%S"),
71
+ "settled_clock_status": "已定盘 (权威定盘数据生效)" if is_settled else "待18:30官方定盘",
72
+ }
73
+
74
+
75
+ @app.get("/api/v1/universe")
76
+ def get_universe(
77
+ category: str = Query("all", description="分类: all | broad | sector"),
78
+ dynamic: bool = Query(False, description="是否返回动态全市场池 (>800只候选)"),
79
+ ) -> Dict[str, Any]:
80
+ """获取当前跟踪的 ETF 标的池."""
81
+ if dynamic:
82
+ pool = get_dynamic_etf_universe(category=category)
83
+ else:
84
+ codes = get_tracked_codes(category)
85
+ pool = [e for e in TRACKED_ETFS if e.code in codes]
86
+
87
+ items = [
88
+ {
89
+ "code": e.code,
90
+ "name": e.name,
91
+ "category": e.category,
92
+ "benchmark": e.benchmark,
93
+ "scale_yi": getattr(e, "scale_yi", 0.0),
94
+ "industry": getattr(e, "industry", ""),
95
+ }
96
+ for e in pool
97
+ ]
98
+ return {
99
+ "total": len(items),
100
+ "category": category,
101
+ "dynamic": dynamic,
102
+ "items": items,
103
+ }
104
+
105
+
106
+ @app.get("/api/v1/recommendations")
107
+ def get_recommendations(
108
+ top_n: int = Query(5, ge=1, le=50, description="推荐数量"),
109
+ scenario: Optional[int] = Query(None, ge=1, le=5, description="场景筛选 (1~5)"),
110
+ min_elasticity: float = Query(300.0, description="最低单亿申购弹性 (万元/亿)"),
111
+ ) -> Dict[str, Any]:
112
+ """全市场扫描 ETF 资金流动并输出最新利好股票推荐池 (PRD F5.3)."""
113
+ try:
114
+ recs = scan_and_recommend_stocks(
115
+ top_n=top_n,
116
+ scenario_filter=scenario,
117
+ min_elasticity=min_elasticity,
118
+ )
119
+ return {
120
+ "success": True,
121
+ "count": len(recs),
122
+ "scenario_filter": scenario,
123
+ "recommendations": [r.__dict__ for r in recs],
124
+ }
125
+ except Exception as e:
126
+ logger.error("扫描推荐股票异常: %s", e)
127
+ raise HTTPException(status_code=500, detail=str(e))
128
+
129
+
130
+ @app.get("/api/v1/stock/{code}/profile")
131
+ def get_stock_profile(code: str) -> Dict[str, Any]:
132
+ """获取指定个股的完整 ETF 挂钩穿透画像 (PRD F5.3)."""
133
+ clean_code = str(code).strip().replace("sh.", "").replace("sz.", "").replace("bj.", "")
134
+ try:
135
+ stock_index = build_stock_etf_index(tracked_etfs=TRACKED_ETFS, use_pcf=True)
136
+ profile = stock_index.get(clean_code)
137
+ if not profile:
138
+ name = (
139
+ kdata.get_name_from_code(f"sh.{clean_code}" if clean_code.startswith("6") else f"sz.{clean_code}")
140
+ or clean_code
141
+ )
142
+ return {
143
+ "stock_code": clean_code,
144
+ "stock_name": name,
145
+ "linked_etf_count": 0,
146
+ "total_weight_sum": 0.0,
147
+ "max_weight": 0.0,
148
+ "holdings": [],
149
+ "message": "当前未在核心跟踪 ETF 的重仓或 PCF 篮子中出现",
150
+ }
151
+
152
+ holdings_data = [
153
+ {
154
+ "etf_code": h.etf_code,
155
+ "etf_name": h.etf_name,
156
+ "category": h.category,
157
+ "weight_pct": h.weight_pct,
158
+ "prev_weight_pct": h.prev_weight_pct,
159
+ "delta_weight_pct": h.delta_weight_pct,
160
+ "shares_wan": h.shares_wan,
161
+ "mkt_val_wan": h.mkt_val_wan,
162
+ "latest_quarter": str(h.latest_quarter),
163
+ "source": getattr(h, "source", "quarterly"),
164
+ }
165
+ for h in profile.holdings
166
+ ]
167
+ return {
168
+ "stock_code": profile.stock_code,
169
+ "stock_name": profile.stock_name,
170
+ "linked_etf_count": profile.linked_etf_count,
171
+ "total_weight_sum": round(profile.total_weight_sum, 2),
172
+ "max_weight": round(profile.max_weight, 2),
173
+ "max_delta_weight": round(profile.max_delta_weight, 2),
174
+ "holdings": holdings_data,
175
+ }
176
+ except Exception as e:
177
+ logger.error("查询个股 %s 穿透画像异常: %s", clean_code, e)
178
+ raise HTTPException(status_code=500, detail=str(e))
179
+
180
+
181
+ @app.get("/api/v1/etf/{code}/flow")
182
+ def get_etf_flow(code: str) -> Dict[str, Any]:
183
+ """查询单只 ETF 的日频官方份额变动与买盘流动测算."""
184
+ clean_code = str(code).strip()
185
+ try:
186
+ deltas = get_etf_share_deltas([clean_code])
187
+ flow_info = deltas.get(clean_code, {})
188
+ return {
189
+ "etf_code": clean_code,
190
+ "date": date.today().isoformat(),
191
+ "shares_info": flow_info,
192
+ }
193
+ except Exception as e:
194
+ raise HTTPException(status_code=500, detail=str(e))
195
+
196
+
197
+ @app.get("/api/v1/arbitrage/{code}")
198
+ def get_etf_arbitrage(code: str) -> Dict[str, Any]:
199
+ """查询单只 ETF 的日K收盘折溢价时序与一二级市场申赎套利沉淀信号 (PRD F1.3)."""
200
+ clean_code = str(code).strip()
201
+ try:
202
+ sig = analyze_settled_arbitrage_flow(clean_code)
203
+ return {
204
+ "etf_code": sig.etf_code,
205
+ "etf_name": sig.etf_name,
206
+ "settled_date": sig.settled_date,
207
+ "latest_close": sig.latest_close,
208
+ "latest_nav": sig.latest_nav,
209
+ "latest_premium_rate_pct": sig.latest_premium_rate_pct,
210
+ "avg_premium_rate_3d": sig.avg_premium_rate_3d,
211
+ "signal_type": sig.signal_type,
212
+ "signal_desc": sig.signal_desc,
213
+ "streak_days": sig.streak_days,
214
+ "arbitrage_pressure_wan": sig.arbitrage_pressure_wan,
215
+ "constituents_impact_desc": sig.constituents_impact_desc,
216
+ "risk_level": sig.risk_level,
217
+ "history": [h.__dict__ for h in sig.history],
218
+ }
219
+ except Exception as e:
220
+ raise HTTPException(status_code=500, detail=str(e))
221
+
222
+
223
+ @app.get("/api/v1/backtest/event-study")
224
+ def get_event_study(
225
+ event_type: str = Query("A", description="事件类型: A | B | C"),
226
+ ) -> Dict[str, Any]:
227
+ """运行事件研究回测并返回超额收益 CAR、胜率与容量衰减曲线 (PRD F4.1)."""
228
+ try:
229
+ engine = EventStudyEngine()
230
+ res = engine.run_event_study(event_type=event_type)
231
+ return {
232
+ "event_type": res.event_type,
233
+ "event_title": res.event_title,
234
+ "total_events": res.total_events,
235
+ "benchmark": res.benchmark,
236
+ "car_timeline": {
237
+ "T+1": res.mean_car_t1,
238
+ "T+3": res.mean_car_t3,
239
+ "T+5": res.mean_car_t5,
240
+ "T+10": res.mean_car_t10,
241
+ "T+20": res.mean_car_t20,
242
+ },
243
+ "win_rate_pct": res.win_rate_pct,
244
+ "profit_loss_ratio": res.profit_loss_ratio,
245
+ "max_drawdown_pct": res.max_drawdown_pct,
246
+ "capacity_decay": [c.__dict__ for c in res.capacity_decay],
247
+ "samples": [s.__dict__ for s in res.samples],
248
+ "summary_desc": res.summary_desc,
249
+ }
250
+ except Exception as e:
251
+ raise HTTPException(status_code=500, detail=str(e))