0. Process

This report explains how the market data results were produced. The methodology proceeds in two main scripts:

  1. claude_v5.py — Fetches latest prices from Yahoo Finance (yfinance), computes prior-close changes, appends snapshots to Parquet, fetches one year of history, and computes 1D/5D/1M/3M/6M/1Y performance returns. Outputs: claude-market-data_*.csv, claude-market-data_*.md, claude-market-performance_*.csv.

  2. market_ytd.py — Imports the asset list from claude_v5.py, fetches latest prices and prior-close changes, plus the value at the start of the calendar year (nearest available close on or before Jan 1). Outputs: market-ytd_*.csv, market-ytd_*.md.

Both scripts use parallel fetching (ThreadPoolExecutor, max 10 threads) for speed. Data source: Yahoo Finance (yfinance). Full source code is provided in the appendices — copy and paste each script to run it independently (see run instructions above each appendix).

Customising the asset list

Edit the ASSETS list in claude_v5.py (lines 66–147). Each entry: ("Display Name", "Yahoo Symbol"). Examples: ("Bitcoin", "BTC-USD"), ("Apple", "AAPL"). market_ytd.py imports from claude_v5.py. Bond yields: CBOE symbols (^TNX). International bonds: ETF proxies (IGLT.L, 2561.T).


1. Latest prices

Latest price, prior close, % change.

Order: US Treasury yields first in yield-curve order (13W → 5Y → 10Y → 30Y); remaining assets by latest timestamp (most recent first).

Latest prices and prior-close change
Source: claude_v5.py — claude-market-data_20260312_190024.csv
Asset Latest Price Prior Close % Chg Opens (ET) Closes (ET) Source
US 13W T-Bill Yield 2026-03-11 14:59 ET 3.600000 3.595000 N/A (yield) 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 5Y Treasury Yield 2026-03-11 14:59 ET 3.782000 3.715000 N/A (yield) 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 10Y Treasury Yield 2026-03-11 14:59 ET 4.208000 4.136000 N/A (yield) 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 30Y Treasury Yield 2026-03-11 14:59 ET 4.857000 4.771000 N/A (yield) 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
USD/JPY 2026-03-12 06:59 ET 158.709000 159.074000 -0.23 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
USD/CHF 2026-03-12 06:59 ET 0.780820 0.781780 -0.12 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
AUD/USD 2026-03-12 06:58 ET 0.713725 0.713114 0.09 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
USD/CNY 2026-03-12 06:58 ET 6.869600 6.865000 0.07 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
GBP/USD 2026-03-12 06:58 ET 1.339400 1.338200 0.09 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
EUR/USD 2026-03-12 06:58 ET 1.156100 1.154600 0.13 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Bitcoin 2026-03-12 06:57 ET 70432.110000 70190.370000 0.34 24/7 24/7 Yahoo Finance (yfinance)
Solana 2026-03-12 06:57 ET 86.728500 86.610300 0.14 24/7 24/7 Yahoo Finance (yfinance)
Ethereum 2026-03-12 06:56 ET 2071.250000 2050.460000 1.01 24/7 24/7 Yahoo Finance (yfinance)
XRP 2026-03-12 06:56 ET 1.389900 1.384900 0.36 24/7 24/7 Yahoo Finance (yfinance)
US Dollar Index (DXY) 2026-03-12 06:49 ET 99.327000 99.474000 -0.15 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Copper 2026-03-12 06:49 ET 5.887000 5.838500 0.83 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
WTI Crude Oil 2026-03-12 06:49 ET 90.950000 94.490000 -3.75 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Wheat 2026-03-12 06:49 ET 600.500000 600.500000 0.00 7:00 PM Sun 1:20 PM Fri Yahoo Finance (yfinance)
Silver 2026-03-12 06:49 ET 87.315000 85.060000 2.65 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Brent Crude Oil 2026-03-12 06:49 ET 92.710000 95.970000 -3.40 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Gold 2026-03-12 06:49 ET 5188.100000 5158.200000 0.58 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Natural Gas 2026-03-12 06:49 ET 3.219000 3.257000 -1.17 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Corn 2026-03-12 06:48 ET 465.000000 463.500000 0.32 7:00 PM Sun 1:20 PM Fri Yahoo Finance (yfinance)
DAX 2026-03-12 06:44 ET 23608.470000 23587.330000 0.09 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
CAC 40 2026-03-12 06:44 ET 8006.500000 8014.990000 -0.11 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
FTSE 100 2026-03-12 06:44 ET 10311.000000 10318.920000 -0.08 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
iShares UK Gilts ETF 2026-03-12 06:39 ET 9.875000 9.892500 -0.18 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
Hongkong Land 2026-03-12 04:59 ET 8.450000 8.350000 1.20 8:00 PM Sun ~4:00 AM Fri Yahoo Finance (yfinance)
Hang Seng 2026-03-12 04:08 ET 25716.760000 25898.760000 -0.70 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Swire Pacific 2026-03-12 04:08 ET 82.750000 80.400000 2.92 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Hysan Development 2026-03-12 04:08 ET 18.420000 18.810000 -2.07 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Sino Land 2026-03-12 04:08 ET 11.280000 11.700000 -3.59 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Nikkei 225 2026-03-12 02:29 ET 54384.470000 54984.460000 -1.09 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
Suzuki Motor 2026-03-12 02:24 ET 2034.500000 2099.000000 -3.07 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
Toyota Motor 2026-03-12 02:24 ET 3451.000000 3521.000000 -1.99 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
iShares JPY Govt Bond 2026-03-12 01:40 ET 2093.000000 2099.000000 -0.29 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
ASX 200 2026-03-12 01:11 ET 8629.000000 8743.500000 -1.31 ~6:00 PM Sun ~12:00 AM Fri Yahoo Finance (yfinance)
BHP Group 2026-03-12 01:11 ET 50.980000 51.960000 -1.89 ~6:00 PM Sun ~12:00 AM Fri Yahoo Finance (yfinance)
NZX 50 2026-03-11 23:46 ET 13181.770000 13281.050000 -0.75 ~4:00 PM Sun ~11:45 PM Thu Yahoo Finance (yfinance)
VIX (Fear Index) 2026-03-11 15:59 ET 24.320000 24.230000 N/A (yield) 9:30 AM Mon 4:15 PM Fri Yahoo Finance (yfinance)
S&P 500 2026-03-11 15:59 ET 6774.760000 6781.480000 -0.10 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Dow Jones 2026-03-11 15:59 ET 47414.250000 47706.510000 -0.61 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
NASDAQ Composite 2026-03-11 15:59 ET 22713.000000 22697.100000 0.07 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Russell 2000 2026-03-11 15:59 ET 2542.660000 2548.080000 -0.21 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Microsoft 2026-03-11 15:59 ET 404.820000 402.100000 0.68 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Apple 2026-03-11 15:59 ET 260.810000 259.300000 0.58 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
NVIDIA 2026-03-11 15:59 ET 186.010000 184.420000 0.86 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Meta 2026-03-11 15:59 ET 654.700000 648.410000 0.97 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Amazon 2026-03-11 15:59 ET 212.640000 210.720000 0.91 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Tesla 2026-03-11 15:59 ET 407.840000 404.810000 0.75 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
ProShares Bitcoin ETF 2026-03-11 15:59 ET 9.735000 9.670000 0.67 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Lululemon 2026-03-11 15:59 ET 162.800000 162.550000 0.15 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Alphabet (Google) 2026-03-11 15:59 ET 308.710000 305.800000 0.95 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
JPMorgan Chase 2026-03-11 15:59 ET 287.420000 284.500000 1.03 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
SPDR Gold Shares 2026-03-11 15:59 ET 476.260000 471.600000 0.99 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)

