fundly 0.1.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.
- fundly-0.1.0/.gitignore +28 -0
- fundly-0.1.0/LICENSE +75 -0
- fundly-0.1.0/PKG-INFO +325 -0
- fundly-0.1.0/README.md +296 -0
- fundly-0.1.0/fundly/__init__.py +70 -0
- fundly-0.1.0/fundly/_version.py +1 -0
- fundly-0.1.0/fundly/cache.py +227 -0
- fundly-0.1.0/fundly/client.py +648 -0
- fundly-0.1.0/fundly/currency.py +25 -0
- fundly-0.1.0/fundly/errors.py +22 -0
- fundly-0.1.0/fundly/fx.py +121 -0
- fundly-0.1.0/fundly/ids.py +84 -0
- fundly-0.1.0/fundly/models.py +118 -0
- fundly-0.1.0/fundly/quality.py +55 -0
- fundly-0.1.0/fundly/search.py +73 -0
- fundly-0.1.0/fundly/sources/__init__.py +0 -0
- fundly-0.1.0/fundly/sources/argentina.py +233 -0
- fundly-0.1.0/fundly/sources/base.py +237 -0
- fundly-0.1.0/fundly/sources/brazil.py +203 -0
- fundly-0.1.0/fundly/sources/chile.py +114 -0
- fundly-0.1.0/fundly/sources/china.py +96 -0
- fundly-0.1.0/fundly/sources/colombia.py +106 -0
- fundly-0.1.0/fundly/sources/comparadorfondos.py +62 -0
- fundly-0.1.0/fundly/sources/finect.py +159 -0
- fundly-0.1.0/fundly/sources/ft.py +84 -0
- fundly-0.1.0/fundly/sources/id_managers.py +171 -0
- fundly-0.1.0/fundly/sources/india.py +192 -0
- fundly-0.1.0/fundly/sources/indonesia.py +176 -0
- fundly-0.1.0/fundly/sources/israel.py +110 -0
- fundly-0.1.0/fundly/sources/morningstar.py +102 -0
- fundly-0.1.0/fundly/sources/poland.py +61 -0
- fundly-0.1.0/fundly/sources/quefondos.py +34 -0
- fundly-0.1.0/fundly/sources/vietnam.py +103 -0
- fundly-0.1.0/fundly/sources/yahoo.py +80 -0
- fundly-0.1.0/pyproject.toml +67 -0
- fundly-0.1.0/tests/conftest.py +6 -0
- fundly-0.1.0/tests/test_client.py +239 -0
- fundly-0.1.0/tests/test_client_edges.py +285 -0
- fundly-0.1.0/tests/test_fx.py +176 -0
- fundly-0.1.0/tests/test_ids.py +94 -0
- fundly-0.1.0/tests/test_live.py +100 -0
- fundly-0.1.0/tests/test_live_asia.py +56 -0
- fundly-0.1.0/tests/test_live_latam.py +56 -0
- fundly-0.1.0/tests/test_live_misc.py +57 -0
- fundly-0.1.0/tests/test_live_sea.py +86 -0
- fundly-0.1.0/tests/test_multiprocess.py +61 -0
- fundly-0.1.0/tests/test_properties.py +122 -0
- fundly-0.1.0/tests/test_quality.py +209 -0
- fundly-0.1.0/tests/test_resilience.py +227 -0
- fundly-0.1.0/tests/test_search.py +168 -0
- fundly-0.1.0/tests/test_sources.py +352 -0
- fundly-0.1.0/tests/test_src_asia.py +170 -0
- fundly-0.1.0/tests/test_src_id_managers.py +130 -0
- fundly-0.1.0/tests/test_src_latam.py +257 -0
- fundly-0.1.0/tests/test_src_misc.py +313 -0
- fundly-0.1.0/tests/test_src_sea.py +208 -0
fundly-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# environments (.venv is a link to .venv.nosync so iCloud doesn't sync it)
|
|
2
|
+
.venv
|
|
3
|
+
.venv/
|
|
4
|
+
.venv.nosync/
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.pyc
|
|
7
|
+
|
|
8
|
+
# tool caches and build output
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.hypothesis/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
|
|
18
|
+
# local fundly caches
|
|
19
|
+
*.db
|
|
20
|
+
*.db-*
|
|
21
|
+
|
|
22
|
+
# duplicates iCloud creates on sync conflicts ("uv 2.lock")
|
|
23
|
+
* 2
|
|
24
|
+
* 2.*
|
|
25
|
+
.DS_Store
|
|
26
|
+
|
|
27
|
+
# git metadata lives here so iCloud does not sync it (.git is a pointer file)
|
|
28
|
+
.git.nosync/
|
fundly-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Required Notice: Copyright (c) 2026 antooro
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncommercial Purposes
|
|
34
|
+
|
|
35
|
+
Any noncommercial purpose is a permitted purpose.
|
|
36
|
+
|
|
37
|
+
## Personal Uses
|
|
38
|
+
|
|
39
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
40
|
+
|
|
41
|
+
## Noncommercial Organizations
|
|
42
|
+
|
|
43
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
44
|
+
|
|
45
|
+
## Fair Use
|
|
46
|
+
|
|
47
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
48
|
+
|
|
49
|
+
## No Other Rights
|
|
50
|
+
|
|
51
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
52
|
+
|
|
53
|
+
## Patent Defense
|
|
54
|
+
|
|
55
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
56
|
+
|
|
57
|
+
## Violations
|
|
58
|
+
|
|
59
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
64
|
+
|
|
65
|
+
## Definitions
|
|
66
|
+
|
|
67
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
68
|
+
|
|
69
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
70
|
+
|
|
71
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
72
|
+
|
|
73
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
74
|
+
|
|
75
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
fundly-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fundly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Prices, history and search for mutual funds, ETFs and pension plans worldwide, by ISIN, ticker or local code
|
|
5
|
+
Project-URL: Homepage, https://github.com/antooro/fundly
|
|
6
|
+
Project-URL: Source, https://github.com/antooro/fundly
|
|
7
|
+
Project-URL: Issues, https://github.com/antooro/fundly/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/antooro/fundly/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: PolyForm-Noncommercial-1.0.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: etf,finance,fondos de inversión,funds,isin,morningstar,mutual funds,nav,pension,planes de pensiones
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: tzdata; sys_platform == 'win32'
|
|
26
|
+
Provides-Extra: pandas
|
|
27
|
+
Requires-Dist: pandas>=1.5; extra == 'pandas'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="https://raw.githubusercontent.com/antooro/fundly/main/docs/banner.svg" alt="fundly: prices for funds, ETFs and pension plans, worldwide" width="100%">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://github.com/antooro/fundly/actions/workflows/tests.yml"><img alt="Tests" src="https://github.com/antooro/fundly/actions/workflows/tests.yml/badge.svg"></a>
|
|
36
|
+
<img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white">
|
|
37
|
+
<img alt="License: PolyForm Noncommercial 1.0.0" src="https://img.shields.io/badge/license-PolyForm%20Noncommercial-3ddc97">
|
|
38
|
+
<img alt="Status: alpha" src="https://img.shields.io/badge/status-alpha-f5b84b">
|
|
39
|
+
<img alt="Sources: 17" src="https://img.shields.io/badge/sources-17-7aa2ff">
|
|
40
|
+
<img alt="Dependencies: httpx" src="https://img.shields.io/badge/dependencies-httpx-7aa2ff">
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<b>One Python call for the price of any mutual fund, ETF or pension plan in the world.<br>
|
|
45
|
+
Look it up by ISIN, ticker or its local code; no API keys, no accounts.</b>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
Over <b>350,000 funds</b> and <b>38,000 ETF listings</b> worldwide, plus <b>45,000+ funds and pension plans</b> from national sources.
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import fundly
|
|
56
|
+
|
|
57
|
+
fundly.quote("VTSAX").price # 182.82 US mutual fund, by ticker
|
|
58
|
+
fundly.quote("ES0112611001").price # 367.82113 Spanish fund, by ISIN
|
|
59
|
+
fundly.quote("cl-afp:HABITAT-A").price # 100365.02 Chilean pension fund, local code
|
|
60
|
+
fundly.history("INF179K01XQ0").to_pandas() # Indian fund, daily history as a pandas Series
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Why fundly
|
|
64
|
+
|
|
65
|
+
Ticker-based tools leave most of the world's savings out: European share classes, Indian schemes, Chilean AFPs, Spanish pension plans. fundly starts from the identifiers people actually have, asks several sources, and tells you exactly what kind of price you got.
|
|
66
|
+
|
|
67
|
+
| | **fundly** | yfinance | mstarpy |
|
|
68
|
+
|---|:---:|:---:|:---:|
|
|
69
|
+
| Look up by **ISIN** | ✅ | via search | via search |
|
|
70
|
+
| **Local codes** (AMFI, CAFCI, AFP, DGS…) | ✅ | ❌ | ❌ |
|
|
71
|
+
| **Pension plans** in 7 countries | ✅ | ❌ | ❌ |
|
|
72
|
+
| Says whether a price is **NAV, exchange close or index** | ✅ | ❌ | ❌ |
|
|
73
|
+
| **Several sources** with automatic fallback | ✅ | ❌ | ❌ |
|
|
74
|
+
| **Cross-check** sources side by side | ✅ | ❌ | ❌ |
|
|
75
|
+
| Needs a browser / Selenium | no | no | yes (v10+) |
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install fundly # pip install "fundly[pandas]" for .to_pandas()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## A quick tour
|
|
84
|
+
|
|
85
|
+
**Search by name, in any language.** Every word counts, in any order, accents optional:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
for f in fundly.search("emaxis slim", limit=3):
|
|
89
|
+
print(f.id, f.name)
|
|
90
|
+
# JP90C000PZY9 MUAM eMAXIS Slim All US Equity
|
|
91
|
+
# JP90C000G7W3 MUAM eMAXIS Slim All World Eq 3 Reg Eql
|
|
92
|
+
# JP90C000H1T1 MUAM eMAXIS Slim All Wld Eq All Country
|
|
93
|
+
|
|
94
|
+
[f.id for f in fundly.search("superfondo acciones", limit=3)]
|
|
95
|
+
# ['ar-cafci:148', 'ar-cafci:304', 'ar-cafci:1373']
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Any identifier lands on the same fund.** Tickers and local codes are translated to the ISIN when one exists, so caching and cross-checking work across them:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
fundly.quote("in-amfi:118989").id # 'INF179K01XQ0' (AMFI scheme code → ISIN)
|
|
102
|
+
fundly.quote("VOO").id, fundly.instrument("VOO").type # ('US9229083632', 'etf')
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Daily history**, straight into pandas:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
fundly.history("N5508", start="2026-10-01").to_pandas().tail(3) # Spanish pension plan
|
|
109
|
+
# date
|
|
110
|
+
# 2026-10-21 134.58548
|
|
111
|
+
# 2026-10-22 133.59280
|
|
112
|
+
# 2026-10-23 133.50458
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Cross-check every source.** Independent feeds agreeing to the last decimal is how you know a price is right:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
c = fundly.compare("ES0173311103") # Renta 4 Numantia Patrimonio Global
|
|
119
|
+
for q in c.quotes:
|
|
120
|
+
print(f"{q.source:<12} {q.price:>9} {q.currency} {q.date}")
|
|
121
|
+
# morningstar 29.3605 EUR 2026-10-23
|
|
122
|
+
# finect 29.00609 EUR 2026-10-21
|
|
123
|
+
# ft 29.3605 EUR 2026-10-23
|
|
124
|
+
# quefondos 29.41016 EUR 2026-10-22
|
|
125
|
+
# yahoo 29.4102 EUR 2026-10-22
|
|
126
|
+
|
|
127
|
+
c.nav_spread_pct # 0.0 (spread between sources that share the latest date)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Know what kind of price you got.** Every quote says whether it is an official NAV, an exchange close, or an index rebuilt from published returns:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
fundly.quote("IE00B4L5Y983").price_type # 'nav' iShares Core MSCI World: issuer NAV
|
|
134
|
+
fundly.quote("IE00B4L5Y983", source="morningstar").price_type # 'market' exchange close
|
|
135
|
+
fundly.quote("il-gemel:880").price_type # 'index' Israeli fund: monthly returns only
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Spot bad data instead of trusting it blindly.** A price older than expected is flagged `stale`; one far from what other sources said in the last month is checked against the next source and replaced or flagged `suspicious`. Histories note isolated spikes and ×100 jumps:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
q = fundly.quote("ar-cafci:3906") # Argentine agro fund that stopped reporting
|
|
142
|
+
q.stale, q.age_days, q.warnings
|
|
143
|
+
# (True, 90, ('stale: last price is 90 days old (2026-07-31)',))
|
|
144
|
+
|
|
145
|
+
q.to_dict() # every result is JSON-ready
|
|
146
|
+
# {'id': 'ar-cafci:3906', 'name': 'ADBlick Granos Plus FCIC', 'price': 0.488829, 'date': '2026-07-31', ...}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Value everything in one currency.** Prices come in each fund's own currency; ask for another and fundly converts at the ECB reference rate of the price's date, keeping the original figure:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
q = fundly.quote("INF179K01XQ0", currency="EUR") # Indian fund, in euros
|
|
153
|
+
round(q.price, 4), q.original_price, q.original_currency, round(q.fx_rate, 6), q.fx_source
|
|
154
|
+
# (2.0377, 216.123, 'INR', 0.009428, 'ecb')
|
|
155
|
+
|
|
156
|
+
fundly.history("VTSAX", start="2025-01-01", currency="EUR") # each day at that day's rate
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Coverage
|
|
160
|
+
|
|
161
|
+
Real quotes taken with fundly on 27 October 2026:
|
|
162
|
+
|
|
163
|
+
| Market | Example id | What it is | Latest | Source |
|
|
164
|
+
|---|---|---|---|---|
|
|
165
|
+
| 🌍 Worldwide funds | `ZAE000025029` | Allan Gray Balanced A | 201.67 ZAR | Morningstar |
|
|
166
|
+
| 🌍 Worldwide ETFs | `VOO` → `US9229083632` | Vanguard S&P 500 ETF | 703.41 USD (market) | Morningstar |
|
|
167
|
+
| 🇺🇸 US mutual funds | `VTSAX` | Vanguard Total Stock Market Admiral | 182.82 USD | Morningstar |
|
|
168
|
+
| 🇪🇸 Spanish funds | `ES0112611001` | Azvalor Internacional FI | 367.82 EUR | Morningstar, Finect, FT, Quefondos |
|
|
169
|
+
| 🇪🇸 Spanish pension plans | `N5508` | Santander Autónomos RVM PPES | 133.50 EUR | Finect, comparadorfondos |
|
|
170
|
+
| 🇬🇧 UK pension & life funds | `ft:0P00009VB8` | Aviva Pension Invesco Emerging Markets | 2.53 GBP | FT |
|
|
171
|
+
| 🇵🇱 Poland (funds, PPK, OFE) | `pl-analizy:ARK52` | Erste PPK 2050 | 2,439.80 PLN | analizy.pl |
|
|
172
|
+
| 🇮🇳 India mutual funds | `in-amfi:118989` | HDFC Mid Cap Direct Growth | 216.12 INR | AMFI (official) |
|
|
173
|
+
| 🇮🇳 India NPS pensions | `in-nps:SM001001` | SBI Pension Fund, Central Govt | 49.36 INR | npsnav.in |
|
|
174
|
+
| 🇨🇳 China | `cn:110022` | 易方达消费行业股票 | 2.79 CNY | Eastmoney |
|
|
175
|
+
| 🇯🇵 Japan | `JP90C000H1T1` | eMAXIS Slim All Country | 37,441 JPY | Morningstar |
|
|
176
|
+
| 🇻🇳 Vietnam | `vn-fmarket:VFF` | VinaCapital Enhanced Bond | 27,154.70 VND | fmarket |
|
|
177
|
+
| 🇮🇩 Indonesia | `IDN000007002` | Simpan Balanced | 944.27 IDR | Morningstar, Pasardana, fund managers |
|
|
178
|
+
| 🇨🇱 Chile pension funds | `cl-afp:HABITAT-A` | AFP Habitat, Fund A | 100,365.02 CLP | Superintendencia de Pensiones |
|
|
179
|
+
| 🇨🇴 Colombia pensions & funds | `co-pension:10-6000` | Colfondos, higher-risk fund | 97,116.41 COP | datos.gov.co |
|
|
180
|
+
| 🇧🇷 Brazil funds | `br-cvm:00017024000153` | Renda fixa class | 45.26 BRL | CVM (official open data) |
|
|
181
|
+
| 🇦🇷 Argentina funds | `ar-cafci:148` | Superfondo Acciones, Clase A | 486.53 ARS | CAFCI, argentinadatos |
|
|
182
|
+
| 🇮🇱 Israel provident & pension | `il-gemel:880` | Meitav Hishtalmut General | index | Gemel-net (gov) |
|
|
183
|
+
|
|
184
|
+
**Tested at random:** in 326 funds, ETFs and pension plans drawn at random from 15 universes (worldwide Morningstar pages, AMFI, CAFCI, CVM, Chilean AFPs, Spanish plans…), **96% returned a price** and none raised an unexpected error. The misses were mostly funds that have since closed but are still listed.
|
|
185
|
+
|
|
186
|
+
Morningstar alone reaches funds and ETFs in most countries (US, Canada, Europe, Japan, Korea, Taiwan, Hong Kong, Singapore, Australia, South Africa, the Gulf…). The national sources add official data and products Morningstar doesn't carry. Not covered yet: Turkey, Peru, Mexico, Hong Kong MPF and Australian superannuation. Their public data sits behind bot protection or isn't published.
|
|
187
|
+
|
|
188
|
+
**History depth.** Argentine funds have daily history from January 2020 for every class, from the argentinadatos.com community mirror of the regulator's (CNV) daily sheets. Indonesian funds get full history since launch from Morningstar (about a third of them), or from the manager's own site for Batavia, Trimegah, Avrist and Syailendra (about 140 more funds, some back to 1996). Others have Pasardana's last 12 months: older data is a paid product everywhere we looked. With the cache on, fundly keeps every Indonesian and Argentine price it downloads, so those histories grow on your machine instead of sliding along.
|
|
189
|
+
|
|
190
|
+
## Identifiers
|
|
191
|
+
|
|
192
|
+
| You type | Means | Example |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| ISIN | any fund or ETF | `IE00B4L5Y983` |
|
|
195
|
+
| ticker | listed ETF or US/CA mutual fund | `VTSAX`, `ticker:VOO` |
|
|
196
|
+
| `N####` · `S####` | Spanish pension plan · employment sub-plan (DGS code) | `N5508`, `S0156` |
|
|
197
|
+
| `ms:` | Morningstar id, for funds without ISIN (Korea, China…) | `ms:F00001H9R0` |
|
|
198
|
+
| `ft:` | FT symbol, for UK pension/life funds | `ft:0P00009VB8` |
|
|
199
|
+
| `in-amfi:` · `in-nps:` | India scheme code · NPS scheme | `in-amfi:118989` |
|
|
200
|
+
| `cn:` | Chinese fund code | `cn:110022` |
|
|
201
|
+
| `cl-afp:` | Chilean AFP + fund letter | `cl-afp:HABITAT-A` |
|
|
202
|
+
| `co-pension:` · `co-fic:` | Colombia entity–fund · collective fund | `co-pension:10-6000` |
|
|
203
|
+
| `br-cvm:` | Brazilian CNPJ (digits) | `br-cvm:00017024000153` |
|
|
204
|
+
| `ar-cafci:` | CAFCI class code | `ar-cafci:148` |
|
|
205
|
+
| `pl-analizy:` | analizy.pl code | `pl-analizy:PZU50` |
|
|
206
|
+
| `il-gemel:` · `il-pensia:` | Israeli fund id | `il-gemel:880` |
|
|
207
|
+
| `vn-fmarket:` · `id-pasardana:` | Vietnam fund code · Pasardana id | `vn-fmarket:VFF` |
|
|
208
|
+
|
|
209
|
+
Results always carry a canonical `id`: the ISIN when there is one, otherwise `scheme:code`.
|
|
210
|
+
|
|
211
|
+
## How it works
|
|
212
|
+
|
|
213
|
+
```mermaid
|
|
214
|
+
flowchart LR
|
|
215
|
+
Q["fundly.quote(id)"] --> I["Translate the id<br/>(ticker → ISIN…)"]
|
|
216
|
+
I --> C{"Fresh in the<br/>local cache?"}
|
|
217
|
+
C -- yes --> R(["Quote"])
|
|
218
|
+
C -- no --> T["Work out the type<br/>fund · ETF · plan"]
|
|
219
|
+
T --> S1["1st source that<br/>knows this id"]
|
|
220
|
+
S1 -- answers --> R
|
|
221
|
+
S1 -- "fails or 429" --> S2["next source"]
|
|
222
|
+
S2 -- answers --> R
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- **Each source only gets the ids it understands.** National sources answer for their own codes; Morningstar, FT and Yahoo for ISINs and tickers.
|
|
226
|
+
- **Priority depends on the product.** ETFs go to the official NAV first, Indian funds to AMFI first, and pension plans to the portals that carry them.
|
|
227
|
+
- **If a source fails, the next one answers.** Rate-limited sources are paused, not hammered.
|
|
228
|
+
- **Search only asks who can answer.** An ISIN or a local code is looked up directly. Text goes to the worldwide sources, plus national ones whose words appear in it ("afp" → Chile, "hdfc" → India…); Chinese, Hebrew and Vietnamese text go straight to the sources that publish names in that script. Other national catalogues are only tried when nothing matches every word. Results are ranked by how well the name matches, then by fund size.
|
|
229
|
+
- **Bad data is flagged, not hidden.** A price far from the last month's prices from other sources (a ×100 unit error, a wrong share class) makes fundly ask the next source; whichever two agree wins, and the quote says what was replaced. Old prices are marked `stale` (10 days; 45 for monthly series).
|
|
230
|
+
- **Local cache** in your platform's cache folder (`~/.cache/fundly` on Linux, `~/Library/Caches/fundly` on macOS, `%LOCALAPPDATA%\fundly\Cache` on Windows; or `$FUNDLY_CACHE_DIR`): quotes are reused for 6 h, history and searches for 24 h. The catalogue starts empty and only grows with what you look up. Bulk files (Brazil's monthly CVM archives, Argentine history files) are kept there too and revalidated with the server, so a later session downloads only what changed.
|
|
231
|
+
|
|
232
|
+
## API
|
|
233
|
+
|
|
234
|
+
| Call | Returns |
|
|
235
|
+
|---|---|
|
|
236
|
+
| `fundly.search(q, type=None, category=None, live=True, limit=20)` | `list[Instrument]` |
|
|
237
|
+
| `fundly.instrument(id)` | `Instrument` (name, type, currency, domicile, provider, category, TER, size, other ids) |
|
|
238
|
+
| `fundly.quote(id, source=None, currency=None)` | `Quote` (price, date, currency, `price_type`, source, `warnings`, `stale`, `suspicious`, `age_days`; plus the original price and FX rate if converted) |
|
|
239
|
+
| `fundly.history(id, start=None, end=None, source=None, currency=None)` | `History`, with `.points`, `.warnings` and `.to_pandas()` |
|
|
240
|
+
| `fundly.compare(id)` | `Comparison`, with every source's quote (the odd one out flagged `suspicious`) and `nav_spread_pct` |
|
|
241
|
+
|
|
242
|
+
Every result has `.to_dict()`, with dates as ISO strings, ready for `json.dumps`.
|
|
243
|
+
|
|
244
|
+
For custom settings, create your own client:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from fundly import Client
|
|
248
|
+
|
|
249
|
+
with Client(cache="~/data/fundly", min_interval=1.0, max_wait=60) as fl:
|
|
250
|
+
fl.history("IE00B4L5Y983", start="2015-01-01")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- `min_interval`: seconds between two requests to the same site (default 0.5).
|
|
254
|
+
- `max_wait`: total seconds one call may take across every source it tries (default 120; `None` for no limit, e.g. for many years of Brazilian history, whose first download takes a while; if a call times out, the download carries on in the background and the next call finds it on disk). A hanging site can't hold your program beyond it.
|
|
255
|
+
- `exclude`: sources to leave out.
|
|
256
|
+
|
|
257
|
+
Errors: `InvalidId`, `NotFound`, `UnknownSource`, `FXUnavailable` and `FundlyTimeout`, all subclasses of `FundlyError`. Source fallbacks are logged on the `fundly` logger.
|
|
258
|
+
|
|
259
|
+
### Currencies
|
|
260
|
+
|
|
261
|
+
- Prices are always in the share class's **own currency**, never silently converted. Prices quoted in pence, cents or agorot (GBp/GBX, ZAc, ILA) are turned into pounds, rand and shekels.
|
|
262
|
+
- With `currency="EUR"` (or any ISO code) the price is converted through the euro at the **rate in force on the price's date**; weekends and holidays use the last published rate. History converts each day with its own rate.
|
|
263
|
+
- Rates are **ECB euro reference rates** (29 currencies, source: ECB). Currencies the ECB doesn't publish (ARS, CLP, COP, VND, TWD…) come from Yahoo Finance. Converted quotes keep `original_price`, `original_currency`, `fx_rate`, `fx_date` and `fx_source`, so a converted price is never mistaken for a native one.
|
|
264
|
+
|
|
265
|
+
## Sources
|
|
266
|
+
|
|
267
|
+
| Source | Covers | Data |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| Morningstar | Funds & ETFs worldwide, search | NAV / exchange close |
|
|
270
|
+
| Finect | Spanish & EU funds, ETF NAVs, Spanish plans | NAV |
|
|
271
|
+
| Financial Times | Funds & ETFs, UK pension funds | NAV / exchange close |
|
|
272
|
+
| Quefondos | Spanish funds & plans | latest NAV |
|
|
273
|
+
| comparadorfondos | Spanish pension plans | NAV |
|
|
274
|
+
| Yahoo Finance | Funds & ETFs (fallback) | NAV / exchange close |
|
|
275
|
+
| AMFI (+ mfapi.in history) | India mutual funds | NAV, official |
|
|
276
|
+
| npsnav.in | India NPS pensions | NAV |
|
|
277
|
+
| Eastmoney | China funds | NAV |
|
|
278
|
+
| Superintendencia de Pensiones | Chile AFP funds A–E | NAV, official |
|
|
279
|
+
| datos.gov.co | Colombia pensions & collective funds | NAV, official |
|
|
280
|
+
| CVM open data | Brazil funds | NAV, official (ODbL) |
|
|
281
|
+
| CAFCI (+ argentinadatos.com history) | Argentina funds | NAV, official |
|
|
282
|
+
| analizy.pl | Poland funds, PPK, OFE | NAV |
|
|
283
|
+
| Gemel-net / Pensia-net | Israel provident & pension funds | index from monthly returns, official |
|
|
284
|
+
| fmarket | Vietnam funds | NAV |
|
|
285
|
+
| Pasardana | Indonesia funds | NAV, last 12 months of history |
|
|
286
|
+
| Batavia, Trimegah, Avrist, Syailendra | Their Indonesian funds | full NAV history, from each manager's site |
|
|
287
|
+
|
|
288
|
+
## Responsible use
|
|
289
|
+
|
|
290
|
+
fundly is an independent, source-available project, **not affiliated with any of the sites above**. All data belongs to its owners.
|
|
291
|
+
|
|
292
|
+
- **No data ships with fundly.** Every request goes from your machine to the public website, as if you visited it, and you are responsible for following each site's terms of use.
|
|
293
|
+
- **Personal, non-commercial use.** Most of these sites allow personal use and **forbid redistributing or reselling their data**. Don't use fundly to build a data feed, a paid API or any service that republishes it.
|
|
294
|
+
- **robots.txt.** Morningstar, Finect, Yahoo, npsnav.in, analizy.pl and Gemel-net ask automated clients to stay away in their robots.txt. Like yfinance, fundly uses them by default. If you prefer to respect robots.txt strictly, leave them out:
|
|
295
|
+
```python
|
|
296
|
+
Client(exclude=["morningstar", "finect", "yahoo", "npsnav", "analizy", "gemelnet"])
|
|
297
|
+
```
|
|
298
|
+
- **Gentle by default.** At most one request every 0.5 s per site, cached results, and a pause whenever a site answers *429 Too Many Requests*.
|
|
299
|
+
- **Honest.** fundly identifies itself in its `User-Agent` and never bypasses anti-bot protection. Sites that block automated clients are not supported.
|
|
300
|
+
- **No advice.** Prices can be late, missing or wrong. Nothing here is investment advice, and the software comes with no warranty.
|
|
301
|
+
|
|
302
|
+
If you run one of these sites and want it out of fundly, open an issue.
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
fundly is released under the [PolyForm Noncommercial License 1.0.0](LICENSE).
|
|
307
|
+
|
|
308
|
+
- ✅ **Free** for personal use, research, education, hobby projects, and non-profit, educational and government organisations.
|
|
309
|
+
- ❌ **No commercial use.** You can't sell it, include it in a paid product or service, or use it inside a business.
|
|
310
|
+
|
|
311
|
+
Because it forbids commercial use, fundly is *source-available*, not "open source" in the OSI sense.
|
|
312
|
+
|
|
313
|
+
## Contributing
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
uv sync
|
|
317
|
+
uv run pytest # 527 offline tests: parsers, client, currencies, broken sites, properties, concurrency
|
|
318
|
+
uv run pytest -m live # 75 tests against the real sites
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Adding a country is one class with `get_quote`, `get_history` and, optionally, `search`, plus an id scheme registered in `fundly.ids`. See `fundly/sources/`.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
<sub>🌍 <b>En español:</b> fundly consulta el valor liquidativo de fondos de inversión, ETFs y planes de pensiones de todo el mundo por ISIN, ticker o código local, cruzando varias fuentes. Licencia PolyForm Noncommercial: uso personal y no comercial; no redistribuye datos.</sub>
|