pyjpx-etf 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. pyjpx_etf-0.2.0/.claude/CLAUDE.md +103 -0
  2. pyjpx_etf-0.2.0/.claude/settings.local.json +33 -0
  3. pyjpx_etf-0.2.0/.github/workflows/publish.yml +26 -0
  4. pyjpx_etf-0.2.0/.gitignore +16 -0
  5. pyjpx_etf-0.2.0/LICENSE +21 -0
  6. pyjpx_etf-0.2.0/PKG-INFO +122 -0
  7. pyjpx_etf-0.2.0/README.md +95 -0
  8. pyjpx_etf-0.2.0/docs/api/config.md +7 -0
  9. pyjpx_etf-0.2.0/docs/api/etf.md +3 -0
  10. pyjpx_etf-0.2.0/docs/api/exceptions.md +9 -0
  11. pyjpx_etf-0.2.0/docs/getting-started.md +68 -0
  12. pyjpx_etf-0.2.0/docs/guide/etf.md +96 -0
  13. pyjpx_etf-0.2.0/docs/index.md +60 -0
  14. pyjpx_etf-0.2.0/examples/quickstart.ipynb +87 -0
  15. pyjpx_etf-0.2.0/mkdocs.yml +46 -0
  16. pyjpx_etf-0.2.0/pyproject.toml +62 -0
  17. pyjpx_etf-0.2.0/src/pyjpx_etf/__init__.py +19 -0
  18. pyjpx_etf-0.2.0/src/pyjpx_etf/_internal/__init__.py +0 -0
  19. pyjpx_etf-0.2.0/src/pyjpx_etf/_internal/fees.py +129 -0
  20. pyjpx_etf-0.2.0/src/pyjpx_etf/_internal/fetcher.py +74 -0
  21. pyjpx_etf-0.2.0/src/pyjpx_etf/_internal/master.py +101 -0
  22. pyjpx_etf-0.2.0/src/pyjpx_etf/_internal/parser.py +99 -0
  23. pyjpx_etf-0.2.0/src/pyjpx_etf/cli.py +107 -0
  24. pyjpx_etf-0.2.0/src/pyjpx_etf/config.py +57 -0
  25. pyjpx_etf-0.2.0/src/pyjpx_etf/etf.py +138 -0
  26. pyjpx_etf-0.2.0/src/pyjpx_etf/exceptions.py +17 -0
  27. pyjpx_etf-0.2.0/src/pyjpx_etf/models.py +38 -0
  28. pyjpx_etf-0.2.0/src/pyjpx_etf/py.typed +0 -0
  29. pyjpx_etf-0.2.0/tests/__init__.py +0 -0
  30. pyjpx_etf-0.2.0/tests/conftest.py +16 -0
  31. pyjpx_etf-0.2.0/tests/integration/__init__.py +0 -0
  32. pyjpx_etf-0.2.0/tests/integration/test_etf_real.py +75 -0
  33. pyjpx_etf-0.2.0/tests/unit/__init__.py +0 -0
  34. pyjpx_etf-0.2.0/tests/unit/test_cli.py +233 -0
  35. pyjpx_etf-0.2.0/tests/unit/test_config.py +55 -0
  36. pyjpx_etf-0.2.0/tests/unit/test_etf.py +225 -0
  37. pyjpx_etf-0.2.0/tests/unit/test_fees.py +189 -0
  38. pyjpx_etf-0.2.0/tests/unit/test_fetcher.py +174 -0
  39. pyjpx_etf-0.2.0/tests/unit/test_master.py +147 -0
  40. pyjpx_etf-0.2.0/tests/unit/test_models.py +69 -0
  41. pyjpx_etf-0.2.0/tests/unit/test_parser.py +74 -0
  42. pyjpx_etf-0.2.0/uv.lock +1644 -0