2. Performance returns

1D, 5D, 1M, 3M, 6M, 1Y % returns from historical daily data.

Order: By 1Y return descending (best performers first). Blank/— = insufficient history (e.g. fewer than 252 trading days for 1Y).

Performance returns (1D to 1Y)
Source: claude_v5.py — claude-market-performance_20260312_190024.csv
Asset Symbol Latest 1D % 5D % 1M % 3M % 6M % 1Y %
Silver SI=F 87.290000 2.62 6.86 8.82 45.07 112.21 160.69
Hongkong Land H78.SI 8.490000 1.07 4.17 1.43 29.03 33.28 104.02
Alphabet (Google) GOOGL 308.700000 0.54 1.91 -4.75 -1.53 29 86.75
Tesla TSLA 407.820000 2.15 0.46 -2.28 -7.23 17.54 83.58
SPDR Gold Shares GLD 476.240000 -0.34 0.94 1.97 23.56 42.56 79.01
Gold GC=F 5188.600000 0.41 2.43 3.69 23.34 42.4 76.54
NVIDIA NVDA 186.030000 0.69 1.64 -2.1 0.26 8.96 73.95
Hysan Development 0014.HK 18.420000 -2.07 -6.59 -13.49 7.29 26.77 55.46
Sino Land 0083.HK 11.280000 -2.34 -2.34 -5.64 7.7 24.52 54.12
Nikkei 225 ^N225 54452.960000 -1.04 -1.49 -3.39 7.85 28.7 46.55
BHP Group BHP.AX 50.980000 -1.89 -7.56 1.72 17.27 29 38.87
WTI Crude Oil CL=F 90.900000 4.18 12.21 42.12 56.05 42.77 34.31
Toyota Motor 7203.T 3467.000000 -1.23 -0.4 -7.03 14.35 22.7 33.27
Swire Pacific 0019.HK 82.750000 1.85 1.1 5.41 24.81 26.75 32.21
Brent Crude Oil BZ=F 92.710000 0.79 8.55 34.75 49.68 37.37 30.67
NASDAQ Composite ^IXIC 22716.130000 0.08 -0.4 -2.25 -3.52 3.82 30.04
JPMorgan Chase JPM 287.520000 -0.42 -3.96 -10.74 -8.37 -2.56 26.35
Russell 2000 ^RUT 2542.900000 -0.2 -3.53 -5.44 0.87 6.76 25.94
Copper HG=F 5.886500 0.7 2.32 -0.16 12.33 29.37 22.1
FTSE 100 ^FTSE 10311.970000 -0.4 -0.98 -1.53 6.8 11.08 20.71
S&P 500 ^GSPC 6775.800000 -0.08 -1.36 -2.71 -1.03 4.04 20.68
Apple AAPL 260.810000 -0.01 -0.65 -5.03 -6.06 11.5 15.15
Suzuki Motor 7269.T 2036.500000 -2.77 -2.16 -9.93 -11.19 2.2 14.2
Dow Jones ^DJI 47417.270000 -0.61 -2.71 -5.42 -0.67 3.73 13.14
AUD/USD AUDUSD=X 0.713725 0.23 0.87 0.88 6.93 7.34 12.2
Hang Seng ^HSI 25716.760000 -0.7 1.56 -3.17 -1.41 2.63 12.1
ASX 200 ^AXJO 8629.000000 -1.31 -3.48 -4.28 0.5 -2 11.35
Wheat ZW=F 600.250000 2 3 13.63 11.93 21.26 10.95
Meta META 654.860000 0.12 -1.93 -3.3 -1.71 -14.35 9.86
Amazon AMZN 212.650000 -0.78 -1.92 1.88 -6.28 -10.74 9.31
Microsoft MSFT 404.880000 -0.22 -0.08 -1.88 -17.35 -18.43 7.32
US 30Y Treasury Yield ^TYX 4.857000 1.8 2.99 0.19 0.85 2.95 7.01
USD/JPY JPY=X 158.718000 0.38 1.11 2.74 1.88 7.48 6.2
EUR/USD EURUSD=X 1.156200 -0.42 -0.63 -2.76 -1.19 -1.4 5.69
NZX 50 ^NZ50 13199.290000 -0.71 -2.54 -2.48 -2.29 -0.41 5.46
DAX ^GDAXI 23611.170000 -0.12 -0.86 -5.01 -1.81 -0.09 4.63
iShares UK Gilts ETF IGLT.L 9.875000 -0.2 -1.05 -0.93 -0.23 1.93 4.41
GBP/USD GBPUSD=X 1.339400 -0.19 0.18 -1.77 0.04 -1.18 3.02
Corn ZC=F 465.000000 4.67 5.32 8.45 5.5 17.05 1.86
US 10Y Treasury Yield ^TNX 4.208000 1.74 3.14 0.24 0.86 3.29 -0.12
CAC 40 ^FCHI 8006.750000 -0.44 -0.49 -3.69 -0.2 2.32 -0.83
US Dollar Index (DXY) DX-Y.NYB 99.332000 0.1 0.01 2.62 0.11 1.59 -4.13
US 5Y Treasury Yield ^FVX 3.782000 1.8 3.11 1.1 0.77 5.03 -4.83
USD/CNY USDCNY=X 6.869600 -0.1 -0.4 -0.59 -2.75 -3.57 -4.95
VIX (Fear Index) ^VIX 25.240000 4.17 6.27 41.88 49.08 64.43 -6.24
iShares JPY Govt Bond 2561.T 2091.000000 -0.9 -0.57 1.11 -1.22 -3.54 -7.6
USD/CHF CHF=X 0.780810 0.29 0.21 1.59 -2.32 -1.99 -10.94
US 13W T-Bill Yield ^IRX 3.600000 0.14 0.14 0.19 -0.5 -8.7 -13.98
ProShares Bitcoin ETF BITO 9.730000 0.83 -3.38 -0.36 -22.77 -37.79 -14.43
Ethereum ETH-USD 2071.210000 0.96 5.17 6.31 -33.28 -37.47 -20.06
Natural Gas NG=F 3.216000 0.22 7.09 3.24 -29.69 6.17 -21.25
Bitcoin BTC-USD 70430.220000 0.32 4.69 5.19 -22.63 -30.47 -35.77
XRP XRP-USD 1.390100 0.38 2.48 -1.16 -34.49 -37.12 -38.43
Solana SOL-USD 86.745600 0.21 4.29 5.28 -37.27 -44.06 -43.08
Lululemon LULU 162.790000 -2.19 -6.02 -7.34 -11.06 -1.75 -51.23

3. Prior close and YTD

Latest price, prior close, % change, and year-to-date (YTD) change from the nearest available close on or before 1 January to latest.

Order: By latest timestamp (most recent first).

