digdigdig3 0.1.22

Unified async Rust API for 44 exchange connectors — crypto, stocks, forex. REST + WebSocket.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# Yahoo Finance 401 Unauthorized - Problem Analysis


**Date**: 2026-01-26
**Status**: CRITICAL - Partial API Outage
**Impact**: /v7/finance/quote endpoint returns 401, but chart/search endpoints work

---

## Executive Summary


Yahoo Finance has **disabled the /v7/finance/quote endpoint** as of January 2026, causing all `get_price()` and `get_ticker()` calls to fail with 401 Unauthorized. This is part of Yahoo's ongoing shutdown of their unofficial API.

**Good News**: The `/v8/finance/chart/{symbol}` endpoint still works and provides the exact same data (price, high, low, volume, change%).

**Fix**: Simple 2-3 hour code change to replace quote endpoint with chart endpoint in `get_quote_internal()` method.

**Error Message**:
```json
{
  "finance": {
    "result": null,
    "error": {
      "code": "Unauthorized",
      "description": "User is unable to access this feature - https://bit.ly/yahoo-finance-api-feedback"
    }
  }
}
```

**Working Endpoints** (tested 2026-01-26):
- `/v8/finance/chart/{symbol}` - Full OHLCV data + real-time price ✅
- `/v1/finance/search` - Symbol search ✅
- `/v6/finance/quote/marketSummary` - Market indices ✅

**Broken Endpoints**:
- `/v7/finance/quote` - Returns 401 Unauthorized ❌
- `/v10/finance/quoteSummary` - Requires crumb auth ⚠️

---

## Problem Analysis


### 1. Root Cause


Yahoo Finance is incrementally shutting down their **unofficial API endpoints**. The /v7/finance/quote endpoint was likely the most heavily abused endpoint (used for real-time stock quotes), so Yahoo disabled it first.

**Evidence**:
```bash
# FAILS - 401 Unauthorized

$ curl "https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL"
{"finance":{"result":null,"error":{"code":"Unauthorized",...}}}

# WORKS - Returns full data

$ curl "https://query1.finance.yahoo.com/v8/finance/chart/AAPL"
{"chart":{"result":[{"meta":{"regularMarketPrice":248.04,...}}]}}
```

### 2. Affected Endpoints in Our Connector


**BROKEN** (return 401):
- `YahooFinanceEndpoint::Quote` - `/v7/finance/quote`
  - Used by: `get_price()`, `get_ticker()`

**WORKING**:
- `YahooFinanceEndpoint::Chart` - `/v8/finance/chart/{symbol}`- `YahooFinanceEndpoint::Search` - `/v1/finance/search`- `YahooFinanceEndpoint::MarketSummary` - `/v6/finance/quote/marketSummary`
**REQUIRES CRUMB** (authentication):
- `YahooFinanceEndpoint::QuoteSummary` - `/v10/finance/quoteSummary/{symbol}` ⚠️ (returns "Invalid Crumb")

### 3. Current Implementation Issues


**connector.rs**:
```rust
// Lines 159-164 - Uses BROKEN Quote endpoint
async fn get_quote_internal(&self, yahoo_symbol: &str) -> ExchangeResult<serde_json::Value> {
    let mut params = HashMap::new();
    params.insert("symbols".to_string(), yahoo_symbol.to_string());
    self.get(YahooFinanceEndpoint::Quote, None, params).await  // ❌ RETURNS 401
}

// Lines 208-210 - get_price() depends on broken endpoint
async fn get_price(...) -> ExchangeResult<Price> {
    let yahoo_symbol = format_symbol(&symbol.base, &symbol.quote);
    let response = self.get_quote_internal(&yahoo_symbol).await?;  // ❌ FAILS HERE
    YahooFinanceParser::parse_price(&response)
}
```

**Why it fails**:
- `get_price()` → calls `get_quote_internal()` → uses `/v7/finance/quote`**401 Unauthorized**
- `get_ticker()` → calls `get_quote_internal()` → uses `/v7/finance/quote`**401 Unauthorized**

---

## Tested Solutions


### Solution 1: Use Chart Endpoint Instead of Quote


**Implementation**: Replace `/v7/finance/quote` with `/v8/finance/chart/{symbol}`

**Pros**:
- Chart endpoint **currently works** (tested 2026-01-26)
- Returns same data: regularMarketPrice, bid, ask, volume, high/low
- No authentication needed
- Already implemented in `get_klines()` method