@@ -0,0 +1,103 @@
1
+ # pyjpx-etf
2
+
3
+ A clean, beginner-friendly Python library for fetching JPX ETF portfolio composition (PCF) data.
4
+
5
+ ## Quick Start Commands
6
+
7
+ ```bash
8
+ # Setup
9
+ uv sync
10
+
11
+ # Run unit tests
12
+ uv run pytest tests/unit/ -v
13
+
14
+ # Run integration tests (hits live endpoints, requires JST business hours)
15
+ uv run pytest tests/integration/ --integration -v
16
+
17
+ # Format & lint
18
+ uv run ruff format .
19
+ uv run ruff check .
20
+
21
+ # Serve docs
22
+ uv run mkdocs serve
23
+
24
+ # Build docs (strict)
25
+ uv run mkdocs build --strict
26
+ ```
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ src/pyjpx_etf/
32
+ ├── __init__.py # Public API: ETF class, config, exceptions, models
33
+ ├── etf.py # ETF class (main entry, lazy-loaded) + _resolve_japanese_names()
34
+ ├── models.py # ETFInfo, Holding frozen dataclasses
35
+ ├── config.py # Provider URLs, timeout, delay, lang (mutable singleton, lang validated)
36
+ ├── exceptions.py # PyJPXETFError → ETFNotFoundError, FetchError, ParseError
37
+ ├── cli.py # CLI entry point: `etf <code|alias> [--en] [-a]`, aliases (topix, 225, sox, fang, jpsox1, jpsox2)
38
+ └── _internal/
39
+ ├── fetcher.py # I/O only: HTTP GET → raw CSV text (provider fallback)
40
+ ├── parser.py # Pure parse: CSV text → models (no I/O)
41
+ ├── master.py # JPX master list: fetch XLS (_fetch_master_xls) + parse (_parse_master_xls), 2-tier cache
42
+ └── fees.py # JPX ETF fees (信託報酬): fetch HTML (_fetch_fee_html) + parse (_parse_fee_html), 2-tier cache
43
+ ```
44
+
45
+ ### Key Design Patterns
46
+
47
+ - **Fetch/Parse split**: `_internal/fetcher.py` does HTTP only, `_internal/parser.py` does CSV parsing only. `_internal/master.py` and `_internal/fees.py` also follow this split.
48
+ - **Lazy loading**: ETF data fetched on first `.info` or `.holdings` access
49
+ - **Provider fallback**: Try ICE first, then Solactive. CSV content validated (rejects HTML 200s)
50
+ - **Error precedence**: ETFNotFoundError only if *all* providers return 404; any server/network error → FetchError
51
+ - **Name resolution**: Extracted to `_resolve_japanese_names()` in `etf.py` — keeps `_load()` focused on fetch+parse
52
+ - **Config validation**: `config.lang` only accepts `"ja"` or `"en"`, raises `ValueError` otherwise
53
+
54
+ ## Data Sources
55
+
56
+ | Provider | URL Pattern | Covers |
57
+ |---|---|---|
58
+ | ICE Data Services | `https://inav.ice.com/pcf-download/{code}.csv` | Majority of TSE ETFs |
59
+ | Solactive AG | `https://www.solactive.com/downloads/etfservices/tse-pcf/single/{code}.csv` | Global X Japan ETFs |
60
+
61
+ - Available 7:50–23:55 JST on business days
62
+ - ICE returns HTML (not 404) outside hours and for unknown codes — fetcher handles this
63
+ - Solactive CSVs use `\r\n` line endings — parser normalizes this
64
+
65
+ ### Verified ETF Codes
66
+
67
+ | Code | Name | Provider |
68
+ |---|---|---|
69
+ | 1306 | TOPIX ETF | ICE |
70
+ | 1321 | Nikkei 225 ETF | ICE |
71
+ | 1348 | MAXIS TOPIX ETF | ICE |
72
+ | 2564 | Global X MSCI SuperDividend Japan ETF | Solactive |
73
+ | 2627 | Global X E-Commerce Japan ETF | Solactive |
74
+ | 2644 | Global X Japan Semiconductor ETF | Solactive |
75
+ | 2243 | Global X US Tech Top 20 ETF | Solactive |
76
+ | 200A | 日経半導体株 ETF | ICE |
77
+ | 316A | iShares 日経半導体株 ETF | ICE |
78
+
79
+ ## API
80
+
81
+ ```python
82
+ import pyjpx_etf as etf
83
+
84
+ e = etf.ETF("1306")
85
+ e.info # ETFInfo dataclass
86
+ e.info.name # "TOPIX ETF"
87
+ e.info.to_dict() # dict of all fields
88
+ e.nav # total fund NAV in yen (int)
89
+ e.fee # 0.06 (trust fee %, or None)
90
+ e.holdings # list[Holding]
91
+ e.to_dataframe() # pd.DataFrame with weights
92
+
93
+ etf.config.timeout = 60
94
+ etf.config.request_delay = 0.5
95
+ ```
96
+
97
+ ## Dependencies
98
+
99
+ - `requests>=2.32` — HTTP
100
+ - `pandas>=2.0` — DataFrame output
101
+ - `xlrd>=2.0` — required by `pd.read_excel` for JPX master `.xls` files
102
+ - `lxml>=5.0` — required by `pd.read_html` for JPX ETF fee page
103
+ - No Pydantic, no async
@@ -0,0 +1,33 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Skill(tasks-init)",
5
+ "Bash(uv sync 2>&1)",
6
+ "Bash(uv run pytest tests/unit/ -v --tb=short 2>&1)",
7
+ "Bash(uv run pytest --tb=short 2>&1)",
8
+ "Bash(uv run mkdocs build --strict 2>&1)",
9
+ "Bash(uv run ruff check --select F401,F841 2>&1)",
10
+ "Bash(uv run pytest --tb=short -v 2>&1)",
11
+ "Bash(uv run python -c \"\nimport requests\n\n# Try Solactive first \\(Global X Japan\\)\nurl = 'https://www.solactive.com/downloads/etfservices/tse-pcf/single/2644.csv'\nr = requests.get\\(url, timeout=30\\)\nprint\\(f'Solactive status: {r.status_code}'\\)\nif r.status_code == 200:\n print\\('--- First 30 lines ---'\\)\n for i, line in enumerate\\(r.text.split\\('\\\\n'\\)[:30]\\):\n print\\(f'{i:3}: {repr\\(line\\)}'\\)\nelse:\n # Try ICE\n url2 = 'https://inav.ice.com/pcf-download/2644.csv'\n r2 = requests.get\\(url2, timeout=30\\)\n print\\(f'ICE status: {r2.status_code}'\\)\n if r2.status_code == 200:\n print\\('--- First 30 lines ---'\\)\n for i, line in enumerate\\(r2.text.split\\('\\\\n'\\)[:30]\\):\n print\\(f'{i:3}: {repr\\(line\\)}'\\)\n\")",
12
+ "Bash(uv run python -c \"\nimport requests\nurl = 'https://www.solactive.com/downloads/etfservices/tse-pcf/single/2644.csv'\nr = requests.get\\(url, timeout=30\\)\ntext = r.text\n# Show exact bytes around blank line\nidx = text.find\\('20260227'\\)\nsnippet = text[idx:idx+50]\nprint\\('Bytes around section break:', repr\\(snippet\\)\\)\n\n# Try the normalize\nnorm = text.replace\\('\\\\r\\\\n', '\\\\n'\\).strip\\(\\)\nsections = norm.split\\('\\\\n\\\\n'\\)\nprint\\(f'Sections after normalize: {len\\(sections\\)}'\\)\nif len\\(sections\\) >= 2:\n print\\(f'Section 1 lines: {sections[0].count\\(chr\\(10\\)\\)+1}'\\)\n print\\(f'Section 2 lines: {sections[1].count\\(chr\\(10\\)\\)+1}'\\)\n\")",
13
+ "Bash(uv run python -c \"import pyjpx_etf._internal.parser; print\\(pyjpx_etf._internal.parser.__file__\\)\")",
14
+ "Bash(uv run python -c \"\nimport inspect, pyjpx_etf._internal.parser as p\nsrc = inspect.getsource\\(p.parse_pcf\\)\nprint\\(src[:300]\\)\n\")",
15
+ "Bash(find /Users/soshimizutani/dev/pyjpx-etf/src -name \"*.pyc\" -delete && uv run pytest tests/unit/ --tb=short -v 2>&1)",
16
+ "Bash(find /Users/soshimizutani/dev/pyjpx-etf -name \"*.pyc\" -not -path '*/.venv/*' -delete && uv run pytest tests/integration/ --integration --tb=short -v 2>&1)",
17
+ "Skill(audit)",
18
+ "Bash(uv run mkdocs gh-deploy --strict)",
19
+ "Bash(uv run pytest tests/unit/ --tb=short 2>&1)",
20
+ "Bash(uv run etf 1306 2>&1)",
21
+ "Bash(uv run python -c \"\nfrom pyjpx_etf._internal.fetcher import fetch_pcf\ncsv = fetch_pcf\\('1306'\\)\n# Show header rows and first few data lines\nlines = csv.replace\\('\\\\r\\\\n', '\\\\n'\\).strip\\(\\).split\\('\\\\n'\\)\nfor line in lines[:3]:\n print\\(line\\)\nprint\\('...'\\)\nsections = csv.replace\\('\\\\r\\\\n', '\\\\n'\\).strip\\(\\).split\\('\\\\n\\\\n'\\)\nholdings_lines = sections[1].split\\('\\\\n'\\)\nfor line in holdings_lines[:5]:\n print\\(line\\)\n\" 2>&1)",
22
+ "Bash(uv run etf 1306 --en 2>&1)",
23
+ "Bash(uv run pytest tests/unit/test_master.py -v --tb=short 2>&1)",
24
+ "Bash(uv run pytest tests/unit/test_etf.py -v --tb=short 2>&1)",
25
+ "WebSearch",
26
+ "Bash(uv run pytest tests/unit/ -v 2>&1)",
27
+ "Bash(uv run etf 2042)",
28
+ "Bash(uv run pytest tests/unit/ --tb=short -q)",
29
+ "Bash(uv run ruff check --select F401,F841 src/)",
30
+ "WebFetch(domain:api.github.com)"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.13"
18
+
19
+ - name: Install build tools
20
+ run: pip install build
21
+
22
+ - name: Build package
23
+ run: python -m build
24
+
25
+ - name: Publish to PyPI
26
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,16 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+
8
+ # Virtual environment
9
+ .venv/
10
+
11
+ # Tools
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+
15
+ # MkDocs build output
16
+ site/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 obichan117
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyjpx-etf
3
+ Version: 0.2.0
4
+ Summary: A clean, beginner-friendly Python library for fetching JPX ETF portfolio composition (PCF) data.
5
+ Project-URL: Homepage, https://github.com/obichan117/pyjpx-etf
6
+ Project-URL: Documentation, https://obichan117.github.io/pyjpx-etf
7
+ Project-URL: Repository, https://github.com/obichan117/pyjpx-etf
8
+ Author: obichan117
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: etf,finance,japan,jpx,pcf,portfolio
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Office/Business :: Financial :: Investment
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: lxml>=5.0
23
+ Requires-Dist: pandas>=2.0
24
+ Requires-Dist: requests>=2.32
25
+ Requires-Dist: xlrd>=2.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # pyjpx-etf
29
+
30
+ A clean, beginner-friendly Python library for fetching JPX ETF portfolio composition (PCF) data.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install pyjpx-etf
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ import pyjpx_etf as etf
42
+
43
+ e = etf.ETF("1306")
44
+ print(e.info.name) # "TOPIX連動型上場投資信託"
45
+ print(e.nav) # total fund NAV in yen
46
+ print(e.fee) # trust fee (%) e.g. 0.06
47
+ print(e.holdings[:3])
48
+ # [Holding(code='7203', name='トヨタ自動車', ...),
49
+ # Holding(code='8306', name='三菱UFJフィナンシャル・グループ', ...),
50
+ # Holding(code='6758', name='ソニーグループ', ...)]
51
+ ```
52
+
53
+ ## CLI
54
+
55
+ ```
56
+ $ etf 1306
57
+
58
+ 1306 — TOPIX連動型上場投資信託 (2026-02-27)
59
+ Nav: 5170億 信託報酬: 0.06%
60
+
61
+ Code Name Weight
62
+ ───── ────────────────────────────────── ──────
63
+ 7203 トヨタ自動車 3.7%
64
+ 8306 三菱UFJフィナンシャル・グループ 3.3%
65
+ 6501 日立製作所 2.4%
66
+ ...
67
+ ```
68
+
69
+ ### Aliases
70
+
71
+ Use shorthand aliases instead of codes:
72
+
73
+ | Alias | Code | ETF |
74
+ |-------|------|-----|
75
+ | `etf topix` | 1306 | TOPIX連動型上場投資信託 |
76
+ | `etf 225` | 1321 | 日経225連動型上場投資信託 |
77
+ | `etf core30` | 1311 | TOPIX Core30連動型上場投資信託 |
78
+ | `etf div50` | 1489 | 日経平均高配当株50指数連動型ETF |
79
+ | `etf div70` | 1577 | 野村日本株高配当70連動型ETF |
80
+ | `etf div100` | 1698 | 上場インデックスファンド日本高配当 |
81
+ | `etf pbr` | 2080 | PBR1倍割れ解消推進ETF |
82
+ | `etf fang` | 2243 | Global X US Tech Top 20 ETF |
83
+ | `etf sox` | 2644 | Global X 半導体関連-日本株式 ETF |
84
+ | `etf jpsox1` | 200A | 日経半導体株 ETF |
85
+ | `etf jpsox2` | 316A | iShares 日経半導体株 ETF |
86
+
87
+ ### Options
88
+
89
+ ```
90
+ $ etf sox --en # English names
91
+ $ etf topix -a # all holdings (not just top 10)
92
+ $ etf 1306 -a --en # combine options
93
+ ```
94
+
95
+ ## Language Setting
96
+
97
+ Names default to Japanese. Switch to English via config or CLI flag:
98
+
99
+ ```python
100
+ import pyjpx_etf as etf
101
+
102
+ # English names
103
+ etf.config.lang = "en"
104
+ e = etf.ETF("1306")
105
+ print(e.info.name) # "TOPIX ETF"
106
+ ```
107
+
108
+ ## Configuration
109
+
110
+ ```python
111
+ import pyjpx_etf as etf
112
+
113
+ etf.config.timeout = 60
114
+ etf.config.request_delay = 0.5
115
+ etf.config.lang = "en" # "ja" (default) or "en"
116
+ ```
117
+
118
+ ## Documentation
119
+
120
+ [Full documentation](https://obichan117.github.io/pyjpx-etf)
121
+
122
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/obichan117/pyjpx-etf/blob/main/examples/quickstart.ipynb)
@@ -0,0 +1,95 @@
1
+ # pyjpx-etf
2
+
3
+ A clean, beginner-friendly Python library for fetching JPX ETF portfolio composition (PCF) data.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install pyjpx-etf
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import pyjpx_etf as etf
15
+
16
+ e = etf.ETF("1306")
17
+ print(e.info.name) # "TOPIX連動型上場投資信託"
18
+ print(e.nav) # total fund NAV in yen
19
+ print(e.fee) # trust fee (%) e.g. 0.06
20
+ print(e.holdings[:3])
21
+ # [Holding(code='7203', name='トヨタ自動車', ...),
22
+ # Holding(code='8306', name='三菱UFJフィナンシャル・グループ', ...),
23
+ # Holding(code='6758', name='ソニーグループ', ...)]
24
+ ```
25
+
26
+ ## CLI
27
+
28
+ ```
29
+ $ etf 1306
30
+
31
+ 1306 — TOPIX連動型上場投資信託 (2026-02-27)
32
+ Nav: 5170億 信託報酬: 0.06%
33
+
34
+ Code Name Weight
35
+ ───── ────────────────────────────────── ──────
36
+ 7203 トヨタ自動車 3.7%
37
+ 8306 三菱UFJフィナンシャル・グループ 3.3%
38
+ 6501 日立製作所 2.4%
39
+ ...
40
+ ```
41
+
42
+ ### Aliases
43
+
44
+ Use shorthand aliases instead of codes:
45
+
46
+ | Alias | Code | ETF |
47
+ |-------|------|-----|
48
+ | `etf topix` | 1306 | TOPIX連動型上場投資信託 |
49
+ | `etf 225` | 1321 | 日経225連動型上場投資信託 |
50
+ | `etf core30` | 1311 | TOPIX Core30連動型上場投資信託 |
51
+ | `etf div50` | 1489 | 日経平均高配当株50指数連動型ETF |
52
+ | `etf div70` | 1577 | 野村日本株高配当70連動型ETF |
53
+ | `etf div100` | 1698 | 上場インデックスファンド日本高配当 |
54
+ | `etf pbr` | 2080 | PBR1倍割れ解消推進ETF |
55
+ | `etf fang` | 2243 | Global X US Tech Top 20 ETF |
56
+ | `etf sox` | 2644 | Global X 半導体関連-日本株式 ETF |
57
+ | `etf jpsox1` | 200A | 日経半導体株 ETF |
58
+ | `etf jpsox2` | 316A | iShares 日経半導体株 ETF |
59
+
60
+ ### Options
61
+
62
+ ```
63
+ $ etf sox --en # English names
64
+ $ etf topix -a # all holdings (not just top 10)
65
+ $ etf 1306 -a --en # combine options
66
+ ```
67
+
68
+ ## Language Setting
69
+
70
+ Names default to Japanese. Switch to English via config or CLI flag:
71
+
72
+ ```python
73
+ import pyjpx_etf as etf
74
+
75
+ # English names
76
+ etf.config.lang = "en"
77
+ e = etf.ETF("1306")
78
+ print(e.info.name) # "TOPIX ETF"
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+ ```python
84
+ import pyjpx_etf as etf
85
+
86
+ etf.config.timeout = 60
87
+ etf.config.request_delay = 0.5
88
+ etf.config.lang = "en" # "ja" (default) or "en"
89
+ ```
90
+
91
+ ## Documentation
92
+
93
+ [Full documentation](https://obichan117.github.io/pyjpx-etf)
94
+
95
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/obichan117/pyjpx-etf/blob/main/examples/quickstart.ipynb)
@@ -0,0 +1,7 @@
1
+ # Config
2
+
3
+ ::: pyjpx_etf.config
4
+ options:
5
+ show_root_heading: true
6
+
7
+ ::: pyjpx_etf.config.Config
@@ -0,0 +1,3 @@
1
+ # ETF
2
+
3
+ ::: pyjpx_etf.ETF
@@ -0,0 +1,9 @@
1
+ # Exceptions
2
+
3
+ ::: pyjpx_etf.PyJPXETFError
4
+
5
+ ::: pyjpx_etf.ETFNotFoundError
6
+
7
+ ::: pyjpx_etf.FetchError
8
+
9
+ ::: pyjpx_etf.ParseError
@@ -0,0 +1,68 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ pip install pyjpx-etf
7
+ ```
8
+
9
+ ## Basic Usage
10
+
11
+ ```python
12
+ import pyjpx_etf as etf
13
+
14
+ e = etf.ETF("1306")
15
+
16
+ # ETF metadata (Japanese names by default)
17
+ print(e.info.name) # "TOPIX連動型上場投資信託"
18
+ print(e.info.cash_component) # 496973797639.0
19
+ print(e.info.shares_outstanding) # 8133974978
20
+ print(e.info.date) # datetime.date(2026, 2, 27)
21
+
22
+ # Holdings
23
+ for h in e.holdings[:3]:
24
+ print(f"{h.name}: {h.weight:.2%}")
25
+ # トヨタ自動車: 3.80%
26
+ # 三菱UFJフィナンシャル・グループ: 2.50%
27
+ # ソニーグループ: 2.30%
28
+
29
+ # Full DataFrame
30
+ df = e.to_dataframe()
31
+ ```
32
+
33
+ ### Language
34
+
35
+ Names default to Japanese (`config.lang = "ja"`). Switch to English:
36
+
37
+ ```python
38
+ etf.config.lang = "en"
39
+ e = etf.ETF("1306")
40
+ print(e.info.name) # "TOPIX ETF"
41
+ ```
42
+
43
+ The CLI uses `--en` for English:
44
+
45
+ ```
46
+ $ etf 1306 --en
47
+ ```
48
+
49
+ ### Configuration
50
+
51
+ ```python
52
+ import pyjpx_etf as etf
53
+
54
+ etf.config.timeout = 60 # HTTP timeout in seconds
55
+ etf.config.request_delay = 0.5 # Delay between provider retries
56
+ etf.config.lang = "en" # "ja" (default) or "en"
57
+ ```
58
+
59
+ ## Data Sources
60
+
61
+ pyjpx-etf fetches PCF (Portfolio Composition File) CSVs from:
62
+
63
+ | Provider | Covers |
64
+ |---|---|
65
+ | ICE Data Services | Majority of TSE ETFs |
66
+ | Solactive AG | Global X Japan ETFs |
67
+
68
+ Data is updated daily on business days, available 7:50–23:55 JST.
@@ -0,0 +1,96 @@
1
+ # ETF Data
2
+
3
+ ## The ETF Class
4
+
5
+ The `ETF` class is the main entry point. It lazily fetches data on first access.
6
+
7
+ ```python
8
+ import pyjpx_etf as etf
9
+
10
+ e = etf.ETF("1306")
11
+ ```
12
+
13
+ ### ETF Info
14
+
15
+ Access metadata via the `info` property. Names are Japanese by default:
16
+
17
+ ```python
18
+ e.info.code # "1306"
19
+ e.info.name # "TOPIX連動型上場投資信託"
20
+ e.info.cash_component # Fund cash component
21
+ e.info.shares_outstanding # Shares outstanding
22
+ e.info.date # Fund date as datetime.date
23
+
24
+ # Convert to dict
25
+ dict_info = e.info.to_dict()
26
+ ```
27
+
28
+ ### Holdings
29
+
30
+ Access constituent holdings:
31
+
32
+ ```python
33
+ for h in e.holdings[:3]:
34
+ print(f"{h.code} {h.name}: {h.weight:.2%}")
35
+ # 7203 トヨタ自動車: 3.80%
36
+ # 8306 三菱UFJフィナンシャル・グループ: 2.50%
37
+ # 6758 ソニーグループ: 2.30%
38
+ ```
39
+
40
+ Each `Holding` has: `code`, `name`, `isin`, `exchange`, `currency`, `shares`, `price`, `weight`.
41
+
42
+ ### NAV
43
+
44
+ Total fund net asset value (cash + holdings market value) in yen:
45
+
46
+ ```python
47
+ e.nav # 515997003139 (in yen)
48
+ ```
49
+
50
+ ### Fee (信託報酬)
51
+
52
+ Trust fee (信託報酬) from the JPX ETF list page. Independent of PCF data:
53
+
54
+ ```python
55
+ e.fee # 0.06 (means 0.06%)
56
+ ```
57
+
58
+ Returns `None` if the fee is unavailable for the given ETF code.
59
+
60
+ ### DataFrame Output
61
+
62
+ ```python
63
+ df = e.to_dataframe()
64
+ # Columns: code, name, isin, exchange, currency, shares, price, weight
65
+ ```
66
+
67
+ ### Language
68
+
69
+ Set `config.lang` before creating an `ETF` instance:
70
+
71
+ ```python
72
+ import pyjpx_etf as etf
73
+
74
+ # Japanese (default)
75
+ e = etf.ETF("1306")
76
+ e.info.name # "TOPIX連動型上場投資信託"
77
+
78
+ # English
79
+ etf.config.lang = "en"
80
+ e = etf.ETF("1306")
81
+ e.info.name # "TOPIX ETF"
82
+ ```
83
+
84
+ ## Error Handling
85
+
86
+ ```python
87
+ from pyjpx_etf import ETFNotFoundError, FetchError, ParseError
88
+
89
+ try:
90
+ e = etf.ETF("9999")
91
+ _ = e.info
92
+ except ETFNotFoundError:
93
+ print("ETF not found")
94
+ except FetchError:
95
+ print("Network error")
96
+ ```
@@ -0,0 +1,60 @@
1
+ # pyjpx-etf
2
+
3
+ A clean, beginner-friendly Python library for fetching JPX ETF portfolio composition (PCF) data.
4
+
5
+ ## Features
6
+
7
+ - **Simple API** — yfinance-style `ETF("1306")` interface
8
+ - **Japanese names by default** — powered by the JPX master list
9
+ - **No authentication** — uses free, public PCF CSV endpoints
10
+ - **Lightweight** — just `requests`, `pandas`, and `xlrd`
11
+ - **Auto provider detection** — tries ICE Data Services, then Solactive
12
+
13
+ ## Quick Example
14
+
15
+ ```python
16
+ import pyjpx_etf as etf
17
+
18
+ e = etf.ETF("1306")
19
+ print(e.info.name) # "TOPIX連動型上場投資信託"
20
+ print(e.top())
21
+ ```
22
+
23
+ ```
24
+ code name weight
25
+ 0 7203 トヨタ自動車 3.8
26
+ 1 8306 三菱UFJフィナンシャル・グループ 2.5
27
+ 2 6758 ソニーグループ 2.3
28
+ ...
29
+ ```
30
+
31
+ ### CLI
32
+
33
+ ```
34
+ $ etf 1306
35
+
36
+ 1306 — NEXT FUNDS TOPIX連動型上場投信 (2026-02-27)
37
+
38
+ Code Name Weight
39
+ ───── ────────────────────────────────── ──────
40
+ 7203 トヨタ自動車 3.7%
41
+ 8306 三菱UFJフィナンシャル・グループ 3.3%
42
+ 6501 日立製作所 2.4%
43
+ 8316 三井住友フィナンシャルグループ 2.3%
44
+ 6758 ソニーグループ 2.1%
45
+ 8058 三菱商事 2.0%
46
+ 8411 みずほフィナンシャルグループ 1.8%
47
+ 8035 東京エレクトロン 1.7%
48
+ 7011 三菱重工業 1.7%
49
+ 6857 アドバンテスト 1.6%
50
+ ```
51
+
52
+ Use `--en` for English names: `etf 1306 --en`
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install pyjpx-etf
58
+ ```
59
+
60
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/obichan117/pyjpx-etf/blob/main/examples/quickstart.ipynb)