Prior close and YTD change
Source: market_ytd.py — market-ytd_20260312_173752.csv
Asset Symbol Latest Price Prior Close % Chg YTD Date YTD Value YTD % Chg Opens Closes Source
USD/CHF CHF=X 2026-03-12 05:37 ET 0.780820 0.781780 -0.12 2025-12-31 0.7917 -1.37 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
USD/CNY USDCNY=X 2026-03-12 05:37 ET 6.872300 6.865000 0.11 2025-12-31 6.9961 -1.77 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
USD/JPY JPY=X 2026-03-12 05:37 ET 158.830000 159.074000 -0.15 2025-12-31 156.4130 1.55 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
AUD/USD AUDUSD=X 2026-03-12 05:36 ET 0.713419 0.713114 0.04 2025-12-31 0.6698 6.51 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
GBP/USD GBPUSD=X 2026-03-12 05:36 ET 1.338800 1.338200 0.04 2025-12-31 1.3467 -0.59 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
EUR/USD EURUSD=X 2026-03-12 05:36 ET 1.156100 1.154600 0.13 2025-12-31 1.1747 -1.59 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Solana SOL-USD 2026-03-12 05:35 ET 85.708600 86.610300 -1.04 2026-01-01 126.7611 -32.39 24/7 24/7 Yahoo Finance (yfinance)
Bitcoin BTC-USD 2026-03-12 05:35 ET 69795.990000 70190.370000 -0.56 2026-01-01 88731.9800 -21.34 24/7 24/7 Yahoo Finance (yfinance)
Ethereum ETH-USD 2026-03-12 05:34 ET 2041.480000 2050.460000 -0.44 2026-01-01 3000.3900 -31.96 24/7 24/7 Yahoo Finance (yfinance)
XRP XRP-USD 2026-03-12 05:34 ET 1.377200 1.384900 -0.55 2026-01-01 1.8779 -26.66 24/7 24/7 Yahoo Finance (yfinance)
US Dollar Index (DXY) DX-Y.NYB 2026-03-12 05:27 ET 99.349000 99.474000 -0.13 2025-12-31 98.2800 1.09 5:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Gold GC=F 2026-03-12 05:27 ET 5185.800000 5158.200000 0.54 2025-12-31 4325.6000 19.89 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Brent Crude Oil BZ=F 2026-03-12 05:27 ET 94.420000 95.970000 -1.62 2025-12-31 60.8500 55.17 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Copper HG=F 2026-03-12 05:27 ET 5.889000 5.838500 0.86 2025-12-31 5.6300 4.60 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Silver SI=F 2026-03-12 05:27 ET 87.065000 85.060000 2.36 2025-12-31 70.1340 24.14 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
WTI Crude Oil CL=F 2026-03-12 05:27 ET 92.780000 94.490000 -1.81 2025-12-31 57.4200 61.58 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Natural Gas NG=F 2026-03-12 05:27 ET 3.265000 3.257000 0.25 2025-12-31 3.6860 -11.42 6:00 PM Sun 5:00 PM Fri Yahoo Finance (yfinance)
Corn ZC=F 2026-03-12 05:27 ET 465.000000 463.500000 0.32 2025-12-31 440.2500 5.62 7:00 PM Sun 1:20 PM Fri Yahoo Finance (yfinance)
Wheat ZW=F 2026-03-12 05:27 ET 602.000000 600.500000 0.25 2025-12-31 507.0000 18.74 7:00 PM Sun 1:20 PM Fri Yahoo Finance (yfinance)
DAX ^GDAXI 2026-03-12 05:22 ET 23493.710000 23587.330000 -0.40 2025-12-30 24490.4100 -4.07 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
CAC 40 ^FCHI 2026-03-12 05:22 ET 7978.550000 8014.990000 -0.45 2025-12-31 8149.5000 -2.10 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
FTSE 100 ^FTSE 2026-03-12 05:22 ET 10288.940000 10318.920000 -0.29 2025-12-31 9931.4000 3.60 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
iShares UK Gilts ETF IGLT.L 2026-03-12 05:16 ET 9.867500 9.892500 -0.25 2025-12-31 9.9125 -0.45 3:00 AM Mon 11:30 AM Fri Yahoo Finance (yfinance)
Hongkong Land H78.SI 2026-03-12 04:59 ET 8.450000 8.350000 1.20 2025-12-31 6.9500 21.58 8:00 PM Sun ~4:00 AM Fri Yahoo Finance (yfinance)
Hang Seng ^HSI 2026-03-12 04:08 ET 25716.760000 25898.760000 -0.70 2025-12-31 25630.5400 0.34 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Hysan Development 0014.HK 2026-03-12 04:08 ET 18.420000 18.810000 -2.07 2025-12-31 18.0601 1.99 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Swire Pacific 0019.HK 2026-03-12 04:08 ET 82.750000 80.400000 2.92 2025-12-31 62.7000 31.98 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
HK & China Gas 0083.HK 2026-03-12 04:08 ET 11.280000 11.700000 -3.59 2025-12-31 10.0791 11.91 8:30 PM Sun 3:00 AM Fri Yahoo Finance (yfinance)
Nikkei 225 ^N225 2026-03-12 02:29 ET 54384.470000 54984.460000 -1.09 2025-12-30 50339.4800 8.04 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
Suzuki Motor 7269.T 2026-03-12 02:24 ET 2034.500000 2099.000000 -3.07 2025-12-30 2334.5000 -12.85 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
Toyota Motor 7203.T 2026-03-12 02:24 ET 3451.000000 3521.000000 -1.99 2025-12-30 3356.0000 2.83 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
iShares JPY Govt Bond 2561.T 2026-03-12 01:40 ET 2093.000000 2099.000000 -0.29 2025-12-30 2098.9700 -0.28 7:00 PM Sun 2:00 AM Fri Yahoo Finance (yfinance)
ASX 200 ^AXJO 2026-03-12 01:11 ET 8629.000000 8743.500000 -1.31 2025-12-31 8714.3000 -0.98 ~6:00 PM Sun ~12:00 AM Fri Yahoo Finance (yfinance)
BHP Group BHP.AX 2026-03-12 01:11 ET 50.980000 51.960000 -1.89 2025-12-31 44.6416 14.20 ~6:00 PM Sun ~12:00 AM Fri Yahoo Finance (yfinance)
NZX 50 ^NZ50 2026-03-11 23:46 ET 13181.770000 13281.050000 -0.75 2025-12-31 13548.4200 -2.71 ~4:00 PM Sun ~11:45 PM Thu Yahoo Finance (yfinance)
VIX (Fear Index) ^VIX 2026-03-11 15:59 ET 24.320000 24.230000 N/A (yield) 2025-12-31 14.9500 62.68 9:30 AM Mon 4:15 PM Fri Yahoo Finance (yfinance)
S&P 500 ^GSPC 2026-03-11 15:59 ET 6774.760000 6781.480000 -0.10 2025-12-31 6845.5000 -1.03 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Dow Jones ^DJI 2026-03-11 15:59 ET 47414.250000 47706.510000 -0.61 2025-12-31 48063.2900 -1.35 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
NASDAQ Composite ^IXIC 2026-03-11 15:59 ET 22713.000000 22697.100000 0.07 2025-12-31 23241.9900 -2.28 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Russell 2000 ^RUT 2026-03-11 15:59 ET 2542.660000 2548.080000 -0.21 2025-12-31 2481.9100 2.45 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Apple AAPL 2026-03-11 15:59 ET 260.810000 259.300000 0.58 2025-12-31 271.6058 -3.97 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Amazon AMZN 2026-03-11 15:59 ET 212.640000 210.720000 0.91 2025-12-31 230.8200 -7.88 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Meta META 2026-03-11 15:59 ET 654.700000 648.410000 0.97 2025-12-31 660.0900 -0.82 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
NVIDIA NVDA 2026-03-11 15:59 ET 186.010000 184.420000 0.86 2025-12-31 186.4899 -0.26 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Alphabet (Google) GOOGL 2026-03-11 15:59 ET 308.710000 305.800000 0.95 2025-12-31 312.7798 -1.30 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Tesla TSLA 2026-03-11 15:59 ET 407.840000 404.810000 0.75 2025-12-31 449.7200 -9.31 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
ProShares Bitcoin ETF BITO 2026-03-11 15:59 ET 9.735000 9.670000 0.67 2025-12-31 12.1307 -19.75 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Lululemon LULU 2026-03-11 15:59 ET 162.800000 162.550000 0.15 2025-12-31 207.8100 -21.66 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
Microsoft MSFT 2026-03-11 15:59 ET 404.820000 402.100000 0.68 2025-12-31 482.5187 -16.10 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
SPDR Gold Shares GLD 2026-03-11 15:59 ET 476.260000 471.600000 0.99 2025-12-31 396.3100 20.17 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
JPMorgan Chase JPM 2026-03-11 15:59 ET 287.420000 284.500000 1.03 2025-12-31 320.7731 -10.40 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 13W T-Bill Yield ^IRX 2026-03-11 14:59 ET 3.600000 3.595000 N/A (yield) 2025-12-31 3.5470 1.49 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 10Y Treasury Yield ^TNX 2026-03-11 14:59 ET 4.208000 4.136000 N/A (yield) 2025-12-31 4.1630 1.08 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 30Y Treasury Yield ^TYX 2026-03-11 14:59 ET 4.857000 4.771000 N/A (yield) 2025-12-31 4.8400 0.35 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)
US 5Y Treasury Yield ^FVX 2026-03-11 14:59 ET 3.782000 3.715000 N/A (yield) 2025-12-31 3.7220 1.61 9:30 AM Mon 4:00 PM Fri Yahoo Finance (yfinance)