**Cons**:
- Returns more data than needed (includes full OHLCV arrays)
- Slightly slower response (~35KB vs ~2KB)
- Cannot query multiple symbols in one request

**Code Changes Required**:
```rust
// connector.rs - Replace get_quote_internal()
async fn get_quote_internal(&self, yahoo_symbol: &str) -> ExchangeResult<serde_json::Value> {
    // OLD: self.get(YahooFinanceEndpoint::Quote, None, params).await
    // NEW: Use chart endpoint
    self.get(YahooFinanceEndpoint::Chart, Some(yahoo_symbol), HashMap::new()).await
}
```

```rust
// parser.rs - Update parse_price() and parse_ticker()
pub fn parse_price(response: &Value) -> ExchangeResult<f64> {
    // OLD: Parse from quoteResponse.result[0].regularMarketPrice
    // NEW: Parse from chart.result[0].meta.regularMarketPrice
    let result = Self::get_chart_result(response)?;
    let first = result.get(0).ok_or_else(...)?;
    let meta = first.get("meta").ok_or_else(...)?;
    Self::require_f64(meta, "regularMarketPrice")
}
```

**Complexity**: SIMPLE (1-2 hours)

---

### Solution 2: Use QuoteSummary Endpoint


**Implementation**: Use `/v10/finance/quoteSummary/{symbol}?modules=price`

**Pros**:
- More comprehensive data (includes price, summaryDetail, financialData modules)
- Official-looking endpoint (v10 vs v7)
- May be more stable long-term

**Cons**:
- **REQUIRES CRUMB AUTHENTICATION** (tested 2026-01-26, returns "Invalid Crumb")
- Complex cookie/crumb management needed
- Fragile authentication flow
- Larger payload

**Complexity**: COMPLEX (8-12 hours) - NOT RECOMMENDED

---

### Solution 3: Add Cookie/Crumb Authentication


**Implementation**: Implement full browser-like authentication with cookies

**Pros**:
- May bypass 401 errors on quote endpoint
- Required for historical CSV download anyway

**Cons**:
- **Complex**: Need to scrape cookies from finance.yahoo.com webpage
- **Fragile**: Yahoo changes cookie/crumb format frequently
- **Rate limits**: Still subject to aggressive rate limiting
- **May not work**: Quote endpoint might be permanently disabled

**Complexity**: COMPLEX (8-12 hours) + FRAGILE

---

### Solution 4: Use Alternative Endpoints per Data Type


**Implementation**: Route different data types to different endpoints

**Example**:
- Price data → `/v8/finance/chart/{symbol}` (meta.regularMarketPrice)
- Ticker data → `/v8/finance/chart/{symbol}` (meta + indicators)
- Financial data → `/v10/finance/quoteSummary/{symbol}?modules=financialData`
- Options data → `/v7/finance/options/{symbol}` (if still working)

**Pros**:
- Most resilient approach
- Uses best endpoint for each data type
- Already partially implemented (chart for klines)

**Cons**:
- More code changes
- Different error handling per endpoint

**Complexity**: MEDIUM (4-6 hours)

---

### Solution 5: Add Fallback Chain


**Implementation**: Try quote endpoint first, fallback to chart if 401

**Pros**:
- Graceful degradation
- Future-proof if quote endpoint comes back
- No breaking changes to API

**Cons**:
- Slower on failure (waits for 401 timeout)
- More complex error handling

**Complexity**: SIMPLE (2-3 hours)

---

## Recommendations


### Recommended Solution: **Solution 1 + Solution 5 (Hybrid)**


**Phase 1** (Immediate - 2-3 hours):
1. Modify `get_quote_internal()` to use Chart endpoint
2. Update `parse_price()` and `parse_ticker()` to parse from chart response
3. Add tests to verify chart-based parsing works

**Phase 2** (Short-term - 1-2 hours):
1. Add fallback logic: Try quote endpoint first, fallback to chart on 401
2. Log warnings when fallback is used
3. Document in code comments that quote endpoint is deprecated

**Why this approach**:
- ✅ Fixes the immediate 401 issue
- ✅ Simple implementation (chart endpoint already tested)
- ✅ Future-proof (fallback if quote endpoint comes back)
- ✅ No breaking changes to public API
- ✅ Maintains same functionality

