Spaces:
Running
Running
| from collections import namedtuple | |
| from data.institutional_flow_cache import ( | |
| fetch_daily_rows, | |
| fetch_recent_rows_by_code, | |
| get_fetch_status, | |
| init_db, | |
| upsert_daily_rows, | |
| upsert_fetch_status, | |
| ) | |
| from data.tw_universe import build_universe_records | |
| def test_build_universe_records_from_synthetic_twstock_codes(): | |
| Code = namedtuple("Code", "type code name ISIN start market group CFI") | |
| records = build_universe_records( | |
| { | |
| "2330": Code("股票", "2330", "台積電", "", "", "上市", "半導體業", ""), | |
| "006201": Code("ETF", "006201", "元大富櫃50", "", "", "上櫃", "", ""), | |
| "01001T": Code("受益證券", "01001T", "測試受益證券", "", "", "上市", "", ""), | |
| } | |
| ) | |
| assert records == [ | |
| { | |
| "code": "006201", | |
| "name": "元大富櫃50", | |
| "market": "上櫃", | |
| "exchange": "TPEX", | |
| "type": "ETF", | |
| "group": "", | |
| "subsector": "未分類", | |
| "subsector_path": "未分類", | |
| "subsector_source": "industry_fallback", | |
| "subsector_refined": False, | |
| }, | |
| { | |
| "code": "01001T", | |
| "name": "測試受益證券", | |
| "market": "上市", | |
| "exchange": "TWSE", | |
| "type": "受益證券", | |
| "group": "", | |
| "subsector": "未分類", | |
| "subsector_path": "未分類", | |
| "subsector_source": "industry_fallback", | |
| "subsector_refined": False, | |
| }, | |
| { | |
| "code": "2330", | |
| "name": "台積電", | |
| "market": "上市", | |
| "exchange": "TWSE", | |
| "type": "股票", | |
| "group": "半導體業", | |
| "subsector": "晶圓代工", | |
| "subsector_path": "半導體業 > 晶圓代工", | |
| "subsector_source": "code_override", | |
| "subsector_refined": True, | |
| }, | |
| ] | |
| def test_upsert_and_fetch_daily_rows_round_trip(tmp_path): | |
| db_path = tmp_path / "flow.sqlite3" | |
| init_db(db_path).close() | |
| written = upsert_daily_rows( | |
| db_path, | |
| [ | |
| { | |
| "date": "2026-05-18", | |
| "stock_no": "2330.TW", | |
| "foreign_buy": 100, | |
| "foreign_sell": 80, | |
| "foreign_net": 20, | |
| "trust_net": 3, | |
| "dealer_net": -2, | |
| "institutional_net": 21, | |
| "institutional_source": "TWSE_T86", | |
| "institutional_as_of": "2026-05-18", | |
| }, | |
| { | |
| "date": "2026-05-19", | |
| "stock_no": "2330", | |
| "foreign_net": 30, | |
| "trust_net": 4, | |
| "dealer_net": 5, | |
| "institutional_net": 39, | |
| "institutional_available": False, | |
| "institutional_carry_forward": True, | |
| }, | |
| ], | |
| ) | |
| rows = fetch_daily_rows(db_path, "2330.TW", "2026-05-01", "2026-05-31") | |
| assert written == 2 | |
| assert [row["date"] for row in rows] == ["2026-05-18", "2026-05-19"] | |
| assert rows[0]["stock_no"] == "2330" | |
| assert rows[0]["foreign_buy"] == 100 | |
| assert rows[0]["trust_buy"] == 0 | |
| assert rows[0]["institutional_source"] == "TWSE_T86" | |
| assert rows[0]["institutional_available"] is True | |
| assert rows[1]["institutional_available"] is False | |
| assert rows[1]["institutional_carry_forward"] is True | |
| def test_upsert_daily_rows_replaces_existing_row(tmp_path): | |
| db_path = tmp_path / "flow.sqlite3" | |
| upsert_daily_rows( | |
| db_path, | |
| [{"date": "2026-05-18", "stock_no": "2330", "institutional_net": 10}], | |
| ) | |
| upsert_daily_rows( | |
| db_path, | |
| [{"date": "2026-05-18", "stock_no": "2330", "institutional_net": 99}], | |
| ) | |
| rows = fetch_daily_rows(db_path, "2330", "2026-05-18", "2026-05-18") | |
| assert len(rows) == 1 | |
| assert rows[0]["institutional_net"] == 99 | |
| def test_fetch_status_round_trip(tmp_path): | |
| db_path = tmp_path / "flow.sqlite3" | |
| upsert_fetch_status( | |
| db_path, | |
| "TWSE_T86", | |
| "2026-05-18", | |
| "ok", | |
| row_count=123, | |
| fetched_at="2026-05-18T09:10:00+00:00", | |
| ) | |
| assert get_fetch_status(db_path, "TWSE_T86", "2026-05-18") == { | |
| "source": "TWSE_T86", | |
| "date": "2026-05-18", | |
| "status": "ok", | |
| "fetched_at": "2026-05-18T09:10:00+00:00", | |
| "row_count": 123, | |
| "error": "", | |
| } | |
| def test_fetch_recent_rows_by_code_uses_latest_trading_rows(tmp_path): | |
| db_path = tmp_path / "flow.sqlite3" | |
| upsert_daily_rows( | |
| db_path, | |
| [ | |
| {"date": "2026-05-16", "stock_no": "2330", "institutional_net": 1}, | |
| {"date": "2026-05-17", "stock_no": "2330", "institutional_net": 2}, | |
| {"date": "2026-05-18", "stock_no": "2330", "institutional_net": 3}, | |
| {"date": "2026-05-18", "stock_no": "2454", "institutional_net": 9}, | |
| ], | |
| ) | |
| grouped = fetch_recent_rows_by_code(db_path, codes=["2330"], lookback_days=2) | |
| assert list(grouped) == ["2330"] | |
| assert [row["date"] for row in grouped["2330"]] == ["2026-05-17", "2026-05-18"] | |
| assert [row["institutional_net"] for row in grouped["2330"]] == [2, 3] | |