tradekart-backtester-data 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.
- tradekart_backtester_data-0.1.0/PKG-INFO +46 -0
- tradekart_backtester_data-0.1.0/README.md +29 -0
- tradekart_backtester_data-0.1.0/data/__init__.py +0 -0
- tradekart_backtester_data-0.1.0/data/backfill.py +33 -0
- tradekart_backtester_data-0.1.0/data/constants.py +2 -0
- tradekart_backtester_data-0.1.0/data/fetch.py +18 -0
- tradekart_backtester_data-0.1.0/data/interfaces.py +21 -0
- tradekart_backtester_data-0.1.0/data/lambda_handler.py +11 -0
- tradekart_backtester_data-0.1.0/data/loader.py +19 -0
- tradekart_backtester_data-0.1.0/data/pipeline.py +40 -0
- tradekart_backtester_data-0.1.0/data/storage.py +44 -0
- tradekart_backtester_data-0.1.0/data/symbol_cache.py +57 -0
- tradekart_backtester_data-0.1.0/data/symbols.py +14 -0
- tradekart_backtester_data-0.1.0/data/version.py +4 -0
- tradekart_backtester_data-0.1.0/pyproject.toml +40 -0
- tradekart_backtester_data-0.1.0/setup.cfg +4 -0
- tradekart_backtester_data-0.1.0/tests/test_loader.py +31 -0
- tradekart_backtester_data-0.1.0/tests/test_pipeline.py +31 -0
- tradekart_backtester_data-0.1.0/tests/test_storage.py +15 -0
- tradekart_backtester_data-0.1.0/tests/test_symbol_cache.py +51 -0
- tradekart_backtester_data-0.1.0/tradekart_backtester_data.egg-info/PKG-INFO +46 -0
- tradekart_backtester_data-0.1.0/tradekart_backtester_data.egg-info/SOURCES.txt +23 -0
- tradekart_backtester_data-0.1.0/tradekart_backtester_data.egg-info/dependency_links.txt +1 -0
- tradekart_backtester_data-0.1.0/tradekart_backtester_data.egg-info/requires.txt +10 -0
- tradekart_backtester_data-0.1.0/tradekart_backtester_data.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tradekart-backtester-data
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Daily NSE price data fetcher and storage for TradeKart's backtester
|
|
5
|
+
Author-email: Vikalp Varshney <vikalp@tradekart.gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: tradekart-cloud
|
|
9
|
+
Requires-Dist: yfinance>=0.2.40
|
|
10
|
+
Requires-Dist: pandas>=2.2.0
|
|
11
|
+
Requires-Dist: pyarrow>=16.0.0
|
|
12
|
+
Requires-Dist: requests>=2.31.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
15
|
+
Requires-Dist: moto[s3]>=5.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# README #
|
|
19
|
+
|
|
20
|
+
This README would normally document whatever steps are necessary to get your application up and running.
|
|
21
|
+
|
|
22
|
+
### What is this repository for? ###
|
|
23
|
+
|
|
24
|
+
* Quick summary
|
|
25
|
+
* Version
|
|
26
|
+
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
|
27
|
+
|
|
28
|
+
### How do I get set up? ###
|
|
29
|
+
|
|
30
|
+
* Summary of set up
|
|
31
|
+
* Configuration
|
|
32
|
+
* Dependencies
|
|
33
|
+
* Database configuration
|
|
34
|
+
* How to run tests
|
|
35
|
+
* Deployment instructions
|
|
36
|
+
|
|
37
|
+
### Contribution guidelines ###
|
|
38
|
+
|
|
39
|
+
* Writing tests
|
|
40
|
+
* Code review
|
|
41
|
+
* Other guidelines
|
|
42
|
+
|
|
43
|
+
### Who do I talk to? ###
|
|
44
|
+
|
|
45
|
+
* Repo owner or admin
|
|
46
|
+
* Other community or team contact
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# README #
|
|
2
|
+
|
|
3
|
+
This README would normally document whatever steps are necessary to get your application up and running.
|
|
4
|
+
|
|
5
|
+
### What is this repository for? ###
|
|
6
|
+
|
|
7
|
+
* Quick summary
|
|
8
|
+
* Version
|
|
9
|
+
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
|
10
|
+
|
|
11
|
+
### How do I get set up? ###
|
|
12
|
+
|
|
13
|
+
* Summary of set up
|
|
14
|
+
* Configuration
|
|
15
|
+
* Dependencies
|
|
16
|
+
* Database configuration
|
|
17
|
+
* How to run tests
|
|
18
|
+
* Deployment instructions
|
|
19
|
+
|
|
20
|
+
### Contribution guidelines ###
|
|
21
|
+
|
|
22
|
+
* Writing tests
|
|
23
|
+
* Code review
|
|
24
|
+
* Other guidelines
|
|
25
|
+
|
|
26
|
+
### Who do I talk to? ###
|
|
27
|
+
|
|
28
|
+
* Repo owner or admin
|
|
29
|
+
* Other community or team contact
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import yfinance as yf
|
|
4
|
+
from cloud import AWS
|
|
5
|
+
|
|
6
|
+
from .storage import S3ParquetStore
|
|
7
|
+
from .symbol_cache import get_nifty100_symbols
|
|
8
|
+
|
|
9
|
+
YEARS_OF_HISTORY = 3
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def backfill():
|
|
13
|
+
aws = AWS()
|
|
14
|
+
store = S3ParquetStore(aws=aws)
|
|
15
|
+
symbols = get_nifty100_symbols(aws)
|
|
16
|
+
|
|
17
|
+
start = date.today() - timedelta(days=365 * YEARS_OF_HISTORY)
|
|
18
|
+
print(f"Backfilling {len(symbols)} symbols from {start} to today...")
|
|
19
|
+
|
|
20
|
+
for symbol in symbols:
|
|
21
|
+
data = yf.download(symbol, start=start, end=date.today(), progress=False)
|
|
22
|
+
if data.empty:
|
|
23
|
+
print(f" {symbol}: no data, skipping")
|
|
24
|
+
continue
|
|
25
|
+
data.index.name = "date"
|
|
26
|
+
store.save(symbol, data[["Open", "High", "Low", "Close", "Volume"]])
|
|
27
|
+
print(f" {symbol}: saved {len(data)} rows")
|
|
28
|
+
|
|
29
|
+
print("Backfill complete.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
backfill()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import yfinance as yf
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class YahooFinanceSource:
|
|
8
|
+
def fetch_latest_candle(self, symbol: str, for_date: date) -> pd.DataFrame | None:
|
|
9
|
+
data = yf.download(
|
|
10
|
+
symbol,
|
|
11
|
+
start=for_date,
|
|
12
|
+
end=for_date + timedelta(days=1),
|
|
13
|
+
progress=False,
|
|
14
|
+
)
|
|
15
|
+
if data.empty:
|
|
16
|
+
return None
|
|
17
|
+
data.index.name = "date"
|
|
18
|
+
return data[["Open", "High", "Low", "Close", "Volume"]]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PriceStore(Protocol):
|
|
8
|
+
def load(self, symbol: str) -> pd.DataFrame | None:
|
|
9
|
+
"""
|
|
10
|
+
Return the symbol's full stored history, or None if nothing stored yet.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def save(self, symbol: str, prices: pd.DataFrame) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Overwrite the symbol's stored history with this DataFrame
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DataSource(Protocol):
|
|
20
|
+
def fetch_latest_candle(self, symbol: str, for_date: date) -> pd.DataFrame | None:
|
|
21
|
+
"""Return one day's OHLCV for this symbol, or None if unavailable."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from cloud import AWS
|
|
3
|
+
|
|
4
|
+
from .interfaces import PriceStore
|
|
5
|
+
from .storage import S3ParquetStore
|
|
6
|
+
from .symbol_cache import get_nifty100_symbols
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_all_prices(store: PriceStore | None = None, aws: AWS | None = None):
|
|
10
|
+
aws = aws or AWS()
|
|
11
|
+
store = store or S3ParquetStore(aws)
|
|
12
|
+
symbols = get_nifty100_symbols(aws)
|
|
13
|
+
|
|
14
|
+
closes: dict[str, pd.Series] = {}
|
|
15
|
+
for symbol in symbols:
|
|
16
|
+
data = store.load(symbol)
|
|
17
|
+
if data is not None:
|
|
18
|
+
closes[symbol] = data["Close"]
|
|
19
|
+
return pd.DataFrame(closes).sort_index()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from cloud import AWS
|
|
5
|
+
|
|
6
|
+
from .fetch import YahooFinanceSource
|
|
7
|
+
from .interfaces import DataSource, PriceStore
|
|
8
|
+
from .storage import S3ParquetStore
|
|
9
|
+
from .symbol_cache import get_nifty100_symbols
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run(
|
|
13
|
+
store: PriceStore | None = None,
|
|
14
|
+
data_source: DataSource | None = None,
|
|
15
|
+
aws: AWS | None = None,
|
|
16
|
+
for_date: date | None = None,
|
|
17
|
+
) -> dict:
|
|
18
|
+
aws = aws or AWS()
|
|
19
|
+
store = store or S3ParquetStore(aws)
|
|
20
|
+
data_source = data_source or YahooFinanceSource()
|
|
21
|
+
target_date = for_date or date.today() - timedelta(days=1)
|
|
22
|
+
symbols = get_nifty100_symbols(aws)
|
|
23
|
+
fetched, skipped = 0, 0
|
|
24
|
+
|
|
25
|
+
for symbol in symbols:
|
|
26
|
+
candle = data_source.fetch_latest_candle(symbol, target_date)
|
|
27
|
+
if candle is None:
|
|
28
|
+
skipped += 1
|
|
29
|
+
continue
|
|
30
|
+
existing = store.load(symbol)
|
|
31
|
+
combined = pd.concat([existing, candle]) if existing is not None else candle
|
|
32
|
+
combined = combined[~combined.index.duplicated(keep="last")].sort_index()
|
|
33
|
+
store.save(symbol, combined)
|
|
34
|
+
fetched += 1
|
|
35
|
+
return {
|
|
36
|
+
"fetched": fetched,
|
|
37
|
+
"skipped": skipped,
|
|
38
|
+
"total": len(symbols),
|
|
39
|
+
"date": str(target_date),
|
|
40
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from cloud import AWS
|
|
6
|
+
from cloud.exceptions import TradeKartCloudError
|
|
7
|
+
from cloud.storage.s3 import DownloadFileRequest, UploadBytesRequest
|
|
8
|
+
|
|
9
|
+
from .constants import BUCKET
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class S3ParquetStore:
|
|
13
|
+
def __init__(self, aws: AWS | None = None, bucket: str = BUCKET):
|
|
14
|
+
self._aws = aws or AWS()
|
|
15
|
+
self._bucket = bucket
|
|
16
|
+
|
|
17
|
+
def _object_key(self, symbol: str) -> str:
|
|
18
|
+
return f"prices/{symbol}.parquet"
|
|
19
|
+
|
|
20
|
+
def load(self, symbol: str) -> pd.DataFrame | None:
|
|
21
|
+
tmp_path = Path(f"/tmp/{symbol}.parquet")
|
|
22
|
+
try:
|
|
23
|
+
self._aws.s3.download_file(
|
|
24
|
+
DownloadFileRequest(
|
|
25
|
+
bucket_name=self._bucket,
|
|
26
|
+
object_key=self._object_key(symbol),
|
|
27
|
+
destination_path=tmp_path,
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
except TradeKartCloudError:
|
|
31
|
+
return None
|
|
32
|
+
return pd.read_parquet(tmp_path)
|
|
33
|
+
|
|
34
|
+
def save(self, symbol: str, prices: pd.DataFrame) -> None:
|
|
35
|
+
buffer = io.BytesIO()
|
|
36
|
+
prices.to_parquet(buffer)
|
|
37
|
+
self._aws.s3.upload_bytes(
|
|
38
|
+
UploadBytesRequest(
|
|
39
|
+
bucket_name=self._bucket,
|
|
40
|
+
object_key=self._object_key(symbol),
|
|
41
|
+
body=buffer.getvalue(),
|
|
42
|
+
content_type="application/octet-stream",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from cloud import AWS
|
|
6
|
+
from cloud.exceptions import TradeKartCloudError
|
|
7
|
+
from cloud.storage.s3 import DownloadFileRequest, UploadBytesRequest
|
|
8
|
+
|
|
9
|
+
from .constants import BUCKET
|
|
10
|
+
from .symbols import fetch_nifty100_symbols
|
|
11
|
+
|
|
12
|
+
CACHE_KEY = "symbols/nifty100.json"
|
|
13
|
+
MAX_AGE_DAYS = 180
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_cache(aws: AWS) -> dict | None:
|
|
17
|
+
tmp_path = Path("/tmp/nifty100_cache.json")
|
|
18
|
+
try:
|
|
19
|
+
aws.s3.download_file(
|
|
20
|
+
DownloadFileRequest(
|
|
21
|
+
bucket_name=BUCKET, object_key=CACHE_KEY, destination_path=tmp_path
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
except TradeKartCloudError:
|
|
25
|
+
return None
|
|
26
|
+
return json.loads(tmp_path.read_text())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _save_cache(aws: AWS, symbols: list[str]) -> None:
|
|
30
|
+
payload = json.dumps(
|
|
31
|
+
{"fetched_on": date.today().isoformat(), "symbols": symbols}
|
|
32
|
+
).encode()
|
|
33
|
+
aws.s3.upload_bytes(
|
|
34
|
+
UploadBytesRequest(
|
|
35
|
+
bucket_name=BUCKET,
|
|
36
|
+
object_key=CACHE_KEY,
|
|
37
|
+
body=payload,
|
|
38
|
+
content_type="application/json",
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_nifty100_symbols(aws: AWS) -> list[str]:
|
|
44
|
+
cached = _load_cache(aws)
|
|
45
|
+
if cached and date.today() - date.fromisoformat(cached["fetched_on"]) < timedelta(
|
|
46
|
+
days=MAX_AGE_DAYS
|
|
47
|
+
):
|
|
48
|
+
return cached["symbols"]
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
symbols = fetch_nifty100_symbols()
|
|
52
|
+
except Exception:
|
|
53
|
+
if cached:
|
|
54
|
+
return cached["symbols"]
|
|
55
|
+
raise
|
|
56
|
+
_save_cache(aws, symbols)
|
|
57
|
+
return symbols
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import io
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from data.constants import NIFTY100_URL
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def fetch_nifty100_symbols() -> list[str]:
|
|
10
|
+
headers = {"User-Agent": "Mozilla/5.0"}
|
|
11
|
+
response = requests.get(NIFTY100_URL, headers=headers, timeout=10)
|
|
12
|
+
response.raise_for_status()
|
|
13
|
+
reader = csv.DictReader(io.StringIO(response.text))
|
|
14
|
+
return [f"{row['Symbol'].strip()}.NS" for row in reader]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tradekart-backtester-data"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
authors = [{ name = "Vikalp Varshney", email = "vikalp@tradekart.gmail.com" }]
|
|
9
|
+
description = "Daily NSE price data fetcher and storage for TradeKart's backtester"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"tradekart-cloud",
|
|
14
|
+
"yfinance>=0.2.40",
|
|
15
|
+
"pandas>=2.2.0",
|
|
16
|
+
"pyarrow>=16.0.0",
|
|
17
|
+
"requests>=2.31.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
include-package-data = true
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|
|
25
|
+
include = ["data*"]
|
|
26
|
+
namespaces = false
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.dynamic]
|
|
29
|
+
version = { attr = "data.version.__version__" }
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = ["pytest>=8.0.0", "moto[s3]>=5.0.0", "ruff>=0.6.0"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
ignore = [
|
|
39
|
+
"DTZ011", # date.today() is fine for day-level cache/batch checks - no precise moment-in-time matters here
|
|
40
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from data.loader import load_all_prices
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FakeStore:
|
|
7
|
+
def __init__(self, data: dict[str, pd.DataFrame]):
|
|
8
|
+
self._data = data
|
|
9
|
+
|
|
10
|
+
def load(self, symbol):
|
|
11
|
+
return self._data.get(symbol)
|
|
12
|
+
|
|
13
|
+
def save(self, symbol, prices):
|
|
14
|
+
self._data[symbol] = prices
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_load_all_prices_combines_symbols(monkeypatch):
|
|
18
|
+
fake_data = {
|
|
19
|
+
"RELIANCE.NS": pd.DataFrame(
|
|
20
|
+
{"Close": [100.0]}, index=pd.to_datetime(["2024-01-01"])
|
|
21
|
+
),
|
|
22
|
+
"TCS.NS": pd.DataFrame(
|
|
23
|
+
{"Close": [200.0]}, index=pd.to_datetime(["2024-01-01"])
|
|
24
|
+
),
|
|
25
|
+
}
|
|
26
|
+
monkeypatch.setattr(
|
|
27
|
+
"data.loader.get_nifty100_symbols", lambda aws: list(fake_data.keys())
|
|
28
|
+
)
|
|
29
|
+
wide = load_all_prices(store=FakeStore(fake_data))
|
|
30
|
+
assert list(wide.columns) == list(fake_data.keys())
|
|
31
|
+
assert wide.loc["2024-01-01", "RELIANCE.NS"] == 100.0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from data.pipeline import run
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FakeStore:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._data: dict[str, pd.DataFrame] = {}
|
|
11
|
+
|
|
12
|
+
def load(self, symbol):
|
|
13
|
+
return self._data.get(symbol)
|
|
14
|
+
|
|
15
|
+
def save(self, symbol, prices):
|
|
16
|
+
self._data[symbol] = prices
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FakeSource:
|
|
20
|
+
def fetch_latest_candle(self, symbol, for_date):
|
|
21
|
+
return pd.DataFrame({"Close": [100.0]}, index=pd.to_datetime([for_date]))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_run_fetches_and_saves_each_symbol(monkeypatch):
|
|
25
|
+
monkeypatch.setattr(
|
|
26
|
+
"data.pipeline.get_nifty100_symbols", lambda aws: ["RELIANCE.NS", "TCS.NS"]
|
|
27
|
+
)
|
|
28
|
+
store = FakeStore()
|
|
29
|
+
result = run(store=store, data_source=FakeSource(), for_date=date(2024, 1, 2))
|
|
30
|
+
assert result["fetched"] == 2
|
|
31
|
+
assert result["skipped"] == 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_save_then_load_round_trip(s3_store):
|
|
5
|
+
prices = pd.DataFrame(
|
|
6
|
+
{"Close": [100.0, 101.5]}, index=pd.date_range(start="2026-10-07", periods=2)
|
|
7
|
+
)
|
|
8
|
+
s3_store.save("RELIANCE.NS", prices)
|
|
9
|
+
loaded = s3_store.load("RELIANCE.NS")
|
|
10
|
+
assert loaded is not None
|
|
11
|
+
assert loaded["Close"].tolist() == [100.0, 101.5]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_load_missing_symbol_returns_none(s3_store):
|
|
15
|
+
assert s3_store.load("DOES_NOT_EXIST.NS") is None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from data import symbol_cache
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_fetches_live_when_no_cache(monkeypatch, aws, bucket):
|
|
7
|
+
monkeypatch.setattr(symbol_cache, "BUCKET", bucket)
|
|
8
|
+
monkeypatch.setattr(
|
|
9
|
+
symbol_cache,
|
|
10
|
+
"fetch_nifty100_symbols",
|
|
11
|
+
lambda: ["RELIANCE.NS", "TCS.NS", "INFY.NS"],
|
|
12
|
+
)
|
|
13
|
+
assert symbol_cache.get_nifty100_symbols(aws) == [
|
|
14
|
+
"RELIANCE.NS",
|
|
15
|
+
"TCS.NS",
|
|
16
|
+
"INFY.NS",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_uses_cache_when_fresh(monkeypatch, aws, bucket):
|
|
21
|
+
monkeypatch.setattr(symbol_cache, "BUCKET", bucket)
|
|
22
|
+
symbol_cache._save_cache(aws, ["CACHED.NS"])
|
|
23
|
+
|
|
24
|
+
def fail_if_called():
|
|
25
|
+
raise AssertionError("fetch_nifty100_symbols should not be called")
|
|
26
|
+
|
|
27
|
+
monkeypatch.setattr(symbol_cache, "fetch_nifty100_symbols", fail_if_called)
|
|
28
|
+
assert symbol_cache.get_nifty100_symbols(aws) == ["CACHED.NS"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_falls_back_to_stale_cache_if_nse_fails(monkeypatch, aws, bucket):
|
|
32
|
+
monkeypatch.setattr(symbol_cache, "BUCKET", bucket)
|
|
33
|
+
symbol_cache._save_cache(aws, ["STALE.NS"])
|
|
34
|
+
monkeypatch.setattr(symbol_cache, "MAX_AGE_DAYS", -1)
|
|
35
|
+
monkeypatch.setattr(
|
|
36
|
+
symbol_cache,
|
|
37
|
+
"fetch_nifty100_symbols",
|
|
38
|
+
lambda: (_ for _ in ()).throw(RuntimeError("NSE down")),
|
|
39
|
+
)
|
|
40
|
+
assert symbol_cache.get_nifty100_symbols(aws) == ["STALE.NS"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_raises_if_no_cache_and_nse_fails(monkeypatch, aws, bucket):
|
|
44
|
+
monkeypatch.setattr(symbol_cache, "BUCKET", bucket)
|
|
45
|
+
monkeypatch.setattr(
|
|
46
|
+
symbol_cache,
|
|
47
|
+
"fetch_nifty100_symbols",
|
|
48
|
+
lambda: (_ for _ in ()).throw(RuntimeError("NSE down")),
|
|
49
|
+
)
|
|
50
|
+
with pytest.raises(RuntimeError):
|
|
51
|
+
symbol_cache.get_nifty100_symbols(aws)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tradekart-backtester-data
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Daily NSE price data fetcher and storage for TradeKart's backtester
|
|
5
|
+
Author-email: Vikalp Varshney <vikalp@tradekart.gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: tradekart-cloud
|
|
9
|
+
Requires-Dist: yfinance>=0.2.40
|
|
10
|
+
Requires-Dist: pandas>=2.2.0
|
|
11
|
+
Requires-Dist: pyarrow>=16.0.0
|
|
12
|
+
Requires-Dist: requests>=2.31.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
15
|
+
Requires-Dist: moto[s3]>=5.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# README #
|
|
19
|
+
|
|
20
|
+
This README would normally document whatever steps are necessary to get your application up and running.
|
|
21
|
+
|
|
22
|
+
### What is this repository for? ###
|
|
23
|
+
|
|
24
|
+
* Quick summary
|
|
25
|
+
* Version
|
|
26
|
+
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
|
27
|
+
|
|
28
|
+
### How do I get set up? ###
|
|
29
|
+
|
|
30
|
+
* Summary of set up
|
|
31
|
+
* Configuration
|
|
32
|
+
* Dependencies
|
|
33
|
+
* Database configuration
|
|
34
|
+
* How to run tests
|
|
35
|
+
* Deployment instructions
|
|
36
|
+
|
|
37
|
+
### Contribution guidelines ###
|
|
38
|
+
|
|
39
|
+
* Writing tests
|
|
40
|
+
* Code review
|
|
41
|
+
* Other guidelines
|
|
42
|
+
|
|
43
|
+
### Who do I talk to? ###
|
|
44
|
+
|
|
45
|
+
* Repo owner or admin
|
|
46
|
+
* Other community or team contact
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
data/__init__.py
|
|
4
|
+
data/backfill.py
|
|
5
|
+
data/constants.py
|
|
6
|
+
data/fetch.py
|
|
7
|
+
data/interfaces.py
|
|
8
|
+
data/lambda_handler.py
|
|
9
|
+
data/loader.py
|
|
10
|
+
data/pipeline.py
|
|
11
|
+
data/storage.py
|
|
12
|
+
data/symbol_cache.py
|
|
13
|
+
data/symbols.py
|
|
14
|
+
data/version.py
|
|
15
|
+
tests/test_loader.py
|
|
16
|
+
tests/test_pipeline.py
|
|
17
|
+
tests/test_storage.py
|
|
18
|
+
tests/test_symbol_cache.py
|
|
19
|
+
tradekart_backtester_data.egg-info/PKG-INFO
|
|
20
|
+
tradekart_backtester_data.egg-info/SOURCES.txt
|
|
21
|
+
tradekart_backtester_data.egg-info/dependency_links.txt
|
|
22
|
+
tradekart_backtester_data.egg-info/requires.txt
|
|
23
|
+
tradekart_backtester_data.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
data
|