**Code Template**:
```rust
async fn get_quote_internal(&self, yahoo_symbol: &str) -> ExchangeResult<serde_json::Value> {
    // Try quote endpoint first (for backwards compatibility)
    let mut params = HashMap::new();
    params.insert("symbols".to_string(), yahoo_symbol.to_string());

    match self.get(YahooFinanceEndpoint::Quote, None, params.clone()).await {
        Ok(response) => Ok(response),
        Err(ExchangeError::Api { code: 401, .. }) => {
            // Quote endpoint returned 401 - fallback to chart endpoint
            log::warn!("Yahoo Finance quote endpoint returned 401, falling back to chart endpoint");
            self.get(YahooFinanceEndpoint::Chart, Some(yahoo_symbol), HashMap::new()).await
        },
        Err(e) => Err(e),
    }
}
```

---

## Alternative Long-Term Solutions


### If Yahoo Continues Shutting Down Endpoints:


**Option A**: Switch to yfinance Python library approach
- Use Selenium/headless browser to scrape data
- Extract from HTML instead of API
- VERY complex, fragile

**Option B**: Switch to alternative data provider
- Alpha Vantage (free tier: 25 req/day)
- Twelve Data (free tier: 800 req/day)
- Finnhub (free tier: 60 req/min)
- IEX Cloud (free tier with registration)

**Option C**: Use multiple providers with fallback
- Primary: Yahoo Finance (free, no key)
- Fallback: Alpha Vantage or Finnhub
- Requires API key management

---

## Testing Plan


### 1. Manual Testing (COMPLETED 2026-01-26)


**Results of endpoint testing**:

```bash
# Test quote endpoint - ❌ FAILS with 401

$ curl "https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL" \
  -H "User-Agent: Mozilla/5.0"
# Response: {"finance":{"result":null,"error":{"code":"Unauthorized",...}}}


# Test chart endpoint - ✅ WORKS (returns full data including price)

$ curl "https://query1.finance.yahoo.com/v8/finance/chart/AAPL" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
# Response: {"chart":{"result":[{"meta":{"regularMarketPrice":248.04,...}}]}}


# Test chart with crypto - ✅ WORKS

$ curl "https://query1.finance.yahoo.com/v8/finance/chart/BTC-USD" \
  -H "User-Agent: Mozilla/5.0"
# Response: {"chart":{"result":[{"meta":{"regularMarketPrice":87696.74,...}}]}}


# Test quoteSummary - ❌ REQUIRES CRUMB

$ curl "https://query1.finance.yahoo.com/v10/finance/quoteSummary/AAPL?modules=price" \
  -H "User-Agent: Mozilla/5.0"
# Response: {"finance":{"result":null,"error":{"code":"Unauthorized","description":"Invalid Crumb"}}}


# Test search - ✅ WORKS

$ curl "https://query1.finance.yahoo.com/v1/finance/search?q=apple" \
  -H "User-Agent: Mozilla/5.0"
# Response: {"quotes":[{"symbol":"AAPL","shortname":"Apple Inc.",...}],...}


# Test market summary - ✅ WORKS

$ curl "https://query1.finance.yahoo.com/v6/finance/quote/marketSummary" \
  -H "User-Agent: Mozilla/5.0"
# Response: {"marketSummaryResponse":{"result":[{"symbol":"ES=F",...}]}}

```

**Chart Endpoint Data Availability**:
- ✅ regularMarketPrice (current price)
- ✅ regularMarketDayHigh (24h high)
- ✅ regularMarketDayLow (24h low)
- ✅ regularMarketVolume (24h volume)
- ✅ regularMarketChange (price change)
- ✅ regularMarketChangePercent (percent change)
- ✅ OHLCV data in indicators.quote arrays
- ❌ bid/ask prices (NOT available - normal for Yahoo Finance)
- ❌ previousClose (available as chartPreviousClose)

### 2. Unit Tests


Create parser tests for chart-based responses:

```rust
#[test]

fn test_parse_price_from_chart() {
    let response = json!({
        "chart": {
            "result": [{
                "meta": {
                    "regularMarketPrice": 248.04,
                    "currency": "USD",
                    "symbol": "AAPL"
                }
            }]
        }
    });

    let price = YahooFinanceParser::parse_price_from_chart(&response).unwrap();
    assert_eq!(price, 248.04);
}
```

### 3. Integration Tests