Appendix 1: claude_v5

To run independently: Copy the entire code block below, save as claude_v5.py, then run python claude_v5.py from a terminal. Prerequisites: pip install yfinance (optionally pandas pyarrow for Parquet storage). Outputs go to a reports/ folder created automatically.

#!/usr/bin/env python3
"""
Fetch latest market data from Yahoo Finance (open source, free).
Outputs MD and CSV tables with: Asset, Latest Date/Time, Latest Price, Prior Close, % Chg, Opens, Closes, Source.

Output files: reports/claude-market-data_YYYYMMDD_HHMMSS.csv, reports/claude-market-data_YYYYMMDD_HHMMSS.md,
             reports/claude-market-performance_YYYYMMDD_HHMMSS.csv (with latest_price, 1D/5D/1M/3M/6M/1Y %)

Improvements over v1:
  - Bond yields (US 2Y/10Y/30Y, UK, Germany, Japan)
  - VIX, DXY, more commodities, more crypto
  - Parallel fetching via ThreadPoolExecutor (~5–10x faster)
  - Robust prior-close logic (fast_info first, daily fallback)
  - Fixed naive-datetime TZ assumption (daily bars are date-only)
  - sort_key not exposed in public API
  - fmt_price handles negatives correctly

Usage from Python:
    from fetch_market_data import get_prices
    prices = get_prices()
    # list of dicts: asset, symbol, latest_datetime, latest_price,
    #                prior_close, pct_change, opens, closes, source
"""

import csv
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple

try:
    import pytz
    ET = pytz.timezone("America/New_York")
except ImportError:
    try:
        from dateutil import tz
        ET = tz.gettz("America/New_York")
    except ImportError:
        ET = None

try:
    import yfinance as yf
except ImportError:
    print("Installing yfinance...")
    import subprocess
    subprocess.check_call(["pip", "install", "yfinance", "-q"])
    import yfinance as yf

try:
    import pandas as pd
    _HAS_PANDAS = True
except ImportError:
    _HAS_PANDAS = False

try:
    import pyarrow
    _HAS_PARQUET = True
except ImportError:
    _HAS_PARQUET = False

# ---------------------------------------------------------------------------
# Asset universe — ordered by market open chronology (ET)
# Sections: 24/7 | Bond Yields | Forex | Commodities | Volatility/Dollar |
#           Asia-Pacific Indices | European Indices | US Indices | US Megacaps
# ---------------------------------------------------------------------------
ASSETS = [
    # 24/7 — never closes
    ("Bitcoin",                 "BTC-USD"),
    ("Ethereum",                "ETH-USD"),
    ("Solana",                  "SOL-USD"),
    ("XRP",                     "XRP-USD"),

    # Bond Yields — only CBOE ^-series work reliably on Yahoo Finance
    # International =X yield tickers (GB10Y=X, DE10Y=X, JP10Y=X) are broken/delisted.
    # Use liquid bond ETFs as proxies for UK, EU, Japan instead.
    ("US 13W T-Bill Yield",     "^IRX"),    # short end
    ("US 5Y Treasury Yield",    "^FVX"),
    ("US 10Y Treasury Yield",   "^TNX"),
    ("US 30Y Treasury Yield",   "^TYX"),
    ("iShares UK Gilts ETF",    "IGLT.L"),  # UK gilt proxy (LSE-listed)
    ("iShares EUR Govt Bond",   "IBGM.DE"), # EUR/Bund proxy (XETRA)
    ("iShares JPY Govt Bond",   "2561.T"),  # Japan JGB ETF (TSE)

    # Forex — opens 5:00 PM Sun ET
    ("EUR/USD",                 "EURUSD=X"),
    ("GBP/USD",                 "GBPUSD=X"),
    ("USD/JPY",                 "JPY=X"),
    ("USD/CHF",                 "CHF=X"),
    ("AUD/USD",                 "AUDUSD=X"),
    ("USD/CNY",                 "USDCNY=X"),

    # USD Index & Volatility
    ("US Dollar Index (DXY)",   "DX-Y.NYB"),
    ("VIX (Fear Index)",        "^VIX"),

    # NZX (New Zealand) — opens ~4:00–5:00 PM Sun ET
    ("NZX 50",                  "^NZ50"),

    # CME Globex — opens 6:00 PM Sun ET
    ("WTI Crude Oil",           "CL=F"),
    ("Brent Crude Oil",         "BZ=F"),
    ("Gold",                    "GC=F"),
    ("Silver",                  "SI=F"),
    ("Copper",                  "HG=F"),
    ("Natural Gas",             "NG=F"),
    ("Corn",                    "ZC=F"),
    ("Wheat",                   "ZW=F"),

    # ASX (Australia) — opens ~6:00–7:00 PM Sun ET
    ("ASX 200",                 "^AXJO"),

    # Tokyo (TSE) — opens 7:00 PM Sun ET
    ("Nikkei 225",              "^N225"),

    # Hong Kong (HKEX) — opens 8:30 PM Sun ET
    ("Hang Seng",               "^HSI"),

    # London / Europe — opens 3:00 AM Mon ET
    ("FTSE 100",                "^FTSE"),
    ("DAX",                     "^GDAXI"),
    ("CAC 40",                  "^FCHI"),

    # New York — opens 9:30 AM Mon ET
    ("S&P 500",                 "^GSPC"),
    ("Dow Jones",               "^DJI"),
    ("NASDAQ Composite",        "^IXIC"),
    ("Russell 2000",            "^RUT"),
    ("Apple",                   "AAPL"),
    ("Microsoft",               "MSFT"),
    ("NVIDIA",                  "NVDA"),
    ("Amazon",                  "AMZN"),
    ("Alphabet (Google)",       "GOOGL"),
    ("Tesla",                   "TSLA"),
    ("Meta",                    "META"),

    # Additional stocks
    ("BHP Group",               "BHP.AX"),
    ("ProShares Bitcoin ETF",   "BITO"),
    ("Lululemon",              "LULU"),
    ("Hongkong Land",           "H78.SI"),
    ("Suzuki Motor",           "7269.T"),
    ("Toyota Motor",           "7203.T"),
    ("Swire Pacific",          "0019.HK"),
    ("Hysan Development",      "0014.HK"),
    ("JPMorgan Chase",         "JPM"),
    ("Sino Land",              "0083.HK"),
    ("SPDR Gold Shares",       "GLD"),
]

