nautilus-finzdata 0.1.1__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.
- nautilus_finzdata-0.1.1/LICENSE +21 -0
- nautilus_finzdata-0.1.1/PKG-INFO +97 -0
- nautilus_finzdata-0.1.1/README.md +75 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata/__init__.py +295 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata.egg-info/PKG-INFO +97 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata.egg-info/SOURCES.txt +9 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata.egg-info/dependency_links.txt +1 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata.egg-info/requires.txt +3 -0
- nautilus_finzdata-0.1.1/nautilus_finzdata.egg-info/top_level.txt +1 -0
- nautilus_finzdata-0.1.1/pyproject.toml +28 -0
- nautilus_finzdata-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FinzData
|
|
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,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nautilus-finzdata
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: FinzData loader for NautilusTrader: point-in-time, survivorship-free stock, ETF and futures bars into a ParquetDataCatalog.
|
|
5
|
+
Author-email: FinzData <info@finzdata.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://finzdata.com/nautilus
|
|
8
|
+
Project-URL: Documentation, https://finzdata.com/docs
|
|
9
|
+
Project-URL: Source, https://github.com/FinzData/nautilus-finzdata
|
|
10
|
+
Keywords: nautilus_trader,nautilustrader,finzdata,market-data,backtesting,futures,point-in-time,survivorship-bias
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: finzdata>=0.1.14
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: nautilus_trader>=1.220
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
<p align="center"><a href="https://finzdata.com"><img src="https://finzdata.com/assets/brand/finzdata-logo-full.png" alt="FinzData" width="360"></a></p>
|
|
24
|
+
|
|
25
|
+
# nautilus-finzdata
|
|
26
|
+
|
|
27
|
+
FinzData's point-in-time, survivorship-free market data for NautilusTrader backtests.
|
|
28
|
+
|
|
29
|
+
## Why FinzData
|
|
30
|
+
|
|
31
|
+
A backtest only knows the stocks in its data. FinzData includes delisted stocks, point in time, so results are not inflated by survivorship bias. Every futures contract since 2007 plus continuous series.
|
|
32
|
+
|
|
33
|
+
## Coverage
|
|
34
|
+
|
|
35
|
+
16,000+ US stocks that have traded on NYSE and Nasdaq since January 2000, delisted included, point in time
|
|
36
|
+
231 ETFs
|
|
37
|
+
139 futures markets with every contract since 2007
|
|
38
|
+
1, 5, 15, 30-minute, hourly and daily bars
|
|
39
|
+
|
|
40
|
+
Also via the FinzData API:
|
|
41
|
+
GEX for single stocks, ETFs and the major indexes
|
|
42
|
+
COT since 1986
|
|
43
|
+
13F
|
|
44
|
+
SEC fundamentals as filed
|
|
45
|
+
18,000+ macro series with every vintage kept
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
`pip install nautilus-finzdata`
|
|
50
|
+
|
|
51
|
+
Set `FINZDATA_API_KEY`. Free key, email only, 12 months of daily data: https://finzdata.com/account?ref=nautilus
|
|
52
|
+
Intraday bars and full history from $19 per ticker or $29 per futures market, yours to keep: https://finzdata.com/shop?ref=nautilus
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from nautilus_finzdata import FinzDataLoader
|
|
58
|
+
loader = FinzDataLoader() # venue "FINZDATA"
|
|
59
|
+
loader.to_catalog("catalog", symbols=["AAPL", "SPY"], start="2025-10-27", end="2026-10-23", futures=["ES"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then load it:
|
|
63
|
+
```python
|
|
64
|
+
from nautilus_trader.persistence.catalog import ParquetDataCatalog
|
|
65
|
+
catalog = ParquetDataCatalog("catalog")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
BacktestNode snippet using BacktestDataConfig(catalog_path="catalog", data_cls=Bar, bar_types=["AAPL.FINZDATA-1-DAY-LAST-EXTERNAL"]) inside BacktestRunConfig.
|
|
69
|
+
|
|
70
|
+
## Bars and instruments
|
|
71
|
+
|
|
72
|
+
Instrument ids SYMBOL.FINZDATA
|
|
73
|
+
Continuous futures ROOT.c.0.FINZDATA, e.g. ES.c.0.FINZDATA
|
|
74
|
+
Single contracts e.g. ESZ26.FINZDATA
|
|
75
|
+
|
|
76
|
+
Bar types {instrument_id}-1-DAY-LAST-EXTERNAL / -5-MINUTE- / -1-HOUR-
|
|
77
|
+
Intervals 1min, 5min, 15min, 30min, 1hour, 1day
|
|
78
|
+
Bars are stamped at their close (daily: 16:00 New York) so strategies never see a bar early.
|
|
79
|
+
|
|
80
|
+
loader.bars(...)
|
|
81
|
+
loader.future_bars(root, ..., contract="ESZ26")
|
|
82
|
+
loader.equity(...)
|
|
83
|
+
loader.future(...)
|
|
84
|
+
|
|
85
|
+
## Futures specs
|
|
86
|
+
|
|
87
|
+
Built-in multipliers and tick sizes for ES, MES, NQ, MNQ, YM, RTY, M2K, CL, MCL, NG, RB, HO, PL, GC, MGC, SI, HG, ZN, ZF, ZT, ZC, ZS, ZW, VX, FDAX, FESX, E6, A6, B6, J6
|
|
88
|
+
Others via futures_specs={...}
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
FinzDataPlanError with the link when a request needs a paid plan or an API key.
|
|
93
|
+
|
|
94
|
+
MIT
|
|
95
|
+
FinzData is not affiliated with Nautech Systems or the NautilusTrader project.
|
|
96
|
+
Support info@finzdata.com
|
|
97
|
+
FinzData: built for AI, quants, data scientists and financial analysts who backtest. Clean data. Real insights. Real alpha.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<p align="center"><a href="https://finzdata.com"><img src="https://finzdata.com/assets/brand/finzdata-logo-full.png" alt="FinzData" width="360"></a></p>
|
|
2
|
+
|
|
3
|
+
# nautilus-finzdata
|
|
4
|
+
|
|
5
|
+
FinzData's point-in-time, survivorship-free market data for NautilusTrader backtests.
|
|
6
|
+
|
|
7
|
+
## Why FinzData
|
|
8
|
+
|
|
9
|
+
A backtest only knows the stocks in its data. FinzData includes delisted stocks, point in time, so results are not inflated by survivorship bias. Every futures contract since 2007 plus continuous series.
|
|
10
|
+
|
|
11
|
+
## Coverage
|
|
12
|
+
|
|
13
|
+
16,000+ US stocks that have traded on NYSE and Nasdaq since January 2000, delisted included, point in time
|
|
14
|
+
231 ETFs
|
|
15
|
+
139 futures markets with every contract since 2007
|
|
16
|
+
1, 5, 15, 30-minute, hourly and daily bars
|
|
17
|
+
|
|
18
|
+
Also via the FinzData API:
|
|
19
|
+
GEX for single stocks, ETFs and the major indexes
|
|
20
|
+
COT since 1986
|
|
21
|
+
13F
|
|
22
|
+
SEC fundamentals as filed
|
|
23
|
+
18,000+ macro series with every vintage kept
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
`pip install nautilus-finzdata`
|
|
28
|
+
|
|
29
|
+
Set `FINZDATA_API_KEY`. Free key, email only, 12 months of daily data: https://finzdata.com/account?ref=nautilus
|
|
30
|
+
Intraday bars and full history from $19 per ticker or $29 per futures market, yours to keep: https://finzdata.com/shop?ref=nautilus
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from nautilus_finzdata import FinzDataLoader
|
|
36
|
+
loader = FinzDataLoader() # venue "FINZDATA"
|
|
37
|
+
loader.to_catalog("catalog", symbols=["AAPL", "SPY"], start="2025-10-27", end="2026-10-23", futures=["ES"])
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then load it:
|
|
41
|
+
```python
|
|
42
|
+
from nautilus_trader.persistence.catalog import ParquetDataCatalog
|
|
43
|
+
catalog = ParquetDataCatalog("catalog")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
BacktestNode snippet using BacktestDataConfig(catalog_path="catalog", data_cls=Bar, bar_types=["AAPL.FINZDATA-1-DAY-LAST-EXTERNAL"]) inside BacktestRunConfig.
|
|
47
|
+
|
|
48
|
+
## Bars and instruments
|
|
49
|
+
|
|
50
|
+
Instrument ids SYMBOL.FINZDATA
|
|
51
|
+
Continuous futures ROOT.c.0.FINZDATA, e.g. ES.c.0.FINZDATA
|
|
52
|
+
Single contracts e.g. ESZ26.FINZDATA
|
|
53
|
+
|
|
54
|
+
Bar types {instrument_id}-1-DAY-LAST-EXTERNAL / -5-MINUTE- / -1-HOUR-
|
|
55
|
+
Intervals 1min, 5min, 15min, 30min, 1hour, 1day
|
|
56
|
+
Bars are stamped at their close (daily: 16:00 New York) so strategies never see a bar early.
|
|
57
|
+
|
|
58
|
+
loader.bars(...)
|
|
59
|
+
loader.future_bars(root, ..., contract="ESZ26")
|
|
60
|
+
loader.equity(...)
|
|
61
|
+
loader.future(...)
|
|
62
|
+
|
|
63
|
+
## Futures specs
|
|
64
|
+
|
|
65
|
+
Built-in multipliers and tick sizes for ES, MES, NQ, MNQ, YM, RTY, M2K, CL, MCL, NG, RB, HO, PL, GC, MGC, SI, HG, ZN, ZF, ZT, ZC, ZS, ZW, VX, FDAX, FESX, E6, A6, B6, J6
|
|
66
|
+
Others via futures_specs={...}
|
|
67
|
+
|
|
68
|
+
## Errors
|
|
69
|
+
|
|
70
|
+
FinzDataPlanError with the link when a request needs a paid plan or an API key.
|
|
71
|
+
|
|
72
|
+
MIT
|
|
73
|
+
FinzData is not affiliated with Nautech Systems or the NautilusTrader project.
|
|
74
|
+
Support info@finzdata.com
|
|
75
|
+
FinzData: built for AI, quants, data scientists and financial analysts who backtest. Clean data. Real insights. Real alpha.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FinzData loader for NautilusTrader backtests.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
from nautilus_finzdata import FinzDataLoader
|
|
6
|
+
loader = FinzDataLoader()
|
|
7
|
+
stats = loader.to_catalog("catalog", symbols=["AAPL","SPY"], start="2025-10-27",
|
|
8
|
+
end="2026-10-23", futures=["ES"])
|
|
9
|
+
from nautilus_trader.backtest.node import BacktestNode
|
|
10
|
+
from nautilus_trader.persistence.catalog import ParquetDataCatalog
|
|
11
|
+
catalog = ParquetDataCatalog("catalog")
|
|
12
|
+
node = BacktestNode(config, catalog=catalog)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from zoneinfo import ZoneInfo
|
|
17
|
+
|
|
18
|
+
from nautilus_trader.model.identifiers import InstrumentId, Symbol, Venue
|
|
19
|
+
from nautilus_trader.model.instruments import Equity, FuturesContract
|
|
20
|
+
from nautilus_trader.model.objects import Price, Quantity
|
|
21
|
+
from nautilus_trader.model.currencies import USD, EUR
|
|
22
|
+
from nautilus_trader.model.currencies import Currency as CurrencyClass
|
|
23
|
+
from nautilus_trader.model.enums import AssetClass
|
|
24
|
+
from nautilus_trader.model.data import BarType
|
|
25
|
+
from nautilus_trader.persistence.wranglers import BarDataWrangler
|
|
26
|
+
from nautilus_trader.persistence.catalog import ParquetDataCatalog
|
|
27
|
+
|
|
28
|
+
import finzdata
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.1"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FinzDataPlanError(RuntimeError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
FUTURES_SPECS = {
|
|
39
|
+
"ES": {"multiplier": 50, "tick_size": 0.25, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
40
|
+
"MES": {"multiplier": 5, "tick_size": 0.25, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
41
|
+
"NQ": {"multiplier": 20, "tick_size": 0.25, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
42
|
+
"MNQ": {"multiplier": 2, "tick_size": 0.25, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
43
|
+
"YM": {"multiplier": 5, "tick_size": 1, "currency": "USD", "asset_class": "INDEX", "exchange": "XCBT"},
|
|
44
|
+
"RTY": {"multiplier": 50, "tick_size": 0.1, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
45
|
+
"M2K": {"multiplier": 5, "tick_size": 0.1, "currency": "USD", "asset_class": "INDEX", "exchange": "XCME"},
|
|
46
|
+
"CL": {"multiplier": 1000, "tick_size": 0.01, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
47
|
+
"MCL": {"multiplier": 100, "tick_size": 0.01, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
48
|
+
"NG": {"multiplier": 10000, "tick_size": 0.001, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
49
|
+
"RB": {"multiplier": 42000, "tick_size": 0.0001, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
50
|
+
"HO": {"multiplier": 42000, "tick_size": 0.0001, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
51
|
+
"PL": {"multiplier": 50, "tick_size": 0.1, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XNYM"},
|
|
52
|
+
"GC": {"multiplier": 100, "tick_size": 0.1, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCEC"},
|
|
53
|
+
"MGC": {"multiplier": 10, "tick_size": 0.1, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCEC"},
|
|
54
|
+
"SI": {"multiplier": 5000, "tick_size": 0.005, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCEC"},
|
|
55
|
+
"HG": {"multiplier": 25000, "tick_size": 0.0005, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCEC"},
|
|
56
|
+
"ZN": {"multiplier": 1000, "tick_size": 0.015625, "currency": "USD", "asset_class": "DEBT", "exchange": "XCBT"},
|
|
57
|
+
"ZF": {"multiplier": 1000, "tick_size": 0.0078125, "currency": "USD", "asset_class": "DEBT", "exchange": "XCBT"},
|
|
58
|
+
"ZT": {"multiplier": 2000, "tick_size": 0.00390625, "currency": "USD", "asset_class": "DEBT", "exchange": "XCBT"},
|
|
59
|
+
"ZC": {"multiplier": 50, "tick_size": 0.25, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCBT"},
|
|
60
|
+
"ZS": {"multiplier": 50, "tick_size": 0.25, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCBT"},
|
|
61
|
+
"ZW": {"multiplier": 50, "tick_size": 0.25, "currency": "USD", "asset_class": "COMMODITY", "exchange": "XCBT"},
|
|
62
|
+
"VX": {"multiplier": 1000, "tick_size": 0.05, "currency": "USD", "asset_class": "INDEX", "exchange": "XCBF"},
|
|
63
|
+
"FDAX": {"multiplier": 25, "tick_size": 0.5, "currency": "EUR", "asset_class": "INDEX", "exchange": "XEUR"},
|
|
64
|
+
"FESX": {"multiplier": 10, "tick_size": 1, "currency": "EUR", "asset_class": "INDEX", "exchange": "XEUR"},
|
|
65
|
+
"E6": {"multiplier": 125000, "tick_size": 0.00005, "currency": "USD", "asset_class": "FX", "exchange": "XCME"},
|
|
66
|
+
"A6": {"multiplier": 100000, "tick_size": 0.00005, "currency": "USD", "asset_class": "FX", "exchange": "XCME"},
|
|
67
|
+
"B6": {"multiplier": 62500, "tick_size": 0.0001, "currency": "USD", "asset_class": "FX", "exchange": "XCME"},
|
|
68
|
+
"J6": {"multiplier": 12500000, "tick_size": 0.0000005, "currency": "USD", "asset_class": "FX", "exchange": "XCME"},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_INTERVALS = {
|
|
72
|
+
"1min": (1, "MINUTE"),
|
|
73
|
+
"5min": (5, "MINUTE"),
|
|
74
|
+
"15min": (15, "MINUTE"),
|
|
75
|
+
"30min": (30, "MINUTE"),
|
|
76
|
+
"1hour": (1, "HOUR"),
|
|
77
|
+
"1day": (1, "DAY"),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _precision(tick):
|
|
82
|
+
s = f"{tick:f}".rstrip("0")
|
|
83
|
+
if "." in s:
|
|
84
|
+
return len(s.split(".")[1])
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _make_bars(frame, bar_type, instrument):
|
|
90
|
+
"""Build Nautilus Bar objects directly (BarDataWrangler fails on pandas' read-only arrays).
|
|
91
|
+
frame: UTC DatetimeIndex (bar close) with float columns open, high, low, close, volume."""
|
|
92
|
+
from nautilus_trader.model.data import Bar
|
|
93
|
+
prec = instrument.price_precision
|
|
94
|
+
out = []
|
|
95
|
+
for ts, row in frame.iterrows():
|
|
96
|
+
ns = int(pd.Timestamp(ts).value)
|
|
97
|
+
vol = row["volume"] if pd.notna(row["volume"]) else 0.0
|
|
98
|
+
o, c = float(row["open"]), float(row["close"])
|
|
99
|
+
h, l = max(float(row["high"]), o, c), min(float(row["low"]), o, c) # keep OHLC consistent; Bar rejects high < close
|
|
100
|
+
out.append(Bar(bar_type, Price(o, prec), Price(h, prec), Price(l, prec),
|
|
101
|
+
Price(c, prec), Quantity(max(float(vol), 0.0), 0), ns, ns))
|
|
102
|
+
return out
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FinzDataLoader:
|
|
106
|
+
def __init__(self, client=None, venue: str = "FINZDATA", futures_specs: dict | None = None, api_key: str | None = None):
|
|
107
|
+
self.venue = venue
|
|
108
|
+
self._client = client
|
|
109
|
+
self._api_key = api_key
|
|
110
|
+
self.futures_specs = {**FUTURES_SPECS, **(futures_specs or {})}
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def client(self):
|
|
114
|
+
if self._client is None:
|
|
115
|
+
self._client = finzdata.Client(api_key=self._api_key)
|
|
116
|
+
return self._client
|
|
117
|
+
|
|
118
|
+
def _call(self, fn, **kw):
|
|
119
|
+
try:
|
|
120
|
+
return fn(**kw)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
status = getattr(e, "status", None)
|
|
123
|
+
if status in (402, 403):
|
|
124
|
+
detail = getattr(e, "detail", str(e))
|
|
125
|
+
raise FinzDataPlanError(f"{detail} Intraday bars and full history: https://finzdata.com/shop?ref=nautilus") from e
|
|
126
|
+
if status == 401:
|
|
127
|
+
raise FinzDataPlanError("FinzData needs an API key: set FINZDATA_API_KEY. Free key (email only, 12 months of daily data): https://finzdata.com/account?ref=nautilus") from e
|
|
128
|
+
raise
|
|
129
|
+
|
|
130
|
+
def equity(self, symbol):
|
|
131
|
+
instrument_id = InstrumentId(Symbol(symbol), Venue(self.venue))
|
|
132
|
+
return Equity(
|
|
133
|
+
instrument_id=instrument_id,
|
|
134
|
+
raw_symbol=Symbol(symbol),
|
|
135
|
+
currency=USD,
|
|
136
|
+
price_precision=2,
|
|
137
|
+
price_increment=Price.from_str("0.01"),
|
|
138
|
+
lot_size=Quantity.from_int(1),
|
|
139
|
+
ts_event=0,
|
|
140
|
+
ts_init=0,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def future(self, root, contract=None):
|
|
144
|
+
root = root.upper()
|
|
145
|
+
if root not in self.futures_specs:
|
|
146
|
+
raise ValueError(f"Unknown futures root {root}: pass futures_specs={{'{root}': {{...}}}} with multiplier, tick_size, currency, asset_class")
|
|
147
|
+
spec = self.futures_specs[root]
|
|
148
|
+
currency = CurrencyClass.from_str(spec["currency"])
|
|
149
|
+
asset_class = getattr(AssetClass, spec["asset_class"])
|
|
150
|
+
tick = spec["tick_size"]
|
|
151
|
+
precision = _precision(tick)
|
|
152
|
+
price_increment = Price(tick, precision)
|
|
153
|
+
multiplier = Quantity(spec["multiplier"], 0)
|
|
154
|
+
exchange = spec.get("exchange") # optional in user-supplied specs
|
|
155
|
+
if contract is None:
|
|
156
|
+
instrument_id = InstrumentId(Symbol(f"{root}.c.0"), Venue(self.venue))
|
|
157
|
+
return FuturesContract(
|
|
158
|
+
instrument_id=instrument_id,
|
|
159
|
+
raw_symbol=Symbol(f"{root}.c.0"),
|
|
160
|
+
currency=currency,
|
|
161
|
+
price_precision=precision,
|
|
162
|
+
price_increment=price_increment,
|
|
163
|
+
multiplier=multiplier,
|
|
164
|
+
lot_size=Quantity.from_int(1),
|
|
165
|
+
underlying=root,
|
|
166
|
+
asset_class=asset_class,
|
|
167
|
+
exchange=exchange,
|
|
168
|
+
activation_ns=int(pd.Timestamp("2007-01-01", tz="UTC").value),
|
|
169
|
+
expiration_ns=int(pd.Timestamp("2099-12-31", tz="UTC").value),
|
|
170
|
+
ts_event=0,
|
|
171
|
+
ts_init=0,
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
df = self._call(self.client.futures_contracts, root=root)
|
|
175
|
+
row = df[df["contract"] == contract]
|
|
176
|
+
if row.empty:
|
|
177
|
+
raise ValueError(f"Contract {contract} not found for root {root}")
|
|
178
|
+
first_date = pd.to_datetime(row.iloc[0]["first_date"]).tz_localize("UTC")
|
|
179
|
+
last_date = pd.to_datetime(row.iloc[0]["last_date"]).tz_localize("UTC")
|
|
180
|
+
instrument_id = InstrumentId(Symbol(contract), Venue(self.venue))
|
|
181
|
+
return FuturesContract(
|
|
182
|
+
instrument_id=instrument_id,
|
|
183
|
+
raw_symbol=Symbol(contract),
|
|
184
|
+
currency=currency,
|
|
185
|
+
price_precision=precision,
|
|
186
|
+
price_increment=price_increment,
|
|
187
|
+
multiplier=multiplier,
|
|
188
|
+
lot_size=Quantity.from_int(1),
|
|
189
|
+
underlying=root,
|
|
190
|
+
asset_class=asset_class,
|
|
191
|
+
exchange=exchange,
|
|
192
|
+
activation_ns=int(first_date.value),
|
|
193
|
+
expiration_ns=int(last_date.value),
|
|
194
|
+
ts_event=0,
|
|
195
|
+
ts_init=0,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def bars(self, symbol, start, end, interval="1day"):
|
|
199
|
+
if interval in ("1d", "daily"):
|
|
200
|
+
interval = "1day"
|
|
201
|
+
if interval not in _INTERVALS:
|
|
202
|
+
raise ValueError(f"Unsupported interval {interval}. Supported: {list(_INTERVALS.keys())}")
|
|
203
|
+
step, agg = _INTERVALS[interval]
|
|
204
|
+
instrument = self.equity(symbol)
|
|
205
|
+
if interval == "1day":
|
|
206
|
+
df = self._call(self.client.prices, symbol=symbol, start=start, end=end)
|
|
207
|
+
if df.empty:
|
|
208
|
+
if pd.to_datetime(start) < pd.Timestamp.today() - pd.Timedelta(days=365):
|
|
209
|
+
raise FinzDataPlanError("FinzData returned no rows. Free keys cover the last 12 months; full history (every US stock since 2000, delisted included): https://finzdata.com/shop?ref=nautilus")
|
|
210
|
+
return []
|
|
211
|
+
dates = (pd.to_datetime(df["date"]).dt.normalize() + pd.Timedelta(hours=16)).dt.tz_localize("America/New_York") # daily bar = 16:00 New York close
|
|
212
|
+
df["timestamp"] = dates.dt.tz_convert("UTC")
|
|
213
|
+
else:
|
|
214
|
+
df = self._call(self.client.minutes, symbol=symbol, start=start, end=end, interval=interval)
|
|
215
|
+
if df.empty:
|
|
216
|
+
if pd.to_datetime(start) < pd.Timestamp.today() - pd.Timedelta(days=365):
|
|
217
|
+
raise FinzDataPlanError("FinzData returned no rows. Free keys cover the last 12 months; full history (every US stock since 2000, delisted included): https://finzdata.com/shop?ref=nautilus")
|
|
218
|
+
return []
|
|
219
|
+
ts = pd.to_datetime(df["ts"]).dt.tz_localize("America/New_York")
|
|
220
|
+
delta = pd.Timedelta(**{{"MINUTE": "minutes", "HOUR": "hours"}[agg]: step}) # bar close = open + interval
|
|
221
|
+
df["timestamp"] = (ts + delta).dt.tz_convert("UTC")
|
|
222
|
+
frame = pd.DataFrame({
|
|
223
|
+
"timestamp": df["timestamp"],
|
|
224
|
+
"open": df["open"].astype(float),
|
|
225
|
+
"high": df["high"].astype(float),
|
|
226
|
+
"low": df["low"].astype(float),
|
|
227
|
+
"close": df["close"].astype(float),
|
|
228
|
+
"volume": df["volume"].astype(float),
|
|
229
|
+
}).sort_values("timestamp").set_index("timestamp") # the wrangler needs a UTC DatetimeIndex
|
|
230
|
+
bar_type = BarType.from_str(f"{instrument.id}-{step}-{agg}-LAST-EXTERNAL")
|
|
231
|
+
return _make_bars(frame, bar_type, instrument)
|
|
232
|
+
|
|
233
|
+
def future_bars(self, root, start, end, interval="1day", contract=None):
|
|
234
|
+
if interval in ("1d", "daily"):
|
|
235
|
+
interval = "1day"
|
|
236
|
+
if interval not in _INTERVALS:
|
|
237
|
+
raise ValueError(f"Unsupported interval {interval}. Supported: {list(_INTERVALS.keys())}")
|
|
238
|
+
step, agg = _INTERVALS[interval]
|
|
239
|
+
instrument = self.future(root, contract)
|
|
240
|
+
finz_interval = "1d" if interval == "1day" else interval
|
|
241
|
+
df = self._call(self.client.futures, root=root, contract=contract, continuous=contract is None, interval=finz_interval, adjust="none", start=start, end=end)
|
|
242
|
+
if contract and "contract" in df.columns:
|
|
243
|
+
df = df[df["contract"] == contract]
|
|
244
|
+
if df.empty:
|
|
245
|
+
if pd.to_datetime(start) < pd.Timestamp.today() - pd.Timedelta(days=365):
|
|
246
|
+
raise FinzDataPlanError("FinzData returned no rows. Free keys cover the last 12 months; full history (every US stock since 2000, delisted included): https://finzdata.com/shop?ref=nautilus")
|
|
247
|
+
return []
|
|
248
|
+
if interval == "1day":
|
|
249
|
+
dates = (pd.to_datetime(df["date"]).dt.normalize() + pd.Timedelta(hours=16)).dt.tz_localize("America/New_York") # daily bar = 16:00 New York close
|
|
250
|
+
df["timestamp"] = dates.dt.tz_convert("UTC")
|
|
251
|
+
else:
|
|
252
|
+
ts_col = "ts" if "ts" in df.columns else "date"
|
|
253
|
+
ts = pd.to_datetime(df[ts_col]).dt.tz_localize("America/New_York")
|
|
254
|
+
delta = pd.Timedelta(**{{"MINUTE": "minutes", "HOUR": "hours"}[agg]: step}) # bar close = open + interval
|
|
255
|
+
df["timestamp"] = (ts + delta).dt.tz_convert("UTC")
|
|
256
|
+
frame = pd.DataFrame({
|
|
257
|
+
"timestamp": df["timestamp"],
|
|
258
|
+
"open": df["open"].astype(float),
|
|
259
|
+
"high": df["high"].astype(float),
|
|
260
|
+
"low": df["low"].astype(float),
|
|
261
|
+
"close": df["close"].astype(float),
|
|
262
|
+
"volume": df["volume"].astype(float),
|
|
263
|
+
}).sort_values("timestamp").set_index("timestamp") # the wrangler needs a UTC DatetimeIndex
|
|
264
|
+
bar_type = BarType.from_str(f"{instrument.id}-{step}-{agg}-LAST-EXTERNAL")
|
|
265
|
+
return _make_bars(frame, bar_type, instrument)
|
|
266
|
+
|
|
267
|
+
def to_catalog(self, path, symbols=(), start=None, end=None, interval="1day", futures=()):
|
|
268
|
+
catalog = ParquetDataCatalog(path)
|
|
269
|
+
instruments = []
|
|
270
|
+
all_bars = []
|
|
271
|
+
n_instruments = 0
|
|
272
|
+
n_bars = 0
|
|
273
|
+
for sym in symbols:
|
|
274
|
+
inst = self.equity(sym)
|
|
275
|
+
instruments.append(inst)
|
|
276
|
+
bars = self.bars(sym, start, end, interval)
|
|
277
|
+
if bars:
|
|
278
|
+
all_bars.extend(bars)
|
|
279
|
+
n_bars += len(bars)
|
|
280
|
+
n_instruments += 1
|
|
281
|
+
for root in futures:
|
|
282
|
+
inst = self.future(root, contract=None)
|
|
283
|
+
instruments.append(inst)
|
|
284
|
+
bars = self.future_bars(root, start, end, interval, contract=None)
|
|
285
|
+
if bars:
|
|
286
|
+
all_bars.extend(bars)
|
|
287
|
+
n_bars += len(bars)
|
|
288
|
+
n_instruments += 1
|
|
289
|
+
if instruments:
|
|
290
|
+
catalog.write_data(instruments)
|
|
291
|
+
for inst in instruments:
|
|
292
|
+
bars_for_inst = [b for b in all_bars if b.bar_type.instrument_id == inst.id]
|
|
293
|
+
if bars_for_inst:
|
|
294
|
+
catalog.write_data(bars_for_inst)
|
|
295
|
+
return {"instruments": n_instruments, "bars": n_bars}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nautilus-finzdata
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: FinzData loader for NautilusTrader: point-in-time, survivorship-free stock, ETF and futures bars into a ParquetDataCatalog.
|
|
5
|
+
Author-email: FinzData <info@finzdata.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://finzdata.com/nautilus
|
|
8
|
+
Project-URL: Documentation, https://finzdata.com/docs
|
|
9
|
+
Project-URL: Source, https://github.com/FinzData/nautilus-finzdata
|
|
10
|
+
Keywords: nautilus_trader,nautilustrader,finzdata,market-data,backtesting,futures,point-in-time,survivorship-bias
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: finzdata>=0.1.14
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: nautilus_trader>=1.220
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
<p align="center"><a href="https://finzdata.com"><img src="https://finzdata.com/assets/brand/finzdata-logo-full.png" alt="FinzData" width="360"></a></p>
|
|
24
|
+
|
|
25
|
+
# nautilus-finzdata
|
|
26
|
+
|
|
27
|
+
FinzData's point-in-time, survivorship-free market data for NautilusTrader backtests.
|
|
28
|
+
|
|
29
|
+
## Why FinzData
|
|
30
|
+
|
|
31
|
+
A backtest only knows the stocks in its data. FinzData includes delisted stocks, point in time, so results are not inflated by survivorship bias. Every futures contract since 2007 plus continuous series.
|
|
32
|
+
|
|
33
|
+
## Coverage
|
|
34
|
+
|
|
35
|
+
16,000+ US stocks that have traded on NYSE and Nasdaq since January 2000, delisted included, point in time
|
|
36
|
+
231 ETFs
|
|
37
|
+
139 futures markets with every contract since 2007
|
|
38
|
+
1, 5, 15, 30-minute, hourly and daily bars
|
|
39
|
+
|
|
40
|
+
Also via the FinzData API:
|
|
41
|
+
GEX for single stocks, ETFs and the major indexes
|
|
42
|
+
COT since 1986
|
|
43
|
+
13F
|
|
44
|
+
SEC fundamentals as filed
|
|
45
|
+
18,000+ macro series with every vintage kept
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
`pip install nautilus-finzdata`
|
|
50
|
+
|
|
51
|
+
Set `FINZDATA_API_KEY`. Free key, email only, 12 months of daily data: https://finzdata.com/account?ref=nautilus
|
|
52
|
+
Intraday bars and full history from $19 per ticker or $29 per futures market, yours to keep: https://finzdata.com/shop?ref=nautilus
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from nautilus_finzdata import FinzDataLoader
|
|
58
|
+
loader = FinzDataLoader() # venue "FINZDATA"
|
|
59
|
+
loader.to_catalog("catalog", symbols=["AAPL", "SPY"], start="2025-10-27", end="2026-10-23", futures=["ES"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then load it:
|
|
63
|
+
```python
|
|
64
|
+
from nautilus_trader.persistence.catalog import ParquetDataCatalog
|
|
65
|
+
catalog = ParquetDataCatalog("catalog")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
BacktestNode snippet using BacktestDataConfig(catalog_path="catalog", data_cls=Bar, bar_types=["AAPL.FINZDATA-1-DAY-LAST-EXTERNAL"]) inside BacktestRunConfig.
|
|
69
|
+
|
|
70
|
+
## Bars and instruments
|
|
71
|
+
|
|
72
|
+
Instrument ids SYMBOL.FINZDATA
|
|
73
|
+
Continuous futures ROOT.c.0.FINZDATA, e.g. ES.c.0.FINZDATA
|
|
74
|
+
Single contracts e.g. ESZ26.FINZDATA
|
|
75
|
+
|
|
76
|
+
Bar types {instrument_id}-1-DAY-LAST-EXTERNAL / -5-MINUTE- / -1-HOUR-
|
|
77
|
+
Intervals 1min, 5min, 15min, 30min, 1hour, 1day
|
|
78
|
+
Bars are stamped at their close (daily: 16:00 New York) so strategies never see a bar early.
|
|
79
|
+
|
|
80
|
+
loader.bars(...)
|
|
81
|
+
loader.future_bars(root, ..., contract="ESZ26")
|
|
82
|
+
loader.equity(...)
|
|
83
|
+
loader.future(...)
|
|
84
|
+
|
|
85
|
+
## Futures specs
|
|
86
|
+
|
|
87
|
+
Built-in multipliers and tick sizes for ES, MES, NQ, MNQ, YM, RTY, M2K, CL, MCL, NG, RB, HO, PL, GC, MGC, SI, HG, ZN, ZF, ZT, ZC, ZS, ZW, VX, FDAX, FESX, E6, A6, B6, J6
|
|
88
|
+
Others via futures_specs={...}
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
FinzDataPlanError with the link when a request needs a paid plan or an API key.
|
|
93
|
+
|
|
94
|
+
MIT
|
|
95
|
+
FinzData is not affiliated with Nautech Systems or the NautilusTrader project.
|
|
96
|
+
Support info@finzdata.com
|
|
97
|
+
FinzData: built for AI, quants, data scientists and financial analysts who backtest. Clean data. Real insights. Real alpha.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
nautilus_finzdata/__init__.py
|
|
5
|
+
nautilus_finzdata.egg-info/PKG-INFO
|
|
6
|
+
nautilus_finzdata.egg-info/SOURCES.txt
|
|
7
|
+
nautilus_finzdata.egg-info/dependency_links.txt
|
|
8
|
+
nautilus_finzdata.egg-info/requires.txt
|
|
9
|
+
nautilus_finzdata.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nautilus_finzdata
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nautilus-finzdata"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "FinzData loader for NautilusTrader: point-in-time, survivorship-free stock, ETF and futures bars into a ParquetDataCatalog."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "FinzData", email = "info@finzdata.com" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["finzdata>=0.1.14", "pandas>=2.0", "nautilus_trader>=1.220"]
|
|
14
|
+
keywords = ["nautilus_trader", "nautilustrader", "finzdata", "market-data", "backtesting", "futures", "point-in-time", "survivorship-bias"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
19
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://finzdata.com/nautilus"
|
|
24
|
+
Documentation = "https://finzdata.com/docs"
|
|
25
|
+
Source = "https://github.com/FinzData/nautilus-finzdata"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
packages = ["nautilus_finzdata"]
|