Run existing tests with new implementation:

```bash
cargo test --package digdigdig3 --test yahoo_finance_integration -- --nocapture
```

---

## Risk Assessment


| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Chart endpoint also gets disabled | MEDIUM | HIGH | Add fallback to quoteSummary |
| Rate limiting increases | HIGH | MEDIUM | Add exponential backoff, delays |
| Response format changes | MEDIUM | MEDIUM | Comprehensive error handling |
| All endpoints disabled | LOW | CRITICAL | Switch to alternative provider |

---

## Related Resources


- Yahoo Finance API unofficial docs: https://github.com/ranaroussi/yfinance/wiki
- Alternative: https://www.alphavantage.co/documentation/
- Alternative: https://finnhub.io/docs/api
- Community discussion: https://stackoverflow.com/questions/tagged/yahoo-finance

---

## Next Steps


1. **Document the issue** (this file)
2.**Get approval** for recommended solution
3.**Implement Phase 1** (chart endpoint replacement)
4.**Test thoroughly** (unit + integration tests)
5.**Implement Phase 2** (fallback logic)
6.**Monitor** for additional endpoint failures
7.**Consider** migration to alternative provider if situation worsens

---

## Conclusion


Yahoo Finance is gradually shutting down their unofficial API. The `/v7/finance/quote` endpoint now returns 401 Unauthorized, but the `/v8/finance/chart` endpoint still works and provides the same data.

**Recommended immediate fix**: Replace quote endpoint with chart endpoint in `get_quote_internal()` method and update parsers. This is a **simple, 2-3 hour fix** that restores full functionality.

**Long-term consideration**: Monitor Yahoo Finance API status and prepare migration plan to alternative provider (Alpha Vantage, Finnhub, or IEX Cloud) if more endpoints fail.

---

## Quick Reference: Implementation Changes Required


### Files to Modify:

1. `connector.rs` - Update `get_quote_internal()` method (line 159)
2. `parser.rs` - Update `parse_price()` and `parse_ticker()` methods

### Code Changes:


**connector.rs (line 159-164)**:
```rust
// OLD CODE (returns 401):
async fn get_quote_internal(&self, yahoo_symbol: &str) -> ExchangeResult<serde_json::Value> {
    let mut params = HashMap::new();
    params.insert("symbols".to_string(), yahoo_symbol.to_string());
    self.get(YahooFinanceEndpoint::Quote, None, params).await  // ❌ FAILS
}

// NEW CODE (works):
async fn get_quote_internal(&self, yahoo_symbol: &str) -> ExchangeResult<serde_json::Value> {
    // Use chart endpoint instead of quote endpoint (quote returns 401 as of Jan 2026)
    self.get(YahooFinanceEndpoint::Chart, Some(yahoo_symbol), HashMap::new()).await
}
```

**parser.rs (line 26-32)**:
```rust
// OLD CODE:
pub fn parse_price(response: &Value) -> ExchangeResult<f64> {
    let result = Self::get_quote_response_result(response)?;
    let first = result.get(0).ok_or_else(...)?;
    Self::require_f64(first, "regularMarketPrice")
}

// NEW CODE:
pub fn parse_price(response: &Value) -> ExchangeResult<f64> {
    let result = Self::get_chart_result(response)?;
    let first = result.get(0).ok_or_else(...)?;
    let meta = first.get("meta").ok_or_else(...)?;
    Self::require_f64(meta, "regularMarketPrice")
}
```

**parser.rs (line 36-56)** - Similar changes for `parse_ticker()`:
```rust
// Parse from: response -> chart -> result[0] -> meta -> regularMarketPrice
// Instead of: response -> quoteResponse -> result[0] -> regularMarketPrice
```

### Test Command:

```bash
cargo test --package digdigdig3 --test yahoo_finance_integration test_get_price -- --nocapture --exact
```

---

## TL;DR - For the Busy Developer


**Problem**: Yahoo Finance `/v7/finance/quote` endpoint returns 401 Unauthorized

**Cause**: Yahoo is shutting down unofficial API endpoints

**Solution**: Use `/v8/finance/chart/{symbol}` endpoint instead (still works)

**Effort**: 2-3 hours (simple code change + parser update)

**Files**: `connector.rs` (1 method), `parser.rs` (2 methods)

**Alternative**: Do nothing and wait for Yahoo to restore quote endpoint (unlikely)