SOURCE = "Yahoo Finance (yfinance)"

# Trading hours (ET) per asset
TRADING_HOURS: Dict[str, Tuple[str, str]] = {
    "Bitcoin":                  ("24/7", "24/7"),
    "Ethereum":                 ("24/7", "24/7"),
    "Solana":                   ("24/7", "24/7"),
    "XRP":                      ("24/7", "24/7"),
    # Bond yields
    "US 13W T-Bill Yield":      ("9:30 AM Mon", "4:00 PM Fri"),
    "US 5Y Treasury Yield":     ("9:30 AM Mon", "4:00 PM Fri"),
    "US 10Y Treasury Yield":    ("9:30 AM Mon", "4:00 PM Fri"),
    "US 30Y Treasury Yield":    ("9:30 AM Mon", "4:00 PM Fri"),
    "iShares UK Gilts ETF":     ("3:00 AM Mon", "11:30 AM Fri"),
    "iShares EUR Govt Bond":    ("3:00 AM Mon", "11:30 AM Fri"),
    "iShares JPY Govt Bond":    ("7:00 PM Sun", "2:00 AM Fri"),
    "EUR/USD":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "GBP/USD":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "USD/JPY":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "USD/CHF":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "AUD/USD":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "USD/CNY":                  ("5:00 PM Sun", "5:00 PM Fri"),
    "US Dollar Index (DXY)":    ("5:00 PM Sun", "5:00 PM Fri"),
    "VIX (Fear Index)":         ("9:30 AM Mon", "4:15 PM Fri"),
    "NZX 50":                   ("~4:00 PM Sun", "~11:45 PM Thu"),
    "WTI Crude Oil":            ("6:00 PM Sun", "5:00 PM Fri"),
    "Brent Crude Oil":          ("6:00 PM Sun", "5:00 PM Fri"),
    "Gold":                     ("6:00 PM Sun", "5:00 PM Fri"),
    "Silver":                   ("6:00 PM Sun", "5:00 PM Fri"),
    "Copper":                   ("6:00 PM Sun", "5:00 PM Fri"),
    "Natural Gas":              ("6:00 PM Sun", "5:00 PM Fri"),
    "Corn":                     ("7:00 PM Sun", "1:20 PM Fri"),
    "Wheat":                    ("7:00 PM Sun", "1:20 PM Fri"),
    "ASX 200":                  ("~6:00 PM Sun", "~12:00 AM Fri"),
    "Nikkei 225":               ("7:00 PM Sun", "2:00 AM Fri"),
    "Hang Seng":                ("8:30 PM Sun", "3:00 AM Fri"),
    "FTSE 100":                 ("3:00 AM Mon", "11:30 AM Fri"),
    "DAX":                      ("3:00 AM Mon", "11:30 AM Fri"),
    "CAC 40":                   ("3:00 AM Mon", "11:30 AM Fri"),
    "S&P 500":                  ("9:30 AM Mon", "4:00 PM Fri"),
    "Dow Jones":                ("9:30 AM Mon", "4:00 PM Fri"),
    "NASDAQ Composite":         ("9:30 AM Mon", "4:00 PM Fri"),
    "Russell 2000":             ("9:30 AM Mon", "4:00 PM Fri"),
    "Apple":                    ("9:30 AM Mon", "4:00 PM Fri"),
    "Microsoft":                ("9:30 AM Mon", "4:00 PM Fri"),
    "NVIDIA":                   ("9:30 AM Mon", "4:00 PM Fri"),
    "Amazon":                   ("9:30 AM Mon", "4:00 PM Fri"),
    "Alphabet (Google)":        ("9:30 AM Mon", "4:00 PM Fri"),
    "Tesla":                    ("9:30 AM Mon", "4:00 PM Fri"),
    "Meta":                     ("9:30 AM Mon", "4:00 PM Fri"),
    "BHP Group":                ("~6:00 PM Sun", "~12:00 AM Fri"),
    "ProShares Bitcoin ETF":    ("9:30 AM Mon", "4:00 PM Fri"),
    "Lululemon":                ("9:30 AM Mon", "4:00 PM Fri"),
    "Hongkong Land":            ("8:00 PM Sun", "~4:00 AM Fri"),
    "Suzuki Motor":             ("7:00 PM Sun", "2:00 AM Fri"),
    "Toyota Motor":             ("7:00 PM Sun", "2:00 AM Fri"),
    "Swire Pacific":            ("8:30 PM Sun", "3:00 AM Fri"),
    "Hysan Development":        ("8:30 PM Sun", "3:00 AM Fri"),
    "JPMorgan Chase":           ("9:30 AM Mon", "4:00 PM Fri"),
    "Sino Land":                ("8:30 PM Sun", "3:00 AM Fri"),
    "SPDR Gold Shares":         ("9:30 AM Mon", "4:00 PM Fri"),
}

# Assets whose values are already in % (yields) — suppress pct-change, show as X.XXX%
YIELD_ASSETS = {
    "US 13W T-Bill Yield", "US 5Y Treasury Yield",
    "US 10Y Treasury Yield", "US 30Y Treasury Yield",
    "VIX (Fear Index)",
}
# ETF proxies — shown as regular prices (not yields), but grouped with bonds
BOND_ETF_ASSETS = {
    "iShares UK Gilts ETF", "iShares EUR Govt Bond", "iShares JPY Govt Bond",
}

MAX_WORKERS = 10  # parallel fetch threads


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _to_et(ts) -> object:
    """Convert a pandas Timestamp (tz-aware or naive-UTC) to ET. Returns as-is on failure."""
    if ET is None:
        return ts
    try:
        import pandas as pd
        ts = pd.Timestamp(ts)
        if ts.tzinfo is None:
            # Daily bars have no time component — treat as date only, no TZ conversion
            return ts
        return ts.astimezone(ET)
    except Exception:
        return ts


def fmt_price(v: float, commas: bool = False) -> str:
    """Format price with appropriate decimal places. Handles negatives correctly."""
    abs_v = abs(v)
    if abs_v >= 1000:
        formatted = f"{v:,.2f}" if commas else f"{v:.2f}"
    elif abs_v >= 1:
        formatted = f"{v:.4f}"
    elif abs_v == 0:
        formatted = "0.00"
    else:
        formatted = f"{v:.6f}"
    return formatted


# ---------------------------------------------------------------------------
# Core fetch
# ---------------------------------------------------------------------------

