Skip to content

Commit e1aa611

Browse files
committed
Add latest
1 parent 1cbda97 commit e1aa611

11 files changed

Lines changed: 51879 additions & 13 deletions

File tree

Trading/algo/ranker/ranker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ class RobustRangeScorer(ScoreCalculator):
9797
3. Trend Penalty (penalizing diagonal movement).
9898
"""
9999

100-
upper_percentile: float = 90.0
101-
lower_percentile: float = 10.0
100+
upper_percentile: float = 95.0
101+
lower_percentile: float = 5.0
102102

103103
def calculate(self, history: History) -> float:
104104
if history.len < 2:

Trading/live/alert/yfinance_alert.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
import yfinance as yf
66

7-
# session pooling
8-
import requests
9-
session = requests.Session()
107
class YFinanceSpotAlert(SpotAlert):
118
model_config = ConfigDict(arbitrary_types_allowed=True)
129
# configure field ticker to not be included in serialization

Trading/live/range/filter_ranging_stocks.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from Trading.instrument import Instrument
88
from Trading.model.timeframes import Timeframe
99
from Trading.live.client.client import YFinanceLoggingClient
10-
from Trading.symbols.constants import YAHOO_STOCK_SYMBOLS
10+
from Trading.symbols.constants import (YAHOO_STOCK_SYMBOLS,
11+
YAHOO_FTSE_ALL_WORLD_SYMBOLS,
12+
YAHOO_SWITZERLAND_SYMBOLS)
1113
from Trading.model.history import History, OHLC
1214
from Trading.utils.time import get_date_now_cet
1315
from Trading.utils.custom_logging import get_logger
@@ -34,7 +36,7 @@ def exit():
3436
range_scorer = RangeScorer(window=RANGE_WIDTH)
3537
range_coherence = RangeCoherenceMetric(window=RANGE_WIDTH)
3638
range_robust_scorer = RobustRangeScorer(window=RANGE_WIDTH)
37-
range_ordering = Ordering(top_n=ORDERING_SIZE, score_calculator=range_robust_scorer)
39+
range_ordering = Ordering(top_n=ORDERING_SIZE, score_calculator=range_coherence)
3840

3941
class StockRangeProcessor(StatefulDataProcessor):
4042
def __init__(
@@ -86,6 +88,11 @@ def reprocess_item(self, item, iteration_index, client):
8688
# This works best if some parameter of the range ordering has changed
8789
if self.data[item] is None:
8890
return
91+
if ".KS" in item or ".TW" in item or ".HK" in item:
92+
return
93+
#If before the first . we have only digits, skip
94+
if item.split(".")[0].isdigit():
95+
return
8996
history = History(**self.data[item])
9097
range_ordering.add_history(history)
9198
self.data["range_ordering"] = range_ordering.model_dump()
@@ -96,15 +103,15 @@ def reprocess_item(self, item, iteration_index, client):
96103
client = YFinanceLoggingClient()
97104

98105
# temp json file storage
99-
file_path = f"range-scorer-stocks-{get_date_now_cet()}.json"
106+
file_path = f"range-scorer-stocks-switzerland.json"
100107
js = JsonFileRW(
101108
RANGING_STOCKS_PATH.joinpath(file_path),
102109
LOGGER,
103110
)
104111
sp = StockRangeProcessor(
105112
js, LOGGER, should_reload_ordering=False, should_reprocess=True
106113
)
107-
symbols = YAHOO_STOCK_SYMBOLS
114+
symbols = YAHOO_SWITZERLAND_SYMBOLS
108115
LOGGER.info(f"Items length: {len(symbols)}")
109116
sp.run(items=symbols, client=client)
110117

@@ -211,3 +218,36 @@ def reprocess_item(self, item, iteration_index, client):
211218
#TBCG.UK_9: 1.105
212219
#TXRH.US_9: 1.103
213220
#TKA.DE_9: 1.100
221+
222+
# improved coherence
223+
# 005420.KS: 1.453
224+
# INFO - 1691.HK: 1.386
225+
# INFO - TKMS.DE: 1.346
226+
# INFO - 2158.HK: 1.331
227+
# INFO - SIDO.JK: 1.328
228+
# INFO - SUNTV.NS: 1.266
229+
# INFO - RNA: 1.256
230+
# INFO - RENT4.SA: 1.243
231+
# INFO - MKS.L: 1.231
232+
# INFO - 6632.T: 1.224
233+
# INFO - EXP: 1.210
234+
# INFO - 7575.T: 1.206
235+
# INFO - 138040.KS: 1.193
236+
# INFO - HO.PA: 1.188
237+
# INFO - 2034.TW: 1.188
238+
# INFO - MICC.AS: 1.184
239+
# INFO - VOLTAS.NS: 1.181
240+
# INFO - 2474.TW: 1.176
241+
# INFO - MAS: 1.170
242+
# INFO - BPT.AX: 1.156
243+
# INFO - MONC.MI: 1.144
244+
# INFO - HOMB: 1.131
245+
# INFO - 600639.SS: 1.131
246+
# INFO - 365550.KS: 1.130
247+
# INFO - 7545.T: 1.119
248+
# INFO - NHPC.NS: 1.113
249+
# INFO - SIG.AX: 1.110
250+
# INFO - ENOG.L: 1.108
251+
# INFO - BVI.PA: 1.105
252+
# INFO - 7414.T: 1.104
253+
# INFO - 4369.T: 1.101

Trading/render/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from loan.loan import LoanJsonParser
1414
from stock.pvgo_calculator import calculate_pvgo
1515
from stock.iv_15 import calculate_iv15_tragic_algebra
16-
from stock.yfinance.dividend_sustainability import get_data, analyze_dividend_sustainability
16+
from stock.yf_stock.dividend_sustainability import get_data, analyze_dividend_sustainability
1717

1818
app = FastAPI()
1919

Trading/stock/fetch_sp500_weights.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818

1919
def fetch_market_caps(symbols: list) -> dict:
20+
import time
2021
print(f"Fetching market caps for {len(symbols)} tickers...", flush=True)
2122
batch = yf.Tickers(" ".join(symbols))
2223
caps = {}
@@ -25,6 +26,7 @@ def fetch_market_caps(symbols: list) -> dict:
2526
caps[sym] = batch.tickers[sym].fast_info.market_cap
2627
except Exception:
2728
caps[sym] = None
29+
time.sleep(0.15)
2830
if i % 50 == 0:
2931
print(f" {i}/{len(symbols)}", flush=True)
3032
return caps

Trading/symbols/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def append_to_alphaspread_url_dict(ticker_urls: dict):
3636
YAHOO_COMMODITY_SYMBOLS_PATH = YAHOO_FINANCE_SYMBOLS_PATH / __COMMODITIES_JSON
3737
YAHOO_STOCK_SYMBOLS_PATH = YAHOO_FINANCE_SYMBOLS_PATH / __STOCKS_JSON
3838
YAHOO_FTSE_ALL_WORLD_SYMBOLS_PATH = YAHOO_FINANCE_SYMBOLS_PATH / "ftse_allworld_stocks.json"
39+
YAHOO_SWITZERLAND_SYMBOLS_PATH = YAHOO_FINANCE_SYMBOLS_PATH / "switzerland_stocks.json"
3940

4041
with open(YAHOO_STOCK_SYMBOLS_PATH, "r") as f:
4142
YAHOO_STOCK_SYMBOLS_DICT = json.load(f)
@@ -45,6 +46,10 @@ def append_to_alphaspread_url_dict(ticker_urls: dict):
4546
YAHOO_FTSE_ALL_WORLD_SYMBOLS_DICT = json.load(f)
4647
YAHOO_FTSE_ALL_WORLD_SYMBOLS = [symbol for symbol in YAHOO_FTSE_ALL_WORLD_SYMBOLS_DICT]
4748

49+
with open(YAHOO_SWITZERLAND_SYMBOLS_PATH, "r") as f:
50+
YAHOO_SWITZERLAND_SYMBOLS_DICT = json.load(f)
51+
YAHOO_SWITZERLAND_SYMBOLS = [symbol for symbol in YAHOO_SWITZERLAND_SYMBOLS_DICT]
52+
4853
# GURUFOCUS
4954
GURUFOCUS_SYMBOLS_PATH = __CURRENT_FILE_PATH.joinpath("gurufocus/")
5055
GURUFOCUS_STOCK_SYMBOLS_PATH = GURUFOCUS_SYMBOLS_PATH / __STOCKS_JSON

Trading/symbols/yfinance/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ def get_exchanes_by_countries(country_codes: list[str]) -> list[str]:
7070
for code in country_codes:
7171
exchanges.extend(EXCHANGES_BY_COUNTRY.get(code, []))
7272
return exchanges
73+
74+
FILTERED_EXCHANGES = get_exchanes_by_countries(["CH"])

Trading/symbols/yfinance/fetch_all_stocks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
import yfinance as yf
2121
from stateful_data_processor.file_rw import JsonFileRW
2222
from stateful_data_processor.parallel_processor import ParallelStatefulDataProcessor
23-
from Trading.symbols.yfinance.constants import EXCHANGES
23+
from Trading.symbols.yfinance.constants import FILTERED_EXCHANGES
2424

2525
SCRIPT_DIR = Path(__file__).parent
2626

2727
RAW_FILE = SCRIPT_DIR / f"{date.today()}-all-equities.json"
28-
OUTPUT_FILE = SCRIPT_DIR / "all_stocks.json"
28+
OUTPUT_FILE = SCRIPT_DIR / "switzerland_stocks.json"
2929

3030

3131
PAGE_SIZE = 250
@@ -85,7 +85,7 @@ def build_stocks_json(data: dict) -> dict:
8585
print(f"Output : {OUTPUT_FILE}\n")
8686

8787
fetcher = EquityFetcher(JsonFileRW(str(RAW_FILE)), n_workers=4, logger=LOGGER)
88-
fetcher.run(EXCHANGES)
88+
fetcher.run(FILTERED_EXCHANGES)
8989

9090
total_raw = sum(len(v) for v in fetcher.data.values())
9191
print(f"\nBuilding {OUTPUT_FILE.name} from {total_raw} raw entries...")
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""
2+
Fetch S&P 500 and FTSE All-World index constituent tickers.
3+
4+
S&P 500 — Wikipedia constituent table (stable, maintained by community)
5+
FTSE AW — Vanguard VT ETF portfolio API (tracks FTSE Global All Cap Index,
6+
which is FTSE All-World + small-cap; ~10 000 stocks)
7+
8+
Output format matches all_stocks.json:
9+
{ "AAPL": ["AAPL", "Apple Inc.", "https://finance.yahoo.com/quote/AAPL/"] }
10+
11+
Usage:
12+
python fetch_index_stocks.py # both indices
13+
python fetch_index_stocks.py --sp500
14+
python fetch_index_stocks.py --ftse
15+
"""
16+
17+
import argparse
18+
import json
19+
import logging
20+
from pathlib import Path
21+
22+
import pandas as pd
23+
import requests
24+
25+
SCRIPT_DIR = Path(__file__).parent
26+
27+
SP500_FILE = SCRIPT_DIR / "sp500_stocks.json"
28+
FTSE_AW_FILE = SCRIPT_DIR / "ftse_allworld_stocks.json"
29+
30+
SP500_WIKI_URL = "https://en.wikipedia.org/wiki/List_of_S%26P_500_companies"
31+
32+
# Vanguard VT ETF — tracks FTSE Global All Cap (= FTSE All-World + small-cap)
33+
VT_API_URL = (
34+
"https://investor.vanguard.com/investment-products/etfs/profile/api/"
35+
"VT/portfolio-holding/stock"
36+
)
37+
VT_PAGE_SIZE = 500
38+
39+
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; Python/requests)"}
40+
41+
logging.basicConfig(level=logging.INFO, format="%(message)s")
42+
43+
# ISIN country code → yfinance exchange suffix.
44+
# Countries absent from this map are assumed to already be in yfinance format (US)
45+
# or have no reliable single exchange.
46+
_ISIN_SUFFIX: dict[str, str] = {
47+
"AT": ".VI",
48+
"AU": ".AX",
49+
"BE": ".BR",
50+
"BR": ".SA",
51+
"CA": ".TO",
52+
"CH": ".SW",
53+
"CL": ".SN",
54+
"DE": ".DE",
55+
"DK": ".CO",
56+
"EG": ".CA",
57+
"ES": ".MC",
58+
"FI": ".HE",
59+
"FR": ".PA",
60+
"GB": ".L",
61+
"GR": ".AT",
62+
"HK": ".HK", # also zero-padded to 4 digits; handled in _to_yf_symbol
63+
"ID": ".JK",
64+
"IE": ".I",
65+
"IL": ".TA",
66+
"IN": ".NS",
67+
"IT": ".MI",
68+
"JP": ".T",
69+
"KW": ".KW",
70+
"MX": ".MX",
71+
"MY": ".KL",
72+
"NL": ".AS",
73+
"NO": ".OL",
74+
"NZ": ".NZ",
75+
"PH": ".PS",
76+
"PL": ".WA",
77+
"PT": ".LS",
78+
"QA": ".QA",
79+
"SA": ".SR",
80+
"SE": ".ST",
81+
"SG": ".SI",
82+
"TH": ".BK", # -F/-R/-W exchange suffixes stripped; handled in _to_yf_symbol
83+
"TR": ".IS", # -E exchange suffix stripped; handled in _to_yf_symbol
84+
"TW": ".TW",
85+
"ZA": ".JO",
86+
}
87+
88+
# Vanguard-specific trailing qualifiers that are NOT part of the yfinance ticker
89+
_TH_STRIP = ("-F", "-R", "-W", "-P") # Thai foreign/NVDR/warrant shares
90+
91+
92+
def _to_yf_symbol(ticker: str, isin: str) -> str:
93+
"""Convert a Vanguard-format ticker + ISIN to the correct yfinance symbol."""
94+
if not ticker:
95+
return ticker
96+
97+
country = isin[:2] if isin and len(isin) >= 2 else ""
98+
99+
if not country or country == "US":
100+
return ticker
101+
102+
t = ticker
103+
104+
# Strip exchange-qualifier suffixes that Vanguard appends but yfinance doesn't use
105+
if country == "TH":
106+
for sfx in _TH_STRIP:
107+
if t.endswith(sfx):
108+
t = t[: -len(sfx)]
109+
break
110+
elif country == "TR" and t.endswith("-E"):
111+
t = t[:-2]
112+
113+
# Country-specific suffix logic
114+
if country == "CN":
115+
# Shanghai tickers start with 6; everything else is Shenzhen
116+
suffix = ".SS" if t and t[0] == "6" else ".SZ"
117+
elif country == "KR":
118+
# ISIN position 2: '7' = KOSPI (.KS), '8' = KOSDAQ (.KQ)
119+
suffix = ".KS" if len(isin) > 2 and isin[2] == "7" else ".KQ"
120+
elif country in ("HK", "KY") and t.isdigit():
121+
# HK-listed: direct HK ISIN, or Cayman-registered Chinese companies (KY ISIN)
122+
# KY + alphabetic ticker = US-listed ADR → handled below by the empty suffix
123+
t = t.zfill(4) # '700' → '0700'
124+
suffix = ".HK"
125+
else:
126+
suffix = _ISIN_SUFFIX.get(country, "")
127+
128+
return f"{t}{suffix}" if suffix else t
129+
130+
131+
def fetch_sp500() -> dict[str, list]:
132+
"""Scrape current S&P 500 constituents from Wikipedia."""
133+
print("Fetching S&P 500 from Wikipedia ...")
134+
df = pd.read_html(SP500_WIKI_URL, attrs={"id": "constituents"})[0]
135+
stocks = {}
136+
for _, row in df.iterrows():
137+
symbol = str(row["Symbol"]).strip().replace(".", "-") # BRK.B → BRK-B
138+
name = str(row["Security"])
139+
stocks[symbol] = [symbol, name, f"https://finance.yahoo.com/quote/{symbol}/"]
140+
print(f" {len(stocks)} tickers")
141+
return dict(sorted(stocks.items()))
142+
143+
144+
def fetch_ftse_all_world() -> dict[str, list]:
145+
"""Fetch FTSE All-World constituents via Vanguard's VT ETF portfolio API.
146+
147+
VT tracks the FTSE Global All Cap Index (FTSE All-World + small-cap).
148+
ISINs from the API are used to derive the correct yfinance exchange suffix
149+
for each stock (e.g. 600020 → 600020.SS, 7203 → 7203.T).
150+
"""
151+
print("Fetching FTSE All-World (Vanguard VT portfolio API) ...")
152+
stocks = {}
153+
start = 1
154+
total = None
155+
156+
while True:
157+
resp = requests.get(
158+
VT_API_URL,
159+
params={"start": start, "count": VT_PAGE_SIZE},
160+
headers=HEADERS,
161+
timeout=30,
162+
)
163+
resp.raise_for_status()
164+
data = resp.json()
165+
166+
if total is None:
167+
total = data.get("size", "?")
168+
169+
for entity in data.get("fund", {}).get("entity", []):
170+
raw_ticker = str(entity.get("ticker", "")).strip()
171+
isin = str(entity.get("isin", "")).strip()
172+
name = str(entity.get("longName", "") or entity.get("shortName", "")).strip()
173+
if not raw_ticker or raw_ticker == "nan":
174+
continue
175+
symbol = _to_yf_symbol(raw_ticker, isin)
176+
stocks[symbol] = [symbol, name, f"https://finance.yahoo.com/quote/{symbol}/"]
177+
178+
fetched = start + VT_PAGE_SIZE - 1
179+
print(f" {min(fetched, total if isinstance(total, int) else fetched)}/{total}")
180+
181+
if "next" not in data:
182+
break
183+
start += VT_PAGE_SIZE
184+
185+
print(f" {len(stocks)} unique tickers")
186+
return dict(sorted(stocks.items()))
187+
188+
189+
def _save(stocks: dict, path: Path) -> None:
190+
with open(path, "w") as f:
191+
json.dump(stocks, f, indent=4)
192+
print(f"Saved → {path}")
193+
194+
195+
if __name__ == "__main__":
196+
parser = argparse.ArgumentParser(description=__doc__)
197+
parser.add_argument("--sp500", action="store_true", help="Fetch S&P 500 only")
198+
parser.add_argument("--ftse", action="store_true", help="Fetch FTSE All-World only")
199+
args = parser.parse_args()
200+
run_all = not args.sp500 and not args.ftse
201+
202+
if args.sp500 or run_all:
203+
_save(fetch_sp500(), SP500_FILE)
204+
205+
if args.ftse or run_all:
206+
_save(fetch_ftse_all_world(), FTSE_AW_FILE)

0 commit comments

Comments
 (0)