oilverse-api / pipeline /live_data.py
ๅญ™ๅฎถๆ˜Ž
deploy: OilVerse for HuggingFace (Node.js 18 fix)
fab9847
Raw
History Blame Contribute Delete
34 kB
"""
live_data.py โ€” ๅ…จ็‰นๅพ API ๅฎžๆ—ถๆ›ดๆ–ฐ๏ผˆไธไพ่ต–ๆœฌๅœฐ CSV๏ผ‰
=====================================================
ๆ•ฐๆฎๆบ:
1. FRED API โ€” ็พŽๅ›ฝๅฎ่ง‚ใ€ไปทๆ ผใ€ๅˆฉ็އใ€ISM ๅญๆŒ‡ๆ•ฐ (~90)
2. Yahoo Finance โ€” ๅ•†ๅ“ไปทๆ ผใ€ๆฑ‡็އใ€ๆณขๅŠจ็އๆŒ‡ๆ•ฐ (~25)
3. World Bank API โ€” ๅ…จ็ƒๅคงๅฎ—ๅ•†ๅ“ไปทๆ ผ (~40)
4. akshare โ€” ไธญๅ›ฝๅฎ่ง‚ M1/M2/PMI (~9)
5. CFTC โ€” ๆŒไป“ๆŠฅๅ‘Š๏ผˆ่‡ชๅŠจไธ‹่ฝฝ CSV๏ผ‰(~30)
6. EIA API โ€” ็พŽๅ›ฝ่ƒฝๆบๅบ“ๅญ˜/ไบง้‡ (~8)
7. GPR Index โ€” ๅœฐ็ผ˜ๆ”ฟๆฒป้ฃŽ้™ฉๆŒ‡ๆ•ฐ (Caldara & Iacoviello)
8. ๆดพ็”Ÿ็‰นๅพ โ€” ไปŽๅŽŸๅง‹็‰นๅพ่‡ชๅŠจ่ฎก็ฎ— (~55)
็”จๆณ•:
python live_data.py # ๅ…จ้‡ๆ›ดๆ–ฐ
python live_data.py --test # ๆต‹่ฏ•ๅ„ API ่ฟž้€šๆ€ง
python live_data.py --source fred # ๅชๆ›ดๆ–ฐๆŸไธชๆ•ฐๆฎๆบ
"""
import pandas as pd
import numpy as np
import os, json, warnings, argparse, time, requests
from datetime import datetime, timedelta
warnings.filterwarnings('ignore')
os.chdir(r'e:\ๅคงไธ‰ไธ‹\ๆฏ”่ต›\่Šฑๆ——ๆฏ\ๆจกๅž‹')
FRED_KEY = 'fc02a6e6a359a4cc16f0f1752d258011'
EIA_KEY = '9Nv5PhLREMmmKeo0zJ2U3Zu21Bntf8DfhEKBpi55'
OUTPUT_DIR = 'output'
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 1. FRED API โ€” ็พŽๅ›ฝๅฎ่ง‚ + ไปทๆ ผ + ๅˆฉ็އ + ISM
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
FRED_MAP = {
# โ”€โ”€ ไปทๆ ผ โ”€โ”€
'WTI_spot': 'DCOILWTICO',
'Brent_spot': 'DCOILBRENTEU',
'natgas_spot_henry': 'DHHNGSP',
'gold_spot': 'GOLDAMGBD228NLBM',
# โ”€โ”€ ้œ€ๆฑ‚/ๅฎ่ง‚ โ”€โ”€
'pmi_us_mfg': 'MANEMP',
'ipi_us': 'INDPRO',
'nonfarm_us': 'PAYEMS',
'usd_index': 'DTWEXBGS',
'cpi_us': 'CPIAUCSL',
'fed_funds_rate': 'FEDFUNDS',
'yield_spread_10y2y': 'T10Y2Y',
'us_10y_yield': 'DGS10',
# โ”€โ”€ ้ฃŽ้™ฉ โ”€โ”€
'vix': 'VIXCLS',
# โ”€โ”€ ไพ›็ป™ โ”€โ”€
'us_oil_inventory_total': 'WCESTUS1',
'us_crude_production': 'MCRFPUS2',
# โ”€โ”€ ๅˆฉ็އ/ๆ”ถ็›Š็އ โ”€โ”€
'libor_usd_3m': 'USD3MTD156N',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๆ”ถ็›Š็އ:3ๅนด': 'DGS3',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๆ”ถ็›Š็އ:7ๅนด': 'DGS7',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๆ”ถ็›Š็އ:10ๅนด': 'DGS10',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๆ”ถ็›Š็އ:30ๅนด': 'DGS30',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๅˆฐๆœŸๆ”ถ็›Š็އ(ไปฅ้€š่ƒ€ไธบๆ ‡็š„):7ๅนด': 'DFII7',
'็พŽๅ›ฝ:ๅ›ฝๅ€บๆ”ถ็›Š็އๅˆฉๅทฎ:10ๅนด-2ๅนด': 'T10Y2Y',
# โ”€โ”€ ่ดงๅธ โ”€โ”€
'็พŽๅ›ฝ:M1:้žๅญฃ่ฐƒ': 'M1NS',
'us_m2_yoy': 'WM2NS',
'็พŽๅ›ฝ:่ดงๅธไน˜ๆ•ฐ:M1:้žๅญฃ่ฐƒ': 'MULT',
'็พŽๅ›ฝ:่ดงๅธไน˜ๆ•ฐ:M2:้žๅญฃ่ฐƒ': 'M2REAL',
# โ”€โ”€ ISM ๅˆถ้€ ไธš PMI ๅญๆŒ‡ๆ•ฐ๏ผˆ40ไธช๏ผ‰โ”€โ”€
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI': 'NAPM',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐ่ฎขๅ•': 'NAPMNOI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่‡ชๆœ‰ๅบ“ๅญ˜': 'NAPMII',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฎขๆˆทๅบ“ๅญ˜': 'NAPMCI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฐฑไธš': 'NAPMEI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:็‰ฉไปท': 'NAPMPRI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไบงๅ‡บ': 'NAPMPI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜': 'NAPMSDI',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•': 'NAPMEX',
'็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่ฟ›ๅฃ': 'NAPMIMP',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI': 'NMFBAI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅฐฑไธš': 'NMFEI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐ่ฎขๅ•': 'NMFNOI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:็‰ฉไปท': 'NMFPI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅ•†ไธšๆดปๅŠจ': 'NMFBAI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜': 'NMFSDI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅบ“ๅญ˜': 'NMFII',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฎขๅ•ๅบ“ๅญ˜': 'NMFOBI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•': 'NMFEXI',
'็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฟ›ๅฃ': 'NMFIMI',
'pmi_us_svc': 'NMFBAI',
'employment_us_ๅญฃ่ฐƒ': 'PAYEMS',
# โ”€โ”€ GDP โ”€โ”€
'gdp_us_yoy': 'A191RL1Q225SBEA',
'gdp_japan_yoy': 'JPNRGDPEXP',
'gdp_germany_yoy': 'CLVMNACSCAB1GQDE',
'gdp_uk_yoy': 'CLVMNACSCAB1GQUK',
'gdp_australia_yoy': 'NAEXKP01AUQ189S',
# โ”€โ”€ PPI/CPI โ”€โ”€
'ppi_us_yoy': 'PPIACO',
'ppi_us_ๅŒๆฏ”': 'PPIACO',
'cpi_japan': 'JPNCPIALLMINMEI',
'ppi_japan': 'JPNPROINDMISMEI',
'ppi_eurozone': 'EA19PRMNIG01GYSAM',
'ipi_europe': 'EA19PRINTO01GYSAM',
'ppi_russia': 'RUSCPIALLMINMEI',
# โ”€โ”€ ๆฌงๆดฒ/ๆ—ฅๆœฌๅฎ่ง‚ โ”€โ”€
'pmi_eu_mfg': 'BSCICP03EZM460S',
'ez_econ_sentiment': 'CSCICP03EZM460S',
'ez_consumer_conf': 'CSCICP03EZM460S',
'pmi_japan': 'BSCICP03JPM460S',
'pmi_canada': 'BSCICP03CAM460S',
# โ”€โ”€ OFR โ”€โ”€
'็พŽๅ›ฝ:OFR้‡‘่žๅŽ‹ๅŠ›ๆŒ‡ๆ•ฐ': 'STLFSI2',
# โ”€โ”€ ็พŽ่”ๅ‚จ่ต„ไบง่ดŸๅ€บ่กจ (FREDๆœ‰ๆฑ‡ๆ€ป) โ”€โ”€
'fed_balance_sheet_total': 'WALCL',
# โ”€โ”€ ๅ›ฝ้™…ๅˆฉ็އ (LIBORโ†’SOFRๆ›ฟไปฃ) โ”€โ”€
'libor_3m_่‹ฑ้•‘LIBOR3M': 'GBP3MTD156N',
'libor_3m_ๆ—ฅๅ…ƒLIBOR3M': 'JPY3MTD156N',
'eurlibor_3m': 'EUR3MTD156N',
# โ”€โ”€ ็พŽๅ…ƒๆŒ‡ๆ•ฐ(ๆ–ฐๅ…ดๅธ‚ๅœบ) โ”€โ”€
'usd_index_em': 'DTWEXEMEGS',
# โ”€โ”€ ็พŽๅ›ฝ็Ÿณๆฒนๆถˆ่ดน/้œ€ๆฑ‚ (FRED้•œๅƒEIA) โ”€โ”€
'us_oil_consumption': 'MTTIMUS2',
'demand_us': 'MTTIMUS2',
# โ”€โ”€ ็พŽๅ›ฝ่ดธๆ˜“ โ”€โ”€
'us_crude_export_yoy': 'MCREXUS2',
'us_crude_import_yoy': 'MCRIMUS2',
# โ”€โ”€ ็พŽ่”ๅ‚จ่ต„ไบง่ดŸๅ€บ่กจๆ˜Ž็ป† (ๅทฒ้ชŒ่ฏๅฏ็”จ) โ”€โ”€
'fed_balance_sheet_ๅญ˜ๆฌพๆœบๆž„ๅ‚จๅค‡(ไธๅŒ…ๆ‹ฌFHLBๅญ˜ๆฌพ)': 'WRESBAL',
'fed_balance_sheet_่ฏๅˆธๅ›ž่ดญๅ่ฎฎ(ๅŒ…ๆ‹ฌๅฎ˜ๆ–นๅ‚จๅค‡ไธญ็š„ๅ…ถไป–ๅ€บๆƒ)': 'WLRRAL',
'fed_balance_sheet_ๅฏน่”้‚ฆๆ”ฟๅบœ็š„ๅฏๆ”ฏ็ฅจๅญ˜ๆฌพ': 'WDFOL',
'fed_balance_sheet_่”้‚ฆๅ‚จๅค‡้“ถ่กŒ่‚ก็ฅจ': 'TREAST',
'fed_balance_sheet_่”้‚ฆไฝๆˆฟ่ดทๆฌพ้“ถ่กŒ(FHLB)ๅ€Ÿๆฌพ': 'WSHOMCB',
# โ”€โ”€ ๅ‚จๅค‡/้œ€ๆฑ‚ โ”€โ”€
'reserve_us': 'TOTRESNS',
# โ”€โ”€ HIBOR โ”€โ”€
'hibor_3m': 'HKONTD156N',
# โ”€โ”€ ไธญๅ›ฝPMI (FRED OECD proxy) โ”€โ”€
'pmi_china': 'CHNPMINDMISMEI',
}
def fetch_fred_bulk(start='2000-01-01'):
"""ๆ‰น้‡ไปŽ FRED ๆ‹‰ๅ–ๆ‰€ๆœ‰ๆ˜ ๅฐ„็š„ๆŒ‡ๆ ‡ใ€‚"""
from fredapi import Fred
fred = Fred(api_key=FRED_KEY)
end = datetime.now().strftime('%Y-%m-%d')
results = {}
total = len(FRED_MAP)
print(f"\n[FRED] ๆ‹‰ๅ– {total} ไธชๆŒ‡ๆ ‡...")
for i, (name, sid) in enumerate(FRED_MAP.items()):
try:
data = fred.get_series(sid, observation_start=start, observation_end=end)
if data is not None and len(data) > 0:
monthly = data.resample('ME').last().dropna()
results[name] = monthly
print(f" [{i+1}/{total}] โœ“ {name:<35} {len(monthly)} ๆœˆ")
else:
print(f" [{i+1}/{total}] โœ— {name:<35} ๆ— ๆ•ฐๆฎ")
except Exception as e:
print(f" [{i+1}/{total}] โœ— {name:<35} {str(e)[:40]}")
df = pd.DataFrame(results) if results else pd.DataFrame()
print(f" FRED ๅฎŒๆˆ: {len(results)}/{total}")
return df
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 2. YAHOO FINANCE โ€” ๅ•†ๅ“/ๆฑ‡็އ/ๆณขๅŠจ็އ
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
YFINANCE_MAP = {
# === ๆฑ‡็އ ===
'usd_cny': 'CNY=X',
'usd_jpy': 'JPY=X',
# === ๆณขๅŠจ็އ ===
'vix_nasdaq': '^VXN',
'ovx_crude_vol': '^OVX',
'็บณๆ–ฏ่พพๅ…‹100ๆณขๅŠจ็އๆŒ‡ๆ•ฐ': '^VXN',
# === ่ดต้‡‘ๅฑž ===
'iron_ore_spot': 'TIOC.SI',
'nickel_price': '^SPGSNKP',
'็Žฐ่ดงไปท(ไผฆๆ•ฆๅธ‚ๅœบ):้ป„้‡‘:็พŽๅ…ƒ': 'GC=F',
'็Žฐ่ดงไปท(ไผฆๆ•ฆๅธ‚ๅœบ):็™ฝ้“ถ:็พŽๅ…ƒ': 'SI=F',
'COMEXๅพฎๅž‹้ป„้‡‘': 'MGC=F',
# === ๅคงๅฎ—ๅ•†ๅ“๏ผˆๆ›ฟไปฃ World Bank๏ผ‰===
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:้“œ': 'HG=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:้“': 'ALI=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:้”Œ': 'ZINC.L',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:็މ็ฑณ': 'ZC=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:ๅคง็ฑณ': 'ZR=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:ๆฃ‰่Šฑ': 'CT=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:่ฑ†็ฒ•': 'ZM=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:่ฑ†ๆฒน': 'ZL=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:็ณ–(CESE็ฌฌ11ๅทๅˆๅŒๆœ€่ฟ‘็š„ๆœชๆฅๅคดๅฏธ)': 'SB=F',
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:ๅคฉ็„ถๆฐ”(ๅฐๅฐผๅ‡บๅฃๆ—ฅๆœฌ)': 'TTF=F',
'ๅ…จ็ƒ:ๅ•†ๅ“ไปทๆ ผ:ๅŽŸๆฒน:ไธ‰ๅคงๅธ‚ๅœบๅนณๅ‡ๅ€ผ': 'CL=F',
# === ๅ†œไบงๅ“ ===
'agri_prices_ๅคง่ฑ†': 'ZS=F',
'agri_prices_ๅฐ้บฆ': 'ZW=F',
'agri_prices_็‡•้บฆ': 'ZO=F',
# === ๆ›ดๅคšๅ•†ๅ“๏ผˆๆ›ฟไปฃ World Bank ๅ‰ฉไฝ™๏ผ‰===
'natgas_index': 'NG=F', # ๅคฉ็„ถๆฐ”ๆœŸ่ดง
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:็‰›่‚‰': 'LE=F', # ๆดป็‰›ๆœŸ่ดง
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:ๆฉ„ๆฆ„ๆฒน': 'ZL=F', # ่ฑ†ๆฒน่ฟ‘ไผผ
'ๅ…จ็ƒ:ๅฎž้™…ๅธ‚ๅœบไปทๆ ผ:้ฑผ็ฒ‰': 'ZM=F', # ่ฑ†็ฒ•่ฟ‘ไผผ
# === ๆฑ‡็އproxy๏ผˆ็”จไบŽไพ›็ป™/่ƒฝๆบ่ก็”Ÿ๏ผ‰===
'micex_brent': 'USDRUB=X', # USD/RUBไฝœไธบMICEX-Brent proxy
}
def fetch_yfinance(start='2000-01-01'):
"""ไปŽ Yahoo Finance ๆ‹‰ๅ–ไปทๆ ผ/ๆฑ‡็އ/ๅ•†ๅ“ๆ•ฐๆฎ๏ผˆๆ›ฟไปฃ World Bank๏ผ‰ใ€‚"""
import yfinance as yf
tickers = {k: v for k, v in YFINANCE_MAP.items() if v is not None}
total = len(tickers)
print(f"\n[YFinance] ๆ‹‰ๅ– {total} ไธชๆŒ‡ๆ ‡๏ผˆๅซๅ•†ๅ“ๆ›ฟไปฃ World Bank๏ผ‰...")
results = {}
for i, (name, ticker) in enumerate(tickers.items()):
try:
data = yf.download(ticker, start=start, progress=False, auto_adjust=True)
if data is not None and len(data) > 0:
close = data['Close']
if hasattr(close, 'columns'):
close = close.iloc[:, 0]
monthly = close.resample('ME').last().dropna()
results[name] = monthly
print(f" [{i+1}/{total}] โœ“ {name:<35} {len(monthly)} ๆœˆ")
else:
print(f" [{i+1}/{total}] โœ— {name:<35} ๆ— ๆ•ฐๆฎ")
except Exception as e:
print(f" [{i+1}/{total}] โœ— {name:<35} {str(e)[:40]}")
df = pd.DataFrame(results) if results else pd.DataFrame()
print(f" YFinance ๅฎŒๆˆ: {len(results)}/{total}")
return df
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 3. (ๅทฒๅˆๅนถๅˆฐ YFinance) World Bank ๅ•†ๅ“ โ†’ yfinance ๆœŸ่ดงๆ›ฟไปฃ
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def fetch_worldbank():
"""World Bank ๅทฒ่ขซ yfinance ๅ•†ๅ“ๆœŸ่ดงๆ›ฟไปฃ๏ผŒ่ฟ”ๅ›ž็ฉบ DataFrameใ€‚"""
print(f"\n[World Bank] ๅทฒๅˆๅนถๅˆฐ YFinance ๅ•†ๅ“ๆœŸ่ดง๏ผŒ่ทณ่ฟ‡")
return pd.DataFrame()
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 4. AKSHARE โ€” ไธญๅ›ฝๅฎ่ง‚
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def fetch_akshare():
"""ไปŽ akshare ๆ‹‰ๅ–ไธญๅ›ฝๅฎ่ง‚ๆ•ฐๆฎ๏ผˆM1/M2/PMI/GDP/CPI/PPI๏ผ‰ใ€‚"""
print(f"\n[akshare] ๆ‹‰ๅ–ไธญๅ›ฝๅฎ่ง‚ๆ•ฐๆฎ...")
results = {}
try:
import akshare as ak
# ไธญๅ›ฝ M1/M2 (่ดงๅธไพ›ๅบ”้‡)
try:
ms = ak.macro_china_money_supply()
if ms is not None and len(ms) > 0:
ms['date'] = pd.to_datetime(ms.iloc[:, 0], errors='coerce')
ms = ms.dropna(subset=['date']).set_index('date').sort_index()
for col in ms.columns:
ccl = col.lower()
if 'M2' in col and 'ๅŒๆฏ”' in col:
results['ไธญๅ›ฝ:M2:ๅŒๆฏ”'] = pd.to_numeric(ms[col], errors='coerce')
elif 'M1' in col and 'ๅŒๆฏ”' in col:
results['ไธญๅ›ฝ:M1:ๅŒๆฏ”'] = pd.to_numeric(ms[col], errors='coerce')
elif 'M2' in col and 'ๆ•ฐ้‡' in col:
results['ไธญๅ›ฝ:M2'] = pd.to_numeric(ms[col], errors='coerce')
elif 'M1' in col and 'ๆ•ฐ้‡' in col:
results['ไธญๅ›ฝ:M1'] = pd.to_numeric(ms[col], errors='coerce')
for k in ['ไธญๅ›ฝ:M1', 'ไธญๅ›ฝ:M2', 'ไธญๅ›ฝ:M1:ๅŒๆฏ”', 'ไธญๅ›ฝ:M2:ๅŒๆฏ”']:
if k in results:
print(f" โœ“ {k:<25} {results[k].notna().sum()} ๆœˆ")
except Exception as e:
print(f" โœ— money_supply: {str(e)[:40]}")
# ไธญๅ›ฝ PMI
try:
pmi = ak.macro_china_pmi()
if pmi is not None and len(pmi) > 0:
pmi['date'] = pd.to_datetime(pmi.iloc[:, 0], errors='coerce')
pmi = pmi.dropna(subset=['date']).set_index('date').sort_index()
for col in pmi.columns:
if 'ๅˆถ้€ ไธš' in col and 'ๆŒ‡ๆ•ฐ' in col:
results['pmi_china_mfg'] = pd.to_numeric(pmi[col], errors='coerce')
print(f" โœ“ pmi_china_mfg {results['pmi_china_mfg'].notna().sum()} ๆœˆ")
elif '้žๅˆถ้€ ไธš' in col and 'ๆŒ‡ๆ•ฐ' in col:
results['pmi_china_nonsvc'] = pd.to_numeric(pmi[col], errors='coerce')
print(f" โœ“ pmi_china_nonsvc {results['pmi_china_nonsvc'].notna().sum()} ๆœˆ")
except Exception as e:
print(f" โœ— pmi_china: {str(e)[:40]}")
# ไธญๅ›ฝ CPI (ๆœˆๅบฆ)
try:
cpi = ak.macro_china_cpi_monthly()
if cpi is not None and len(cpi) > 0:
cpi['date'] = pd.to_datetime(cpi.iloc[:, 0], errors='coerce')
cpi = cpi.dropna(subset=['date']).set_index('date').sort_index()
if len(cpi.columns) > 1:
results['cpi_china'] = pd.to_numeric(cpi.iloc[:, 0], errors='coerce')
print(f" โœ“ cpi_china {results['cpi_china'].notna().sum()} ๆœˆ")
except Exception as e:
print(f" โœ— cpi_china: {str(e)[:40]}")
# ไธญๅ›ฝ PPI
try:
ppi = ak.macro_china_ppi()
if ppi is not None and len(ppi) > 0:
ppi['date'] = pd.to_datetime(ppi.iloc[:, 0], errors='coerce')
ppi = ppi.dropna(subset=['date']).set_index('date').sort_index()
if len(ppi.columns) > 1:
results['ppi_china_yoy'] = pd.to_numeric(ppi.iloc[:, 1], errors='coerce')
print(f" โœ“ ppi_china_yoy {results['ppi_china_yoy'].notna().sum()} ๆœˆ")
except Exception as e:
print(f" โœ— ppi_china: {str(e)[:40]}")
# ไธญๅ›ฝ GDP
try:
gdp = ak.macro_china_gdp()
if gdp is not None and len(gdp) > 0:
gdp['date'] = pd.to_datetime(gdp.iloc[:, 0], errors='coerce')
gdp = gdp.dropna(subset=['date']).set_index('date').sort_index()
for col in gdp.columns:
if 'ๅŒๆฏ”ๅขž้•ฟ' in col:
results['gdp_china_yoy'] = pd.to_numeric(gdp[col], errors='coerce')
results['gdp_china_growth_yoy'] = results['gdp_china_yoy']
print(f" โœ“ gdp_china_yoy {results['gdp_china_yoy'].notna().sum()} ๅญฃ")
break
except Exception as e:
print(f" โœ— gdp_china: {str(e)[:40]}")
# SHIBOR 3M
try:
shibor = ak.rate_interbank(market="ไธŠๆตท้“ถ่กŒๅŒไธšๆ‹†ๅ€Ÿๅธ‚ๅœบ",
symbol="Shiborไบบๆฐ‘ๅธ", indicator="3ๆœˆ")
if shibor is not None and len(shibor) > 0:
shibor['date'] = pd.to_datetime(shibor.iloc[:, 0], errors='coerce')
shibor = shibor.set_index('date').sort_index()
series = pd.to_numeric(shibor.iloc[:, 0], errors='coerce')
monthly = series.resample('ME').last().dropna()
results['shibor_3m'] = monthly
print(f" โœ“ shibor_3m {len(monthly)} ๆœˆ")
except Exception as e:
print(f" โœ— shibor_3m: {str(e)[:40]}")
except ImportError:
print(" โœ— akshare ๆœชๅฎ‰่ฃ… (pip install akshare)")
except Exception as e:
print(f" โœ— akshare ้”™่ฏฏ: {e}")
df = pd.DataFrame(results) if results else pd.DataFrame()
print(f" akshare ๅฎŒๆˆ: {len(results)} ๆŒ‡ๆ ‡")
return df
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 5. CFTC ๆŒไป“ๆŠฅๅ‘Š๏ผˆ่‡ชๅŠจไธ‹่ฝฝ๏ผ‰
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def fetch_cftc():
"""ไปŽ CFTC ๅฎ˜็ฝ‘่‡ชๅŠจไธ‹่ฝฝๆŒไป“ๆŠฅๅ‘Šใ€‚"""
print(f"\n[CFTC] ไธ‹่ฝฝๆŒไป“ๆŠฅๅ‘Š...")
results = {}
try:
# CFTC Disaggregated Futures-Only reports
year = datetime.now().year
url = f"https://www.cftc.gov/dea/newcot/{year}fut.zip"
import zipfile, io
resp = requests.get(url, timeout=30)
if resp.status_code == 200:
z = zipfile.ZipFile(io.BytesIO(resp.content))
fname = z.namelist()[0]
df_cftc = pd.read_csv(z.open(fname))
# Filter for crude oil
oil = df_cftc[df_cftc['Market_and_Exchange_Names'].str.contains(
'CRUDE OIL', case=False, na=False)]
if len(oil) > 0:
oil['date'] = pd.to_datetime(oil['As_of_Date_In_Form_YYMMDD'],
format='%y%m%d', errors='coerce')
oil = oil.set_index('date').sort_index()
# Extract key positioning columns
col_map = {
'ice_wti_mm_long': 'M_Money_Positions_Long_All',
'ice_wti_mm_short': 'M_Money_Positions_Short_All',
}
for name, col in col_map.items():
if col in oil.columns:
series = pd.to_numeric(oil[col], errors='coerce')
monthly = series.resample('ME').last().dropna()
results[name] = monthly
print(f" โœ“ {name:<30} {len(monthly)} ๆœˆ")
print(f" CFTC ๅฎŒๆˆ: {len(results)} ๆŒ‡ๆ ‡")
else:
print(f" โœ— CFTC ไธ‹่ฝฝๅคฑ่ดฅ: HTTP {resp.status_code}")
except Exception as e:
print(f" โœ— CFTC ้”™่ฏฏ: {str(e)[:50]}")
return pd.DataFrame(results) if results else pd.DataFrame()
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 6. EIA API
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
EIA_SERIES = {
'us_oil_inventory_total': 'PET.WCESTUS1.W',
'us_crude_production': 'PET.WCRFPUS2.W',
}
def fetch_eia():
"""ไปŽ EIA API ๆ‹‰ๅ–่ƒฝๆบๆ•ฐๆฎใ€‚"""
print(f"\n[EIA] ๆ‹‰ๅ– {len(EIA_SERIES)} ไธชๆŒ‡ๆ ‡...")
results = {}
for name, sid in EIA_SERIES.items():
try:
url = f"https://api.eia.gov/v2/seriesid/{sid}"
resp = requests.get(url, params={'api_key': EIA_KEY}, timeout=30)
if resp.status_code == 200:
data = resp.json()
if 'response' in data and 'data' in data['response']:
records = data['response']['data']
df_tmp = pd.DataFrame(records)
df_tmp['date'] = pd.to_datetime(df_tmp['period'])
df_tmp['value'] = pd.to_numeric(df_tmp['value'], errors='coerce')
series = df_tmp.set_index('date')['value'].sort_index()
monthly = series.resample('ME').last().dropna()
results[name] = monthly
print(f" โœ“ {name:<30} {len(monthly)} ๆœˆ")
else:
print(f" โœ— {name:<30} ๆ— ๆ•ฐๆฎ")
else:
print(f" โœ— {name:<30} HTTP {resp.status_code}")
except Exception as e:
print(f" โœ— {name:<30} {str(e)[:40]}")
return pd.DataFrame(results) if results else pd.DataFrame()
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 7. GPR ๅœฐ็ผ˜ๆ”ฟๆฒป้ฃŽ้™ฉๆŒ‡ๆ•ฐ (Caldara & Iacoviello)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def fetch_gpr():
"""ไปŽๅญฆๆœฏ็ฝ‘็ซ™่‡ชๅŠจไธ‹่ฝฝ GPR ๅœฐ็ผ˜ๆ”ฟๆฒป้ฃŽ้™ฉๆŒ‡ๆ•ฐใ€‚
ๆ›ฟไปฃ30ไธชไบŒๅ€ผๅœฐ็ผ˜ไบ‹ไปถ่™šๆ‹Ÿๅ˜้‡ใ€‚
ๆฅๆบ: https://www.matteoiacoviello.com/gpr.htm
"""
print(f"\n[GPR] ไธ‹่ฝฝๅœฐ็ผ˜ๆ”ฟๆฒป้ฃŽ้™ฉๆŒ‡ๆ•ฐ (Caldara & Iacoviello)...")
results = {}
urls = [
"https://www.matteoiacoviello.com/gpr_files/data_gpr_monthly.xls",
"https://www.matteoiacoviello.com/gpr_files/data_gpr_daily_recent.xls",
]
try:
df_gpr = None
for url in urls:
try:
df_gpr = pd.read_excel(url)
print(f" ไธ‹่ฝฝๆˆๅŠŸ: {url.split('/')[-1]}")
break
except Exception:
continue
if df_gpr is not None and len(df_gpr) > 0:
# ๆ‰พๅˆฐๆ—ฅๆœŸๅˆ—ๅ’ŒGPRๅˆ—
date_col = [c for c in df_gpr.columns if 'date' in c.lower() or 'month' in c.lower()]
gpr_cols = [c for c in df_gpr.columns if 'gpr' in c.lower()]
if date_col:
df_gpr['date'] = pd.to_datetime(df_gpr[date_col[0]], errors='coerce')
else:
df_gpr['date'] = pd.to_datetime(df_gpr.iloc[:, 0], errors='coerce')
df_gpr = df_gpr.dropna(subset=['date']).set_index('date').sort_index()
for col in gpr_cols:
series = pd.to_numeric(df_gpr[col], errors='coerce')
monthly = series.resample('ME').last().dropna()
if len(monthly) > 0:
clean_name = col.strip().lower().replace(' ', '_')
if clean_name in ('gpr', 'gprd'):
clean_name = 'gpr_index'
results[clean_name] = monthly
print(f" โœ“ {clean_name:<30} {len(monthly)} ๆœˆ")
if 'gpr_index' not in results and results:
first_key = list(results.keys())[0]
results['gpr_index'] = results[first_key]
print(f" โœ“ gpr_index (alias of {first_key})")
else:
print(f" โœ— GPR ๆ•ฐๆฎไธบ็ฉบ")
except Exception as e:
print(f" โœ— GPR ไธ‹่ฝฝๅคฑ่ดฅ: {str(e)[:50]}")
df = pd.DataFrame(results) if results else pd.DataFrame()
print(f" GPR ๅฎŒๆˆ: {len(results)} ๆŒ‡ๆ ‡")
return df
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# MERGE + FEATURE ENGINEERING
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def merge_all_sources(fred_df, yf_df, wb_df, ak_df, cftc_df, eia_df, gpr_df=None,
existing_path='output/panel_monthly.csv'):
"""ๅˆๅนถๆ‰€ๆœ‰ๆ•ฐๆฎๆบ๏ผŒ็”Ÿๆˆๆ›ดๆ–ฐๅŽ็š„้ขๆฟใ€‚"""
print(f"\n[MERGE] ๅˆๅนถๆ‰€ๆœ‰ๆ•ฐๆฎๆบ...")
existing = pd.read_csv(existing_path, index_col=0, parse_dates=True)
print(f" ็Žฐๆœ‰้ขๆฟ: {len(existing)} ่กŒ ร— {len(existing.columns)} ๅˆ—")
print(f" ๆˆชๆญข: {existing.index[-1].strftime('%Y-%m')}")
sources = [
('FRED', fred_df), ('YFinance', yf_df), ('WorldBank', wb_df),
('akshare', ak_df), ('CFTC', cftc_df), ('EIA', eia_df),
('GPR', gpr_df),
]
updated_cols = 0
new_rows = 0
# ไปทๆ ผๅˆ—้œ€่ฆ็”จ FRED ๆ•ฐๆฎ่ฆ†็›–ๅทฒๆœ‰ๅ€ผ๏ผŒ็กฎไฟๆ•ฐๆฎๆบไธ€่‡ดๆ€ง
PRICE_OVERRIDE_COLS = {'WTI_spot', 'Brent_spot'}
for src_name, src_df in sources:
if src_df is None or len(src_df) == 0:
continue
for col in src_df.columns:
if col in existing.columns:
is_price_override = (col in PRICE_OVERRIDE_COLS and src_name == 'FRED')
# ่กฅๅ…จ็ผบๅคฑๅ€ผ + ่ฟฝๅŠ ๆ–ฐๆ—ฅๆœŸ
for d in src_df.index:
if d not in existing.index:
existing.loc[d] = np.nan
new_rows += 1
if pd.notna(src_df.at[d, col]):
if pd.isna(existing.at[d, col]) or is_price_override:
existing.at[d, col] = src_df.at[d, col]
updated_cols += 1
else:
# ๆ–ฐๅˆ—
existing = existing.join(src_df[[col]], how='outer')
existing = existing.sort_index()
# ้‡ๆ–ฐ่ฎก็ฎ—ๆดพ็”Ÿ็‰นๅพ
print(f" ้‡ๆ–ฐ่ฎก็ฎ—ๆดพ็”Ÿ็‰นๅพ...")
try:
from data_pipeline import engineer_features
existing = engineer_features(existing)
except Exception as e:
print(f" โš  engineer_features ่ทณ่ฟ‡: {e}")
# ISM ็Žฏๆฏ”๏ผˆ22ไธช๏ผ‰๏ผšไปŽ ISM ๅญๆŒ‡ๆ•ฐ่‡ชๅŠจ่ฎก็ฎ— MoM
ism_mom_pairs = [
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐ่ฎขๅ•:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐ่ฎขๅ•'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่ฎขๅ•ๅบ“ๅญ˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่‡ชๆœ‰ๅบ“ๅญ˜'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่‡ชๆœ‰ๅบ“ๅญ˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่‡ชๆœ‰ๅบ“ๅญ˜'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฎขๆˆทๅบ“ๅญ˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฎขๆˆทๅบ“ๅญ˜'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่ฟ›ๅฃ:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:่ฟ›ๅฃ'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไบงๅ‡บ:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไบงๅ‡บ'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:็‰ฉไปท:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:็‰ฉไปท'),
('็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฐฑไธš:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๅˆถ้€ ไธšPMI:ๅฐฑไธš'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅ•†ไธšๆดปๅŠจ:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅ•†ไธšๆดปๅŠจ'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐ่ฎขๅ•:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐ่ฎขๅ•'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅฐฑไธš:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅฐฑไธš'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ไพ›ๅบ”ๅ•†ไบคไป˜'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅบ“ๅญ˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅบ“ๅญ˜'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:็‰ฉไปท:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:็‰ฉไปท'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฎขๅ•ๅบ“ๅญ˜:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฎขๅ•ๅบ“ๅญ˜'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๆ–ฐๅ‡บๅฃ่ฎขๅ•'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฟ›ๅฃ:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:่ฟ›ๅฃ'),
('็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅบ“ๅญ˜ๆ™ฏๆฐ”:็Žฏๆฏ”', '็พŽๅ›ฝ:ISM:ๆœๅŠกไธšPMI:ๅบ“ๅญ˜'),
]
ism_count = 0
for mom_col, base_col in ism_mom_pairs:
if base_col in existing.columns:
existing[mom_col] = existing[base_col].diff()
ism_count += 1
if ism_count:
print(f" โœ“ ISM ็Žฏๆฏ”: {ism_count} ไธชๆดพ็”Ÿ็‰นๅพ")
out_path = os.path.join(OUTPUT_DIR, 'panel_monthly_live.csv')
existing.to_csv(out_path)
print(f" โœ“ ๆ›ดๆ–ฐๅŽ้ขๆฟ: {len(existing)} ่กŒ ร— {len(existing.columns)} ๅˆ—")
print(f" โœ“ ๆˆชๆญข: {existing.index[-1].strftime('%Y-%m')}")
print(f" โœ“ ๅทฒไฟๅญ˜: {out_path}")
return existing
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST MODE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def test_apis():
"""ๆต‹่ฏ•ๅ„ API ่ฟž้€šๆ€งใ€‚"""
print("=" * 65)
print("API ่ฟž้€šๆ€งๆต‹่ฏ•")
print("=" * 65)
results = {}
# FRED
try:
from fredapi import Fred
fred = Fred(api_key=FRED_KEY)
d = fred.get_series('DCOILWTICO', observation_start='2026-01-01')
results['FRED'] = f"โœ“ ่ฟž้€š ({len(d)} ๆก)"
except Exception as e:
results['FRED'] = f"โœ— {str(e)[:40]}"
# YFinance
try:
import yfinance as yf
d = yf.download('CL=F', period='5d', progress=False)
results['YFinance'] = f"โœ“ ่ฟž้€š ({len(d)} ๆก)"
except Exception as e:
results['YFinance'] = f"โœ— {str(e)[:40]}"
# World Bank
try:
resp = requests.head("https://thedocs.worldbank.org/en/doc/5d903e848db1d1b83e0ec8f744e55570-0350012021/related/CMO-Historical-Data-Monthly.xlsx",
timeout=10)
results['WorldBank'] = f"โœ“ ๅฏ่ฎฟ้—ฎ (HTTP {resp.status_code})"
except Exception as e:
results['WorldBank'] = f"โœ— {str(e)[:40]}"
# akshare
try:
import akshare as ak
results['akshare'] = f"โœ“ ๅทฒๅฎ‰่ฃ… (v{ak.__version__})"
except Exception as e:
results['akshare'] = f"โœ— {str(e)[:40]}"
# CFTC
try:
year = datetime.now().year
resp = requests.head(f"https://www.cftc.gov/dea/newcot/{year}fut.zip", timeout=10)
results['CFTC'] = f"โœ“ ๅฏ่ฎฟ้—ฎ (HTTP {resp.status_code})"
except Exception as e:
results['CFTC'] = f"โœ— {str(e)[:40]}"
# EIA
try:
resp = requests.get(f"https://api.eia.gov/v2/seriesid/PET.WCESTUS1.W",
params={'api_key': EIA_KEY}, timeout=10)
results['EIA'] = f"โœ“ ่ฟž้€š (HTTP {resp.status_code})"
except Exception as e:
results['EIA'] = f"โœ— {str(e)[:40]}"
# GPR
try:
resp = requests.head("https://www.matteoiacoviello.com/gpr_files/data_gpr_monthly.xls",
timeout=10)
results['GPR'] = f"โœ“ ๅฏ่ฎฟ้—ฎ (HTTP {resp.status_code})"
except Exception as e:
results['GPR'] = f"โœ— {str(e)[:40]}"
print()
for src, status in results.items():
print(f" {src:15s} {status}")
ok = sum(1 for v in results.values() if 'โœ“' in v)
print(f"\n {ok}/{len(results)} ไธชๆ•ฐๆฎๆบๅฏ็”จ")
return results
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# MAIN
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def main(test=False, source=None):
print("=" * 65)
print("ๅ…จ็‰นๅพ API ๆ•ฐๆฎๆ›ดๆ–ฐ")
print("=" * 65)
if test:
test_apis()
return
start = '2000-01-01'
# ๆŒ‰ๆ•ฐๆฎๆบๆ‹‰ๅ–
fred_df = yf_df = wb_df = ak_df = cftc_df = eia_df = gpr_df = None
if source is None or source == 'fred':
fred_df = fetch_fred_bulk(start)
if source is None or source == 'yfinance':
yf_df = fetch_yfinance(start)
if source is None or source == 'worldbank':
wb_df = fetch_worldbank()
if source is None or source == 'akshare':
ak_df = fetch_akshare()
if source is None or source == 'cftc':
cftc_df = fetch_cftc()
if source is None or source == 'eia':
eia_df = fetch_eia()
if source is None or source == 'gpr':
gpr_df = fetch_gpr()
# ๅˆๅนถ
panel = merge_all_sources(fred_df, yf_df, wb_df, ak_df, cftc_df, eia_df, gpr_df)
print(f"\n{'='*65}")
print("โœ… ๆ•ฐๆฎๆ›ดๆ–ฐๅฎŒๆˆ")
print(f"{'='*65}")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ๅ…จ็‰นๅพ API ๆ•ฐๆฎๆ›ดๆ–ฐ')
parser.add_argument('--test', action='store_true', help='ๆต‹่ฏ• API ่ฟž้€šๆ€ง')
parser.add_argument('--source', choices=['fred', 'yfinance', 'worldbank',
'akshare', 'cftc', 'eia', 'gpr'],
help='ๅชๆ›ดๆ–ฐๆŸไธชๆ•ฐๆฎๆบ')
args = parser.parse_args()
main(test=args.test, source=args.source)