def fetch_asset_data(name: str, symbol: str) -> Optional[dict]:
    """
    Fetch latest price, prior close, and timestamp for one asset.

    Prior-close priority:
      1. fast_info.previous_close  (most reliable for stocks/ETFs)
      2. hist_daily.iloc[-2]       (fallback for futures/forex/yields)
      3. last_price itself         (last resort — pct_change will be 0)
    """
    try:
        ticker = yf.Ticker(symbol)

        # --- price history --------------------------------------------------
        hist_intra = ticker.history(period="1d", interval="1m")
        hist_daily = ticker.history(period="5d")

        if not hist_intra.empty:
            hist = hist_intra
            use_intra = True
        elif not hist_daily.empty:
            hist = hist_daily
            use_intra = False
        else:
            print(f"  ✗ {name}: no data returned")
            return None

        last_price = float(hist["Close"].iloc[-1])
        raw_dt = hist.index[-1]

        # --- timestamp → ET -------------------------------------------------
        last_dt_et = _to_et(raw_dt)

        if hasattr(last_dt_et, "tzinfo") and last_dt_et.tzinfo is not None:
            dt_str = last_dt_et.strftime("%Y-%m-%d %H:%M ET")
            sort_key = last_dt_et.strftime("%Y-%m-%dT%H:%M:%S")
        elif hasattr(last_dt_et, "strftime"):
            # Date-only (daily bar) — no time conversion needed
            dt_str = last_dt_et.strftime("%Y-%m-%d")
            sort_key = last_dt_et.strftime("%Y-%m-%dT00:00:00")
        else:
            dt_str = str(last_dt_et)[:16].replace("T", " ")
            sort_key = dt_str.replace(" ", "T")

        # --- prior close ----------------------------------------------------
        prev_close: Optional[float] = None

        # 1. fast_info (best for stocks; often None for futures/forex)
        try:
            fi_prev = getattr(ticker.fast_info, "previous_close", None)
            if fi_prev is not None:
                prev_close = float(fi_prev)
        except Exception:
            pass

        # 2. daily history fallback
        if prev_close is None and not hist_daily.empty and len(hist_daily) >= 2:
            prev_close = float(hist_daily["Close"].iloc[-2])

        # 3. last resort
        if prev_close is None:
            prev_close = last_price

        pct_change = ((last_price - prev_close) / prev_close * 100) if prev_close else 0.0
        opens, closes = TRADING_HOURS.get(name, ("—", "—"))
        is_yield = name in YIELD_ASSETS
        is_bond_etf = name in BOND_ETF_ASSETS

        return {
            "asset":            name,
            "symbol":           symbol,
            "latest_datetime":  dt_str,
            "_sort_key":        sort_key,   # internal — stripped before public API return
            "latest_price":     last_price,
            "prior_close":      prev_close,
            "pct_change":       pct_change,
            "is_yield":         is_yield,
            "is_bond_etf":      is_bond_etf,
            "opens":            opens,
            "closes":           closes,
            "source":           SOURCE,
        }

    except Exception as e:
        print(f"  ✗ {name} ({symbol}): {e}")
        return None


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def get_prices(max_workers: int = MAX_WORKERS) -> List[dict]:
    """
    Fetch all asset prices in parallel. Returns list of dicts sorted by
    latest_datetime descending (most recent first). Internal sort key is
    removed before returning.
    """
    rows: List[dict] = []
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = {pool.submit(fetch_asset_data, name, sym): name for name, sym in ASSETS}
        for fut in as_completed(futures):
            result = fut.result()
            if result:
                rows.append(result)

    rows.sort(key=lambda r: r["_sort_key"], reverse=True)
    for r in rows:
        r.pop("_sort_key", None)
    return rows


# ---------------------------------------------------------------------------
# Parquet storage & performance
# ---------------------------------------------------------------------------

PARQUET_DIR = Path(__file__).parent / "market_data"
SNAPSHOTS_FILE = PARQUET_DIR / "snapshots.parquet"
HISTORY_FILE = PARQUET_DIR / "history.parquet"


def _ensure_parquet_dir() -> Path:
    PARQUET_DIR.mkdir(exist_ok=True)
    return PARQUET_DIR


def save_snapshots_to_parquet(rows: List[dict]) -> Optional[Path]:
    """
    Append current snapshot to parquet. Each row gets a fetch_timestamp.
    Returns path if saved, None if parquet unavailable.
    """
    if not (_HAS_PANDAS and _HAS_PARQUET):
        return None
    _ensure_parquet_dir()
    fetch_ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    records = []
    for r in rows:
        records.append({
            "fetch_timestamp": fetch_ts,
            "asset": r["asset"],
            "symbol": r["symbol"],
            "latest_datetime": r["latest_datetime"],
            "latest_price": float(r["latest_price"]),
            "prior_close": float(r["prior_close"]),
            "pct_change": float(r["pct_change"]),
        })
    df_new = pd.DataFrame(records)
    if SNAPSHOTS_FILE.exists():
        df_old = pd.read_parquet(SNAPSHOTS_FILE)
        df = pd.concat([df_old, df_new], ignore_index=True)
    else:
        df = df_new
    df.to_parquet(SNAPSHOTS_FILE, index=False)
    return SNAPSHOTS_FILE


def fetch_and_save_history(period: str = "1y") -> Optional[Path]:
    """
    Fetch historical daily OHLCV for all assets from yfinance, save to parquet.
    Returns path if saved, None if parquet unavailable.
    """
    if not (_HAS_PANDAS and _HAS_PARQUET):
        return None
    _ensure_parquet_dir()
    all_dfs = []
    for name, symbol in ASSETS:
        try:
            ticker = yf.Ticker(symbol)
            hist = ticker.history(period=period)
            if hist.empty or len(hist) < 2:
                continue
            hist = hist.reset_index()
            hist["symbol"] = symbol
            hist["asset"] = name
            hist["date"] = hist["Date"].dt.strftime("%Y-%m-%d")
            all_dfs.append(hist[["date", "symbol", "asset", "Open", "High", "Low", "Close", "Volume"]])
        except Exception as e:
            print(f"  ✗ {name} ({symbol}): {e}")
    if not all_dfs:
        return None
    df = pd.concat(all_dfs, ignore_index=True)
    df = df.rename(columns={"Open": "open", "High": "high", "Low": "low", "Close": "close", "Volume": "volume"})
    df.to_parquet(HISTORY_FILE, index=False)
    return HISTORY_FILE


def get_performance_report() -> List[dict]:
    """
    Compute 1D, 5D, 1M, 3M, 6M, 1Y % returns from history parquet.
    Returns list of dicts: asset, symbol, latest_price, ret_1d, ret_5d, ret_1m, ret_3m, ret_6m, ret_1y.
    """
    if not (_HAS_PANDAS and _HAS_PARQUET) or not HISTORY_FILE.exists():
        return []
    df = pd.read_parquet(HISTORY_FILE)
    df["date"] = pd.to_datetime(df["date"])
    df = df.sort_values(["symbol", "date"])
    report = []
    for symbol, g in df.groupby("symbol"):
        g = g.drop_duplicates(subset=["date"], keep="last").sort_values("date")
        if len(g) < 2:
            continue
        asset = g["asset"].iloc[0]
        latest = g["close"].iloc[-1]
        if latest <= 0:
            continue
        periods = [
            ("ret_1d", 1),
            ("ret_5d", 5),
            ("ret_1m", 21),
            ("ret_3m", 63),
            ("ret_6m", 126),
            ("ret_1y", 252),
        ]
        row = {"asset": asset, "symbol": symbol, "latest_price": latest}
        for key, n in periods:
            if len(g) > n:
                past = g["close"].iloc[-1 - n]
                row[key] = ((latest - past) / past * 100) if past and past > 0 else None
            else:
                row[key] = None
        report.append(row)
    return report


# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------

