# yfinance 互換性ポリシー

## 設計原則

dyfinance は yfinance の **完全な drop-in replacement** である。

```python
# この置き換えが常に成立しなければならない
import dyfinance as yf  # import yfinance as yf と同一の挙動
```

すべての公開 API (`Ticker`, `Tickers`, `download`) は、yfinance と **同一の I/O・オプション・デフォルト値** を持つ。dyfinance が追加するのは DuckDB によるキャッシュ層のみであり、利用者から見た入出力は一切変わらない。

---

## 互換性の範囲

### Ticker.history()

| パラメータ | 型 | デフォルト | yfinance と同一 |
|-----------|-----|----------|:---:|
| period | str \| None | None | Yes |
| interval | str | "1d" | Yes |
| start | str \| datetime \| None | None | Yes |
| end | str \| datetime \| None | None | Yes |
| prepost | bool | False | Yes |
| actions | bool | True | Yes |
| auto_adjust | bool | True | Yes |
| back_adjust | bool | False | Yes |
| repair | bool | False | Yes |
| keepna | bool | False | Yes |
| rounding | bool | False | Yes |
| timeout | float | 10 | Yes |
| raise_errors | bool | False | Yes |

**出力**: yfinance.Ticker.history() と同一の DataFrame 構造（列名・列順序・dtype・インデックス形式）。

### download()

| パラメータ | 型 | デフォルト | yfinance と同一 |
|-----------|-----|----------|:---:|
| tickers | str \| list | - | Yes |
| start | str \| datetime \| None | None | Yes |
| end | str \| datetime \| None | None | Yes |
| actions | bool | False | Yes |
| threads | bool \| int | True | Yes |
| ignore_tz | bool \| None | None | Yes |
| group_by | str | "column" | Yes |
| auto_adjust | bool | True | Yes |
| back_adjust | bool | False | Yes |
| repair | bool | False | Yes |
| keepna | bool | False | Yes |
| progress | bool | True | Yes |
| period | str \| None | None | Yes |
| interval | str | "1d" | Yes |
| prepost | bool | False | Yes |
| rounding | bool | False | Yes |
| timeout | float | 10 | Yes |
| session | - | None | Yes |
| multi_level_index | bool | True | Yes |

**出力**: yfinance.download() と同一の DataFrame 構造（MultiIndex の有無、group_by の挙動を含む）。

### Tickers

| 挙動 | yfinance と同一 |
|------|:---:|
| ティッカー順序の保持 (dedup なし) | Yes |
| history() / download() が `**kwargs` を受け付ける | Yes |
| `.tickers` dict に個別 Ticker を格納 | Yes |

### モジュールレベル fallback

dyfinance に存在しない属性へのアクセスは `yfinance` から透過的に取得する (`__getattr__` fallback)。yfinance の `__all__` に含まれるすべての名前が dyfinance 経由でアクセス可能でなければならない。

---

## 互換性を保証するテスト

以下のテストファイルは **yfinance との I/O・オプション互換性を検証** するものであり、**絶対に変更してはならない**（テスト対象コードの仕様変更を伴わない限り）。

### 変更禁止テストファイル

| ファイル | 検証内容 |
|---------|---------|
| `tests/test_ticker_history.py` | `Ticker.history()` の全オプション (actions, auto_adjust, back_adjust, rounding, keepna, period, start/end, timeout, raise_errors, prepost, repair) が正しい出力構造を生成することを検証 |
| `tests/test_download.py` | `download()` の全オプション (tickers 形式, group_by, multi_level_index, actions, auto_adjust, ignore_tz) が正しい出力構造を生成することを検証 |
| `tests/test_compat.py` | Layer 2 (fixture) と Layer 3 (live) で dyfinance と yfinance の出力が一致することを検証 |
| `tests/test_proxy.py` | モジュールレベル fallback、Ticker/Tickers のプロパティ委譲、yfinance.__all__ の全名前がアクセス可能であることを検証 |

### テストの変更ルール

1. **既存テストの削除・修正は禁止** - 既存のテストケースは yfinance 互換性の仕様書として機能する
2. **テストの追加は許可** - 新たな互換性要件を検証するテストの追加は推奨
3. **リファクタリングによる構造変更は許可** - テストの検証内容が変わらないことを条件に、パラメタライズ化などの構造変更は許可（例: 2つの類似テストを `@pytest.mark.parametrize` で統合）
4. **yfinance の仕様変更に追従する場合のみ修正可** - yfinance 側の仕様が変更された場合は、dyfinance のコードとテストを同時に更新する

---

## yfinance I/F 互換性に関する既知の制約

リファクタリング時に発見された、yfinance との互換性を維持するための制約:

| 制約 | 理由 |
|------|------|
| `_tickers.py` のティッカー正規化は順序保持・dedup なし | `yfinance.Tickers` が入力順を保持し dedup しないため |
| `_download.py` のティッカー正規化は `sorted(set(...))` | `yfinance.download()` が `set()` で dedup するため |
| `_tickers.py` の `history()` / `download()` は `**kwargs` を維持 | `yfinance.Tickers` が `**kwargs` を受け付けるため |
| キャッシュは raw (auto_adjust=False) で保存し、読み出し時に後処理 | オプションの組み合わせに依存しないキャッシュを実現するため |

---

## テスト戦略 (3層)

| 層 | マーカー | 内容 | CI で実行 |
|----|---------|------|:---:|
| Layer 1: Mock | (デフォルト) | yfinance をモックし、dyfinance のロジックを単体テスト | Yes |
| Layer 2: Fixture | (デフォルト, skip if missing) | 事前録画した parquet fixture で出力構造を検証 | Yes |
| Layer 3: Live | `@pytest.mark.network` | yfinance と dyfinance の出力をリアルタイム比較 | No (手動) |