def main():
    base = Path(__file__).parent
    reports_dir = base / "reports"
    reports_dir.mkdir(exist_ok=True)
    rows: List[dict] = []

    print(f"Fetching {len(ASSETS)} assets in parallel (max {MAX_WORKERS} threads)…")
    t0 = datetime.now()

    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
        futures = {pool.submit(fetch_asset_data, name, sym): (name, sym) for name, sym in ASSETS}
        for fut in as_completed(futures):
            name, sym = futures[fut]
            result = fut.result()
            if result:
                rows.append(result)
                print(f"  ✓ {name}")

    elapsed = (datetime.now() - t0).total_seconds()
    print(f"\nFetched {len(rows)}/{len(ASSETS)} assets in {elapsed:.1f}s")

    if not rows:
        print("No data retrieved.")
        return

    rows.sort(key=lambda r: r["_sort_key"], reverse=True)
    for r in rows:
        r.pop("_sort_key", None)

    # --- Parquet snapshots (append) -----------------------------------------
    if _HAS_PANDAS and _HAS_PARQUET:
        p = save_snapshots_to_parquet(rows)
        if p:
            print(f"Appended to {p}")
    else:
        print("(Install pandas and pyarrow for parquet: pip install pandas pyarrow)")

    # --- Parquet history & performance --------------------------------------
    perf_rows: List[dict] = []
    if _HAS_PANDAS and _HAS_PARQUET:
        print("Fetching historical data for performance…")
        fetch_and_save_history(period="2y")  # 2y ensures 253+ days for 1Y return
        perf_rows = get_performance_report()

    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    # --- CSV ----------------------------------------------------------------
    csv_path = reports_dir / f"claude-market-data_{ts}.csv"
    with open(csv_path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=[
            "asset", "latest_datetime", "latest_price", "prior_close",
            "pct_change", "opens", "closes", "source",
        ])
        w.writeheader()
        for r in rows:
            is_yield = r.get("is_yield", False)
            pct_str = "N/A (yield)" if is_yield else f"{r['pct_change']:.2f}"
            w.writerow({
                "asset":            r["asset"],
                "latest_datetime":  r["latest_datetime"],
                "latest_price":     fmt_price(r["latest_price"]),
                "prior_close":      fmt_price(r["prior_close"]),
                "pct_change":       pct_str,
                "opens":            r["opens"],
                "closes":           r["closes"],
                "source":           r["source"],
            })
    print(f"Wrote {csv_path}")

    # --- Markdown -----------------------------------------------------------
    md_path = reports_dir / f"claude-market-data_{ts}.md"
    lines = [
        "# Market Data — Latest Prices",
        "",
        f"*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M ET')}*",
        "",
        "*Sorted by latest timestamp (most recent first). Bond yields shown in % terms.*",
        "",
        "| Asset | Latest | Price | Prior Close | % Chg | Opens (ET) | Closes (ET) | Source |",
        "|-------|--------|-------|-------------|-------|------------|-------------|--------|",
    ]
    for r in rows:
        lp = fmt_price(r["latest_price"], commas=True)
        pc = fmt_price(r["prior_close"], commas=True)
        is_yield = r.get("is_yield", False)
        is_bond_etf = r.get("is_bond_etf", False)
        if is_yield:
            pct_str = "—"
            lp = f"{r['latest_price']:.3f}%"
            pc = f"{r['prior_close']:.3f}%"
        else:
            pct = r["pct_change"]
            pct_str = f"{pct:+.2f}%"
            if is_bond_etf:
                lp = f"{lp} (ETF)"
        lines.append(
            f"| {r['asset']} | {r['latest_datetime']} | {lp} | {pc} "
            f"| {pct_str} | {r['opens']} | {r['closes']} | {r['source']} |"
        )
    lines += [
        "",
        f"*Data source: {SOURCE}*",
        "",
        "**Notes:**",
        "- All timestamps normalized to Eastern Time (ET) where time data is available.",
        "- US Treasury yields (^IRX, ^FVX, ^TNX, ^TYX) are shown as absolute % values — % change column is suppressed.",
        "- VIX is shown as absolute points value.",
        "- **International bond proxies:** Yahoo Finance does not provide reliable yield tickers for UK/EU/Japan.",
        "  `IGLT.L` (iShares UK Gilts, LSE), `IBGM.DE` (iShares EUR Govt Bond, XETRA), and `2561.T` (iShares JPY Govt Bond, TSE)",
        "  are ETF price proxies — price moves *inversely* to yield direction.",
        "- Prices for futures (Oil, Gold, etc.) reflect the front-month contract.",
    ]
    # --- Performance (from parquet history) ----------------------------------
    if perf_rows:
        perf_csv = reports_dir / f"claude-market-performance_{ts}.csv"
        with open(perf_csv, "w", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=[
                "asset", "symbol", "latest_price", "1D %", "5D %", "1M %", "3M %", "6M %", "1Y %",
            ])
            w.writeheader()
            for r in perf_rows:
                w.writerow({
                    "asset": r["asset"],
                    "symbol": r["symbol"],
                    "latest_price": fmt_price(r.get("latest_price", 0)),
                    "1D %": f"{r.get('ret_1d'):.2f}" if r.get("ret_1d") is not None else "",
                    "5D %": f"{r.get('ret_5d'):.2f}" if r.get("ret_5d") is not None else "",
                    "1M %": f"{r.get('ret_1m'):.2f}" if r.get("ret_1m") is not None else "",
                    "3M %": f"{r.get('ret_3m'):.2f}" if r.get("ret_3m") is not None else "",
                    "6M %": f"{r.get('ret_6m'):.2f}" if r.get("ret_6m") is not None else "",
                    "1Y %": f"{r.get('ret_1y'):.2f}" if r.get("ret_1y") is not None else "",
                })
        print(f"Wrote {perf_csv}")

        lines += [
            "",
            "---",
            "",
            "## Performance (from historical data)",
            "",
            "| Asset | Symbol | Latest | 1D % | 5D % | 1M % | 3M % | 6M % | 1Y % |",
            "|-------|--------|--------|------|------|------|------|------|------|",
        ]
        for r in sorted(perf_rows, key=lambda x: (x.get("ret_1y") or -999), reverse=True):
            def _pct(v):
                return f"{v:+.2f}%" if v is not None else "—"
            lp = fmt_price(r.get("latest_price", 0), commas=True)
            lines.append(
                f"| {r['asset']} | {r['symbol']} | {lp} | {_pct(r.get('ret_1d'))} | {_pct(r.get('ret_5d'))} | "
                f"{_pct(r.get('ret_1m'))} | {_pct(r.get('ret_3m'))} | {_pct(r.get('ret_6m'))} | {_pct(r.get('ret_1y'))} |"
            )
        lines.append("")

    Path(md_path).write_text("\n".join(lines), encoding="utf-8")
    print(f"Wrote {md_path}")


if __name__ == "__main__":
    main()

Appendix 2: market_ytd

To run independently: Copy the entire code block below, save as market_ytd.py. Place it in the same directory as claude_v5.py (market_ytd imports the asset list from it). Run python market_ytd.py. Prerequisites: pip install yfinance. Outputs go to reports/.

#!/usr/bin/env python3
"""
Market data with prior-close change and YTD (start-of-year) change.

Outputs CSV and MD with:
  - Latest price, prior close, % change since prior close
  - Value at start of calendar year (nearest available trading day)
  - % change from start of year to latest

Usage:
    python market_ytd.py
"""

import csv
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple

try:
    import yfinance as yf
except ImportError:
    import subprocess
    subprocess.check_call(["pip", "install", "yfinance", "-q"])
    import yfinance as yf

# Import asset list from claude_v5
from claude_v5 import ASSETS, TRADING_HOURS, YIELD_ASSETS, BOND_ETF_ASSETS, SOURCE

MAX_WORKERS = 10


def _to_et(ts) -> object:
    try:
        import pytz
        ET = pytz.timezone("America/New_York")
    except ImportError:
        return ts
    try:
        import pandas as pd
        t = pd.Timestamp(ts)
        if t.tzinfo is None:
            return t
        return t.astimezone(ET)
    except Exception:
        return ts


def fmt_price(v: float, commas: bool = False) -> str:
    abs_v = abs(v)
    if abs_v >= 1000:
        return f"{v:,.2f}" if commas else f"{v:.2f}"
    if abs_v >= 1:
        return f"{v:.4f}"
    if abs_v == 0:
        return "0.00"
    return f"{v:.6f}"


def fetch_asset_with_ytd(name: str, symbol: str, year_start: str) -> Optional[dict]:
    """
    Fetch latest price, prior close, and start-of-year value for one asset.
    year_start: "YYYY-01-01" — we find the nearest available close on or before this date.
    """
    try:
        ticker = yf.Ticker(symbol)
        hist_intra = ticker.history(period="1d", interval="1m")
        hist_daily = ticker.history(period="5d")
        # Need data before Jan 1 to get last trading day of prior year
        y = int(year_start[:4])
        hist_ytd = ticker.history(start=f"{y - 1}-12-01", end=datetime.now().strftime("%Y-%m-%d"))

        if not hist_intra.empty:
            hist = hist_intra
        elif not hist_daily.empty:
            hist = hist_daily
        else:
            print(f"  ✗ {name}: no data")
            return None

        last_price = float(hist["Close"].iloc[-1])
        raw_dt = hist.index[-1]
        last_dt_et = _to_et(raw_dt)

        if hasattr(last_dt_et, "strftime"):
            dt_str = last_dt_et.strftime("%Y-%m-%d %H:%M ET") if getattr(last_dt_et, "tzinfo", None) else last_dt_et.strftime("%Y-%m-%d")
            sort_key = last_dt_et.strftime("%Y-%m-%dT%H:%M:%S") if getattr(last_dt_et, "tzinfo", None) else last_dt_et.strftime("%Y-%m-%dT00:00:00")
        else:
            dt_str = str(last_dt_et)[:16].replace("T", " ")
            sort_key = dt_str.replace(" ", "T")

        # Prior close
        prev_close = None
        try:
            fi_prev = getattr(ticker.fast_info, "previous_close", None)
            if fi_prev is not None:
                prev_close = float(fi_prev)
        except Exception:
            pass
        if prev_close is None and not hist_daily.empty and len(hist_daily) >= 2:
            prev_close = float(hist_daily["Close"].iloc[-2])
        if prev_close is None:
            prev_close = last_price

        pct_change = ((last_price - prev_close) / prev_close * 100) if prev_close else 0.0

        # Start-of-year value: nearest available close on or before Jan 1
        ytd_value = None
        ytd_date = None
        ytd_pct_change = None
        if not hist_ytd.empty:
            hist_ytd = hist_ytd[hist_ytd.index <= year_start]
            if not hist_ytd.empty:
                ytd_value = float(hist_ytd["Close"].iloc[-1])
                ytd_date = hist_ytd.index[-1]
                if hasattr(ytd_date, "strftime"):
                    ytd_date = ytd_date.strftime("%Y-%m-%d")
                else:
                    ytd_date = str(ytd_date)[:10]
                if ytd_value and ytd_value > 0:
                    ytd_pct_change = ((last_price - ytd_value) / ytd_value * 100)

        opens, closes = TRADING_HOURS.get(name, ("—", "—"))
        is_yield = name in YIELD_ASSETS
        is_bond_etf = name in BOND_ETF_ASSETS

        return {
            "asset": name,
            "symbol": symbol,
            "latest_datetime": dt_str,
            "_sort_key": sort_key,
            "latest_price": last_price,
            "prior_close": prev_close,
            "pct_change": pct_change,
            "ytd_value": ytd_value,
            "ytd_date": ytd_date,
            "ytd_pct_change": ytd_pct_change,
            "is_yield": is_yield,
            "is_bond_etf": is_bond_etf,
            "opens": opens,
            "closes": closes,
            "source": SOURCE,
        }
    except Exception as e:
        print(f"  ✗ {name} ({symbol}): {e}")
        return None


def main():
    base = Path(__file__).parent
    reports_dir = base / "reports"
    reports_dir.mkdir(exist_ok=True)
    year = datetime.now().year
    year_start = f"{year}-01-01"

    print(f"Fetching market data (prior close + YTD vs {year_start})…")
    rows: List[dict] = []
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
        futures = {pool.submit(fetch_asset_with_ytd, name, sym, year_start): (name, sym) for name, sym in ASSETS}
        for fut in as_completed(futures):
            result = fut.result()
            if result:
                rows.append(result)
                print(f"  ✓ {result['asset']}")

    if not rows:
        print("No data retrieved.")
        return

    rows.sort(key=lambda r: r["_sort_key"], reverse=True)
    for r in rows:
        r.pop("_sort_key", None)

    ts = datetime.now().strftime("%Y%m%d_%H%M%S")

    # --- CSV ---
    csv_path = reports_dir / f"market-ytd_{ts}.csv"
    with open(csv_path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=[
            "asset", "symbol", "latest_datetime", "latest_price", "prior_close",
            "pct_change", "ytd_date", "ytd_value", "ytd_pct_change", "opens", "closes", "source",
        ])
        w.writeheader()
        for r in rows:
            is_yield = r.get("is_yield", False)
            pct_str = "N/A (yield)" if is_yield else f"{r['pct_change']:.2f}"
            ytd_val = fmt_price(r["ytd_value"]) if r.get("ytd_value") is not None else ""
            ytd_pct = f"{r['ytd_pct_change']:.2f}" if r.get("ytd_pct_change") is not None else ""
            w.writerow({
                "asset": r["asset"],
                "symbol": r["symbol"],
                "latest_datetime": r["latest_datetime"],
                "latest_price": fmt_price(r["latest_price"]),
                "prior_close": fmt_price(r["prior_close"]),
                "pct_change": pct_str,
                "ytd_date": r.get("ytd_date") or "",
                "ytd_value": ytd_val,
                "ytd_pct_change": ytd_pct,
                "opens": r["opens"],
                "closes": r["closes"],
                "source": r["source"],
            })
    print(f"Wrote {csv_path}")

    # --- Markdown ---
    md_path = reports_dir / f"market-ytd_{ts}.md"
    lines = [
        "# Market Data — Prior Close & YTD",
        "",
        f"*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M ET')}*",
        "",
        f"*YTD = change from nearest available close on/before {year}-01-01 to latest.*",
        "",
        "| Asset | Latest | Price | Prior Close | % Chg | YTD Date | YTD Value | YTD % Chg | Opens | Closes | Source |",
        "|-------|--------|-------|-------------|-------|----------|-----------|-----------|-------|--------|--------|",
    ]
    for r in rows:
        lp = fmt_price(r["latest_price"], commas=True)
        pc = fmt_price(r["prior_close"], commas=True)
        is_yield = r.get("is_yield", False)
        is_bond_etf = r.get("is_bond_etf", False)
        if is_yield:
            pct_str = "—"
            lp = f"{r['latest_price']:.3f}%"
            pc = f"{r['prior_close']:.3f}%"
        else:
            pct_str = f"{r['pct_change']:+.2f}%"
            if is_bond_etf:
                lp = f"{lp} (ETF)"

        ytd_date = r.get("ytd_date") or "—"
        ytd_val = fmt_price(r["ytd_value"], commas=True) if r.get("ytd_value") is not None else "—"
        ytd_pct = f"{r['ytd_pct_change']:+.2f}%" if r.get("ytd_pct_change") is not None else "—"

        lines.append(
            f"| {r['asset']} | {r['latest_datetime']} | {lp} | {pc} | {pct_str} | "
            f"{ytd_date} | {ytd_val} | {ytd_pct} | {r['opens']} | {r['closes']} | {r['source']} |"
        )
    lines += ["", f"*Data source: {SOURCE}*"]
    Path(md_path).write_text("\n".join(lines), encoding="utf-8")
    print(f"Wrote {md_path}")


if __name__ == "__main__":
    main()