Market Watch: multiplayer GARP paper trading simulator

- Game engine with multiplayer support (create games, join, leaderboard)
- GARP stock screener (S&P 500 + 400 MidCap, 900+ tickers)
- Automated trading logic for AI player (Case)
- Web portal at marketwatch.local:8889 with dark theme
- Systemd timer for Mon-Fri market hours
- Telegram alerts on trades and daily summary
- Stock analysis deep dive data (BAC, CFG, FITB, INCY)
- Expanded scan results (22 GARP candidates)
- Craigslist account setup + credentials
This commit is contained in:
2026-02-08 15:18:41 -06:00
parent b6095ec964
commit be43231c3f
29 changed files with 4169 additions and 4 deletions

902
data/broad_tickers.txt Normal file
View File

@ -0,0 +1,902 @@
SLB
ADBE
WMG
DT
WBD
ELS
TKO
VZ
MTZ
SYNA
AZO
FITB
OPCH
AVTR
UNH
TFC
MANH
LDOS
MOS
MTB
SON
CFG
BLDR
LOW
TTMI
USB
SPGI
AA
CDP
WLK
SRE
GDDY
ALL
CARR
DECK
SW
ULTA
FND
IDA
COST
CSL
DG
CMS
IPGP
HOG
PBF
INVH
TSCO
MET
FNF
VNT
PII
KEYS
PWR
CHWY
NDAQ
PVH
PRI
MSCI
BXP
MSI
ARE
CINF
IR
HWC
APA
KRG
AMD
WING
LEN
FLR
KKR
USFD
GS
TAP
KNF
WWD
EBAY
ALLE
YETI
CAT
KMPR
DD
CAH
FCX
CTSH
RTX
TYL
MCO
ABNB
MO
OLLI
FICO
MNST
SSD
LVS
BMRN
CFR
PKG
WYNN
NFLX
RSG
WRB
RYAN
BHF
JPM
GPC
TXRH
AVB
IBM
PAYX
HRB
KBH
OTIS
FTNT
OLN
JCI
DLB
COF
HBAN
BSY
COTY
WTS
LRCX
AEP
PPL
VRTX
WMS
GTLS
V
CMC
WH
NRG
CPB
PB
CPRI
AXTA
RPM
IRT
SYF
HII
PLNT
HPE
WFRD
TXNM
AMP
ATO
WSO
AEE
CL
MMS
HLI
AMZN
AMCR
NXST
SBRA
CBRE
KVUE
HAE
LEA
ZBH
PK
SIGI
SHC
SLM
OGE
ULS
J
XPO
BEN
BRKR
VC
AKAM
FHI
MGM
KMI
WAB
CBSH
FIX
RH
IDXX
RF
GWW
SR
SLAB
SWX
INCY
LW
GE
SYY
EXPE
MAR
SEIC
IT
COIN
RRC
KLAC
HOMB
ADP
MRNA
BYD
CRL
GLW
ROL
VICI
AAON
ARMK
CRBG
STWD
TOL
MUSA
PPG
DY
CG
CBT
OC
VAL
BBY
DAR
XOM
VTRS
JAZZ
XYZ
MEDP
FIS
KIM
CHRD
MPC
WFC
BDX
IEX
AES
HOLX
RL
ACM
CART
BWA
DTE
PGR
ITT
ROP
CHDN
NBIX
WST
GMED
DOW
STLD
SFM
CDNS
WTRG
XRAY
UAL
WAT
DASH
JBL
FFIV
ETR
TTC
CPRT
D
THC
IBOC
ALGN
ILMN
H
DXCM
FISV
MLI
SWK
COO
IBKR
KNX
FLEX
STX
GEHC
HQY
HL
PSN
NTAP
ESNT
LPX
CCI
VMC
FDX
ERIE
L
TMUS
CTRA
COLB
BRBR
UFPI
CACI
CVS
WPC
RVTY
GD
FAF
TNL
ASB
REGN
MIDD
COHR
CNM
AME
HAL
PTC
AFL
MTD
LSCC
MRSH
TSN
GILD
HLNE
LAD
ADSK
NLY
R
THG
TLN
FE
PNFP
UPS
REXR
SNDK
MSM
OLED
TRU
UNM
IP
HR
COLM
EQIX
CSX
ANF
FRT
BDC
PSTG
BJ
COP
JHG
FCFS
MU
BWXT
HIG
INTC
NWS
NJR
JKHY
KEY
SARO
GM
SCHW
ES
VRSN
ON
APG
FTV
HIMS
BRO
GOOG
PLD
SJM
TRGP
FANG
JLL
MTG
STZ
ANET
MDLZ
WSM
DTM
SWKS
RBA
APP
BLD
WBS
QCOM
AMT
RGEN
AYI
YUM
AMH
OMC
G
SATS
ALB
APO
FAST
PSKY
PCAR
TTEK
ALK
CELH
HD
CRWD
PSA
PNC
RRX
LYV
LULU
ENSG
AOS
MCD
MCHP
ZTS
EEFT
GWRE
TEX
NEU
CNH
GOOGL
ODFL
CIEN
TTWO
KBR
UTHR
INTU
IQV
O
EVR
POR
VNO
FCN
STAG
SOLV
GLPI
COR
LIVN
MKC
MA
FOX
CNXC
Q
MCK
FOUR
BC
AON
CMI
CBOE
SMG
GPK
BLKB
NVDA
PANW
HPQ
ATR
DVA
ACN
HOOD
NSC
PNR
POOL
TT
AIT
CLH
EXR
GIS
TRV
CNO
MSFT
DLR
ESAB
GEN
NVT
WAL
UGI
PEP
NWSA
CRH
LHX
GAP
PH
LOPE
RCL
LNT
WEC
EPAM
TSLA
PEN
CRS
RYN
SNX
KTOS
ADI
GNRC
OXY
RJF
ONTO
NOC
CXT
TREX
CHD
JBHT
RBC
AXP
TPL
KNSL
ROIV
VST
META
REG
NOV
CVNA
DRI
BAX
ASGN
GGG
EQT
CHTR
PPC
CF
RS
DLTR
STRL
PNW
CEG
SBAC
PAYC
BBWI
LECO
APD
BSX
AMAT
OKE
ABT
CAR
COKE
CDW
NSA
NDSN
VFC
JNJ
NOVT
SYK
PATH
LAMR
EQH
AMGN
BURL
WTFC
HSY
AN
NXPI
KO
UBER
CPT
ORLY
HLT
WMB
EIX
INGR
PFE
CGNX
CMCSA
GEV
PR
APTV
FLS
PCG
PYPL
ELAN
GRMN
EGP
DOC
CVX
DIS
FR
LNTH
CSGP
DUOL
CME
FN
TXT
NOW
WHR
CHH
AHR
EXLS
PLTR
BKR
ASH
HSIC
BLK
T
NXT
MOH
XEL
MPWR
EFX
AVAV
EPR
VVV
DOCU
KHC
MAA
EXE
AR
DBX
ALLY
MMM
BALL
FNB
SAIA
UMBF
CAVA
AFG
TDY
IFF
CCK
POST
ATI
ORI
PINS
SGI
TECH
ARW
AAPL
ACGL
EL
HST
UBSI
SBUX
EA
RLI
C
KDP
PHM
BRK.B
CROX
NUE
KEX
FLG
MTN
PM
HGV
SCI
DKS
LII
MLM
ARES
CRM
AJG
ISRG
SMCI
EXP
NEE
LLY
LSTR
TKR
DDOG
DCI
DAL
TXN
LMT
ST
DELL
TJX
CYTK
GNTX
MTCH
BROS
VLO
VRSK
SNA
TEL
MDT
ACI
MUR
WEX
AM
UNP
GTM
MASI
PEG
MTDR
CPAY
SSB
ENS
OVV
ZION
EHC
UDR
BAH
AMKR
MORN
STE
VMI
MTSI
F
FBIN
EVRG
CTAS
AVY
AVGO
DOCS
EOG
NFG
EG
CNX
VNOM
SF
PCTY
BAC
HON
FHN
RNR
PEGA
TWLO
WDC
OGS
ED
AIG
MRK
HUBB
LITE
TPR
HRL
EME
AAL
MAT
CNC
DOV
TDG
DINO
EWBC
BKH
GEF
RGLD
ROST
ZBRA
BG
WCC
KD
EW
RMBS
MS
SHW
NI
KMB
STT
OSK
XYL
HXL
ITW
OZK
CB
PODD
PG
NVST
CMG
IRM
CNP
TER
ENTG
FLO
GL
TTD
BR
BA
ROK
CUZ
GT
BILL
AXON
MZTI
GPN
VLTO
VTR
CRUS
FIVE
KR
MSA
LYB
BX
ALGM
A
DHI
LUV
TMHC
CLX
ETN
CLF
BIIB
EXEL
SAM
AVT
VOYA
ECL
UHS
RMD
EXPO
LFUS
DVN
BK
M
TMO
SAIC
OKTA
LH
NKE
GATX
KRC
RGA
TROW
CR
BMY
ESS
GXO
ICE
WY
JEF
MP
WELL
FTI
IVZ
EMR
LIN
HUM
BKNG
ELF
FOXA
HAS
CI
TRMB
AEIS
GHC
CTVA
EQR
AGCO
GME
WM
NCLH
GBCI
HWM
AMG
DE
BCO
ONB
CASY
ABBV
HALO
DHR
WTW
ORCL
MAS
PFG
WMT
SNPS
ALV
NYT
FDS
MKSI
CCL
PRU
URI
TCBI
BRX
BF.B
EXC
WDAY
FFIN
AWK
CUBE
NTNX
CAG
NWE
ADC
APPF
DUK
SLGN
THO
CW
SPG
NEM
CSCO
PAG
APH
FSLR
VLY
DPZ
EXPD
PFGC
PSX
AVNT
CHE
OHI
HCA
CHRW
TGT
SPXC
ADM
NVR
BIO
QLYS
ELV
SO
DGX
ORA
AIZ
CVLT
NNN
NTRS

View File

@ -0,0 +1,310 @@
[
{
"ticker": "ALLY",
"name": "Ally Financial Inc.",
"market_cap_B": 13.1,
"trailing_pe": 17.85,
"forward_pe": 6.7,
"peg": null,
"revenue_growth_pct": 12.0,
"earnings_growth_pct": 265.4,
"roe_pct": 5.8,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Credit Services"
},
{
"ticker": "WAL",
"name": "Western Alliance Bancorporation",
"market_cap_B": 10.4,
"trailing_pe": 10.81,
"forward_pe": 7.93,
"peg": null,
"revenue_growth_pct": 16.6,
"earnings_growth_pct": 32.9,
"roe_pct": 13.5,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "CART",
"name": "Maplebear Inc.",
"market_cap_B": 9.1,
"trailing_pe": 19.03,
"forward_pe": 8.84,
"peg": null,
"revenue_growth_pct": 10.2,
"earnings_growth_pct": 21.1,
"roe_pct": 15.3,
"debt_to_equity": 1.0,
"sector": "Consumer Cyclical",
"industry": "Internet Retail"
},
{
"ticker": "ONB",
"name": "Old National Bancorp",
"market_cap_B": 10.1,
"trailing_pe": 14.46,
"forward_pe": 9.02,
"peg": null,
"revenue_growth_pct": 41.4,
"earnings_growth_pct": 17.2,
"roe_pct": 9.0,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "VLY",
"name": "Valley National Bancorp",
"market_cap_B": 7.6,
"trailing_pe": 13.57,
"forward_pe": 9.19,
"peg": null,
"revenue_growth_pct": 38.3,
"earnings_growth_pct": 66.3,
"roe_pct": 7.8,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "FSLR",
"name": "First Solar, Inc.",
"market_cap_B": 23.5,
"trailing_pe": 16.77,
"forward_pe": 9.4,
"peg": null,
"revenue_growth_pct": 79.7,
"earnings_growth_pct": 45.7,
"roe_pct": 16.9,
"debt_to_equity": 9.9,
"sector": "Technology",
"industry": "Solar"
},
{
"ticker": "FNB",
"name": "F.N.B. Corporation",
"market_cap_B": 6.8,
"trailing_pe": 12.12,
"forward_pe": 9.66,
"peg": null,
"revenue_growth_pct": 26.4,
"earnings_growth_pct": 56.5,
"roe_pct": 8.7,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "WBS",
"name": "Webster Financial Corporation",
"market_cap_B": 11.8,
"trailing_pe": 12.39,
"forward_pe": 9.77,
"peg": null,
"revenue_growth_pct": 18.2,
"earnings_growth_pct": 53.4,
"roe_pct": 10.8,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "ZION",
"name": "Zions Bancorporation, National Association",
"market_cap_B": 9.6,
"trailing_pe": 10.86,
"forward_pe": 9.99,
"peg": null,
"revenue_growth_pct": 13.6,
"earnings_growth_pct": 31.4,
"roe_pct": 13.5,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "JHG",
"name": "Janus Henderson Group plc",
"market_cap_B": 7.4,
"trailing_pe": 9.22,
"forward_pe": 10.12,
"peg": null,
"revenue_growth_pct": 61.3,
"earnings_growth_pct": 243.6,
"roe_pct": 16.2,
"debt_to_equity": 6.5,
"sector": "Financial Services",
"industry": "Asset Management"
},
{
"ticker": "SSB",
"name": "SouthState Bank Corporation",
"market_cap_B": 10.8,
"trailing_pe": 13.7,
"forward_pe": 10.19,
"peg": null,
"revenue_growth_pct": 53.2,
"earnings_growth_pct": 30.9,
"roe_pct": 10.7,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "PINS",
"name": "Pinterest, Inc.",
"market_cap_B": 13.3,
"trailing_pe": 6.88,
"forward_pe": 10.37,
"peg": null,
"revenue_growth_pct": 16.8,
"earnings_growth_pct": 225.0,
"roe_pct": 51.5,
"debt_to_equity": 4.3,
"sector": "Communication Services",
"industry": "Internet Content & Information"
},
{
"ticker": "RRC",
"name": "Range Resources Corporation",
"market_cap_B": 8.7,
"trailing_pe": 15.37,
"forward_pe": 10.45,
"peg": null,
"revenue_growth_pct": 16.1,
"earnings_growth_pct": 189.8,
"roe_pct": 14.2,
"debt_to_equity": 32.7,
"sector": "Energy",
"industry": "Oil & Gas E&P"
},
{
"ticker": "EWBC",
"name": "East West Bancorp, Inc.",
"market_cap_B": 16.9,
"trailing_pe": 12.87,
"forward_pe": 11.18,
"peg": null,
"revenue_growth_pct": 21.6,
"earnings_growth_pct": 21.3,
"roe_pct": 15.9,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "FHN",
"name": "First Horizon Corporation",
"market_cap_B": 12.9,
"trailing_pe": 14.03,
"forward_pe": 11.19,
"peg": null,
"revenue_growth_pct": 23.7,
"earnings_growth_pct": 74.9,
"roe_pct": 10.9,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "ORI",
"name": "Old Republic International Corporation",
"market_cap_B": 10.3,
"trailing_pe": 11.23,
"forward_pe": 11.99,
"peg": null,
"revenue_growth_pct": 19.3,
"earnings_growth_pct": 97.3,
"roe_pct": 16.3,
"debt_to_equity": 26.8,
"sector": "Financial Services",
"industry": "Insurance - Property & Casualty"
},
{
"ticker": "WTFC",
"name": "Wintrust Financial Corporation",
"market_cap_B": 10.8,
"trailing_pe": 14.14,
"forward_pe": 12.03,
"peg": null,
"revenue_growth_pct": 10.5,
"earnings_growth_pct": 19.4,
"roe_pct": 12.1,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "UBSI",
"name": "United Bankshares, Inc.",
"market_cap_B": 6.3,
"trailing_pe": 13.92,
"forward_pe": 12.08,
"peg": null,
"revenue_growth_pct": 22.1,
"earnings_growth_pct": 32.1,
"roe_pct": 8.9,
"debt_to_equity": null,
"sector": "Financial Services",
"industry": "Banks - Regional"
},
{
"ticker": "PGR",
"name": "The Progressive Corporation",
"market_cap_B": 118.6,
"trailing_pe": 10.51,
"forward_pe": 12.49,
"peg": null,
"revenue_growth_pct": 12.2,
"earnings_growth_pct": 25.2,
"roe_pct": 40.4,
"debt_to_equity": 22.7,
"sector": "Financial Services",
"industry": "Insurance - Property & Casualty"
},
{
"ticker": "EXEL",
"name": "Exelixis, Inc.",
"market_cap_B": 11.8,
"trailing_pe": 18.45,
"forward_pe": 12.79,
"peg": null,
"revenue_growth_pct": 10.8,
"earnings_growth_pct": 72.5,
"roe_pct": 30.6,
"debt_to_equity": 8.2,
"sector": "Healthcare",
"industry": "Biotechnology"
},
{
"ticker": "NEM",
"name": "Newmont Corporation",
"market_cap_B": 126.7,
"trailing_pe": 17.93,
"forward_pe": 12.89,
"peg": null,
"revenue_growth_pct": 20.0,
"earnings_growth_pct": 108.1,
"roe_pct": 22.9,
"debt_to_equity": 16.9,
"sector": "Basic Materials",
"industry": "Gold"
},
{
"ticker": "CTRA",
"name": "Coterra Energy Inc.",
"market_cap_B": 23.3,
"trailing_pe": 14.19,
"forward_pe": 13.95,
"peg": null,
"revenue_growth_pct": 34.9,
"earnings_growth_pct": 23.5,
"roe_pct": 11.9,
"debt_to_equity": 28.0,
"sector": "Energy",
"industry": "Oil & Gas E&P"
}
]

74
data/garp_scan.py Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""GARP scan on broad ticker list."""
import yfinance as yf
import json, time
EXCLUDE = {"BAC", "CFG", "FITB", "INCY"}
with open("broad_tickers.txt") as f:
tickers = [l.strip().replace(".", "-") for l in f if l.strip()]
print(f"Screening {len(tickers)} tickers...")
passed = []
for i, tick in enumerate(tickers):
if tick in EXCLUDE:
continue
if i % 100 == 0:
print(f" Progress: {i}/{len(tickers)} ({len(passed)} passed so far)")
try:
t = yf.Ticker(tick)
info = t.info or {}
mc = info.get("marketCap", 0) or 0
if mc < 5e9: continue
tpe = info.get("trailingPE")
if not tpe or tpe >= 25 or tpe <= 0: continue
fpe = info.get("forwardPE")
if not fpe or fpe >= 15 or fpe <= 0: continue
rg = info.get("revenueGrowth")
if rg is None or rg < 0.10: continue
eg = info.get("earningsGrowth")
if eg is None or eg < 0.15: continue
roe = info.get("returnOnEquity")
if roe is None or roe < 0.05: continue
peg = info.get("pegRatio")
if peg is not None and peg > 1.2: continue
dte = info.get("debtToEquity")
if dte is not None and dte > 35: continue
entry = {
"ticker": tick,
"name": info.get("longName", tick),
"market_cap_B": round(mc / 1e9, 1),
"trailing_pe": round(tpe, 2),
"forward_pe": round(fpe, 2),
"peg": round(peg, 2) if peg else None,
"revenue_growth_pct": round(rg * 100, 1),
"earnings_growth_pct": round(eg * 100, 1),
"roe_pct": round(roe * 100, 1),
"debt_to_equity": round(dte, 1) if dte else None,
"sector": info.get("sector"),
"industry": info.get("industry"),
}
passed.append(entry)
print(f"{tick}: PE={tpe:.1f} FPE={fpe:.1f} RG={rg*100:.0f}% EG={eg*100:.0f}% ROE={roe*100:.0f}%")
except Exception as e:
continue
passed.sort(key=lambda x: x.get("forward_pe", 99))
with open("garp-expanded-scan.json", "w") as f:
json.dump(passed, f, indent=2)
print(f"\nDone! {len(passed)} stocks passed GARP screen")
for s in passed:
print(f" {s['ticker']:6s} ${s['market_cap_B']:6.1f}B PE:{s['trailing_pe']:5.1f} FPE:{s['forward_pe']:5.1f} RG:{s['revenue_growth_pct']:5.1f}% EG:{s['earnings_growth_pct']:5.1f}% ROE:{s['roe_pct']:5.1f}%")

85
data/garp_scan2.py Normal file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""GARP scan - batch download approach for speed."""
import yfinance as yf
import json, sys
sys.stdout.reconfigure(line_buffering=True)
EXCLUDE = {"BAC", "CFG", "FITB", "INCY"}
with open("broad_tickers.txt") as f:
tickers = [l.strip().replace(".", "-") for l in f if l.strip()]
print(f"Screening {len(tickers)} tickers in batches...")
passed = []
batch_size = 20
for batch_start in range(0, len(tickers), batch_size):
batch = tickers[batch_start:batch_start + batch_size]
batch = [t for t in batch if t not in EXCLUDE]
if not batch:
continue
print(f"Batch {batch_start}-{batch_start+len(batch)} / {len(tickers)}...")
try:
data = yf.Tickers(" ".join(batch))
for tick in batch:
try:
info = data.tickers[tick].info or {}
mc = info.get("marketCap", 0) or 0
if mc < 5e9: continue
tpe = info.get("trailingPE")
if not tpe or tpe >= 25 or tpe <= 0: continue
fpe = info.get("forwardPE")
if not fpe or fpe >= 15 or fpe <= 0: continue
rg = info.get("revenueGrowth")
if rg is None or rg < 0.10: continue
eg = info.get("earningsGrowth")
if eg is None or eg < 0.15: continue
roe = info.get("returnOnEquity")
if roe is None or roe < 0.05: continue
peg = info.get("pegRatio")
if peg is not None and peg > 1.2: continue
dte = info.get("debtToEquity")
if dte is not None and dte > 35: continue
entry = {
"ticker": tick,
"name": info.get("longName", tick),
"market_cap_B": round(mc / 1e9, 1),
"trailing_pe": round(tpe, 2),
"forward_pe": round(fpe, 2),
"peg": round(peg, 2) if peg else None,
"revenue_growth_pct": round(rg * 100, 1),
"earnings_growth_pct": round(eg * 100, 1),
"roe_pct": round(roe * 100, 1),
"debt_to_equity": round(dte, 1) if dte else None,
"sector": info.get("sector"),
"industry": info.get("industry"),
}
passed.append(entry)
print(f"{tick}: PE={tpe:.1f} FPE={fpe:.1f} RG={rg*100:.0f}% EG={eg*100:.0f}% ROE={roe*100:.0f}%")
except:
continue
except Exception as e:
print(f" Batch error: {e}")
continue
passed.sort(key=lambda x: x.get("forward_pe", 99))
with open("garp-expanded-scan.json", "w") as f:
json.dump(passed, f, indent=2)
print(f"\nDone! {len(passed)} stocks passed GARP screen")
for s in passed:
print(f" {s['ticker']:6s} ${s['market_cap_B']:6.1f}B PE:{s['trailing_pe']:5.1f} FPE:{s['forward_pe']:5.1f} RG:{s['revenue_growth_pct']:5.1f}% EG:{s['earnings_growth_pct']:5.1f}% ROE:{s['roe_pct']:5.1f}%")

View File

@ -0,0 +1,762 @@
{
"BAC": {
"ticker": "BAC",
"name": "Bank of America Corporation",
"price_action": {
"current": 56.53,
"1yr_ago": 46.33,
"1yr_return_pct": 22.02,
"52wk_high": 57.25,
"52wk_low": 33.82,
"pct_from_52wk_high": -1.26,
"50d_ma": 54.25,
"200d_ma": 49.18,
"above_50d_ma": true,
"above_200d_ma": true
},
"insider_transactions": [
{
"date": "2026-01-15 00:00:00",
"insider": "MOYNIHAN BRIAN T",
"transaction": "",
"shares": "17891",
"value": "nan"
},
{
"date": "2025-12-15 00:00:00",
"insider": "MOYNIHAN BRIAN T",
"transaction": "",
"shares": "17892",
"value": "nan"
},
{
"date": "2025-12-11 00:00:00",
"insider": "MOYNIHAN BRIAN T",
"transaction": "",
"shares": "130000",
"value": "0.0"
},
{
"date": "2025-12-09 00:00:00",
"insider": "GREENER GEOFFREY S",
"transaction": "",
"shares": "9265",
"value": "0.0"
},
{
"date": "2025-12-03 00:00:00",
"insider": "SCRIVENER THOMAS M",
"transaction": "",
"shares": "3000",
"value": "0.0"
},
{
"date": "2025-11-28 00:00:00",
"insider": "OKPARA JOHNBULL",
"transaction": "",
"shares": "26784",
"value": "nan"
},
{
"date": "2025-11-14 00:00:00",
"insider": "MOYNIHAN BRIAN T",
"transaction": "",
"shares": "17891",
"value": "nan"
},
{
"date": "2025-11-14 00:00:00",
"insider": "SCHIMPF ERIC A",
"transaction": "",
"shares": "1234",
"value": "nan"
},
{
"date": "2025-11-14 00:00:00",
"insider": "HANS LINDSAY D",
"transaction": "",
"shares": "975",
"value": "nan"
},
{
"date": "2025-11-14 00:00:00",
"insider": "GOPALKRISHNAN HARI",
"transaction": "",
"shares": "2703",
"value": "nan"
}
],
"top_institutional_holders": [
{
"holder": "Vanguard Group Inc",
"shares": 651076825,
"pct_held": "0.0892"
},
{
"holder": "Berkshire Hathaway, Inc",
"shares": 568070012,
"pct_held": "0.077800006"
},
{
"holder": "Blackrock Inc.",
"shares": 535326028,
"pct_held": "0.0733"
},
{
"holder": "JPMORGAN CHASE & CO",
"shares": 363341282,
"pct_held": "0.0498"
},
{
"holder": "State Street Corporation",
"shares": 301312466,
"pct_held": "0.041300002"
}
],
"institutional_pct": 0.71739,
"earnings": [
{
"date": "2026-04-15 09:00:00-04:00",
"eps_estimate": 0.99,
"eps_actual": null,
"surprise_pct": null
},
{
"date": "2026-01-14 06:00:00-05:00",
"eps_estimate": 0.96,
"eps_actual": 0.98,
"surprise_pct": 2.23
},
{
"date": "2025-10-15 06:00:00-04:00",
"eps_estimate": 0.95,
"eps_actual": 1.06,
"surprise_pct": 11.43
},
{
"date": "2025-07-16 06:00:00-04:00",
"eps_estimate": 0.86,
"eps_actual": 0.89,
"surprise_pct": 3.61
},
{
"date": "2025-04-15 06:00:00-04:00",
"eps_estimate": 0.82,
"eps_actual": 0.9,
"surprise_pct": 10.29
},
{
"date": "2025-01-16 06:00:00-05:00",
"eps_estimate": 0.77,
"eps_actual": 0.82,
"surprise_pct": 6.84
},
{
"date": "2024-10-15 06:00:00-04:00",
"eps_estimate": 0.76,
"eps_actual": 0.81,
"surprise_pct": 6.34
},
{
"date": "2024-07-16 06:00:00-04:00",
"eps_estimate": 0.8,
"eps_actual": 0.83,
"surprise_pct": 3.58
}
],
"analyst": {
"target_mean": 62.20833,
"target_low": 56.0,
"target_high": 71.0,
"recommendation": "buy",
"num_analysts": 24,
"upside_pct": 10.04
},
"fundamentals": {
"trailing_pe": 14.83727,
"forward_pe": 11.407365,
"peg_ratio": null,
"market_cap": 412810084352,
"revenue_growth": 0.132,
"earnings_growth": 0.209,
"roe": 0.1019,
"debt_to_equity": null,
"dividend_yield": 1.95,
"beta": 1.273
},
"technical": {
"rsi_14": 71.14,
"trend": "bullish"
}
},
"CFG": {
"ticker": "CFG",
"name": "Citizens Financial Group, Inc.",
"price_action": {
"current": 68.12,
"1yr_ago": 46.24,
"1yr_return_pct": 47.32,
"52wk_high": 68.12,
"52wk_low": 33.06,
"pct_from_52wk_high": 0.0,
"50d_ma": 59.58,
"200d_ma": 49.69,
"above_50d_ma": true,
"above_200d_ma": true
},
"insider_transactions": [
{
"date": "2025-12-11 00:00:00",
"insider": "VAN SAUN BRUCE W",
"transaction": "",
"shares": "8000",
"value": "458558"
},
{
"date": "2025-12-03 00:00:00",
"insider": "LAMONICA SUSAN",
"transaction": "",
"shares": "20128",
"value": "1120727"
},
{
"date": "2025-11-12 00:00:00",
"insider": "KELLY EDWARD J. III",
"transaction": "",
"shares": "316",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "HANKOWSKY WILLIAM P",
"transaction": "",
"shares": "347",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "ZURAITIS MARITA",
"transaction": "",
"shares": "347",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "CUMMING CHRISTINE M",
"transaction": "",
"shares": "347",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "ATKINSON TRACY A",
"transaction": "",
"shares": "85",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "CUMMINGS KEVIN",
"transaction": "",
"shares": "162",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "SWIFT CHRISTOPHER J",
"transaction": "",
"shares": "200",
"value": "0"
},
{
"date": "2025-11-12 00:00:00",
"insider": "SIEKERKA MICHELE N",
"transaction": "",
"shares": "162",
"value": "0"
}
],
"top_institutional_holders": [
{
"holder": "Vanguard Group Inc",
"shares": 51303226,
"pct_held": "0.1195"
},
{
"holder": "Blackrock Inc.",
"shares": 43851199,
"pct_held": "0.1021"
},
{
"holder": "Capital World Investors",
"shares": 37289711,
"pct_held": "0.0868"
},
{
"holder": "Invesco Ltd.",
"shares": 24064513,
"pct_held": "0.055999998"
},
{
"holder": "State Street Corporation",
"shares": 22969833,
"pct_held": "0.0535"
}
],
"institutional_pct": 0.99170995,
"earnings": [
{
"date": "2026-04-16 09:00:00-04:00",
"eps_estimate": 1.09,
"eps_actual": null,
"surprise_pct": null
},
{
"date": "2026-01-21 06:00:00-05:00",
"eps_estimate": 1.11,
"eps_actual": 1.13,
"surprise_pct": 2.24
},
{
"date": "2025-10-15 06:00:00-04:00",
"eps_estimate": 1.03,
"eps_actual": 1.05,
"surprise_pct": 2.27
},
{
"date": "2025-07-17 06:00:00-04:00",
"eps_estimate": 0.88,
"eps_actual": 0.92,
"surprise_pct": 4.13
},
{
"date": "2025-04-16 06:00:00-04:00",
"eps_estimate": 0.75,
"eps_actual": 0.77,
"surprise_pct": 2.68
},
{
"date": "2025-01-17 06:00:00-05:00",
"eps_estimate": 0.83,
"eps_actual": 0.85,
"surprise_pct": 2.3
},
{
"date": "2024-10-16 06:00:00-04:00",
"eps_estimate": 0.79,
"eps_actual": 0.79,
"surprise_pct": -0.24
},
{
"date": "2024-07-17 03:00:00-04:00",
"eps_estimate": 0.79,
"eps_actual": 0.82,
"surprise_pct": 3.5
}
],
"analyst": {
"target_mean": 72.275,
"target_low": 62.5,
"target_high": 80.0,
"recommendation": "buy",
"num_analysts": 20,
"upside_pct": 6.1
},
"fundamentals": {
"trailing_pe": 17.647669,
"forward_pe": 10.848602,
"peg_ratio": null,
"market_cap": 29256599552,
"revenue_growth": 0.107,
"earnings_growth": 0.359,
"roe": 0.07241,
"debt_to_equity": null,
"dividend_yield": 2.7,
"beta": 1.071
},
"technical": {
"rsi_14": 75.46,
"trend": "bullish"
}
},
"FITB": {
"ticker": "FITB",
"name": "Fifth Third Bancorp",
"price_action": {
"current": 55.08,
"1yr_ago": 42.49,
"1yr_return_pct": 29.63,
"52wk_high": 55.08,
"52wk_low": 32.46,
"pct_from_52wk_high": 0.0,
"50d_ma": 48.27,
"200d_ma": 42.82,
"above_50d_ma": true,
"above_200d_ma": true
},
"insider_transactions": [
{
"date": "2026-02-02 00:00:00",
"insider": "SMITH BARBARA",
"transaction": "",
"shares": "40498",
"value": "0"
},
{
"date": "2026-02-02 00:00:00",
"insider": "SEFZIK PETER L",
"transaction": "",
"shares": "209382",
"value": "0"
},
{
"date": "2026-02-02 00:00:00",
"insider": "KERR DEREK J",
"transaction": "",
"shares": "14189",
"value": "0"
},
{
"date": "2026-02-02 00:00:00",
"insider": "VAN DE VEN MICHAEL G",
"transaction": "",
"shares": "47972",
"value": "0"
},
{
"date": "2026-01-07 00:00:00",
"insider": "ALMODOVAR PRISCILLA",
"transaction": "",
"shares": "848",
"value": "0"
},
{
"date": "2025-12-15 00:00:00",
"insider": "PRESTON BRYAN D",
"transaction": "",
"shares": "1100",
"value": "0"
},
{
"date": "2025-12-11 00:00:00",
"insider": "SCHRAMM JUDE",
"transaction": "",
"shares": "2250",
"value": "109125"
},
{
"date": "2025-10-20 00:00:00",
"insider": "BAYH B EVAN III",
"transaction": "",
"shares": "3000",
"value": "123650"
},
{
"date": "2025-10-09 00:00:00",
"insider": "GONZALEZ CHRISTIAN",
"transaction": "",
"shares": "5709",
"value": "0"
},
{
"date": "2025-08-28 00:00:00",
"insider": "SHAFFER ROBERT P",
"transaction": "",
"shares": "14035",
"value": "372208"
}
],
"top_institutional_holders": [
{
"holder": "Vanguard Group Inc",
"shares": 107237083,
"pct_held": "0.16219999"
},
{
"holder": "Blackrock Inc.",
"shares": 88421716,
"pct_held": "0.1338"
},
{
"holder": "JPMORGAN CHASE & CO",
"shares": 65878286,
"pct_held": "0.099700004"
},
{
"holder": "State Street Corporation",
"shares": 39653081,
"pct_held": "0.06"
},
{
"holder": "Charles Schwab Investment Management, Inc.",
"shares": 32138466,
"pct_held": "0.048600003"
}
],
"institutional_pct": 0.66008,
"earnings": [
{
"date": "2026-04-17 09:00:00-04:00",
"eps_estimate": 0.43,
"eps_actual": null,
"surprise_pct": null
},
{
"date": "2026-01-20 06:00:00-05:00",
"eps_estimate": 0.99,
"eps_actual": 1.04,
"surprise_pct": 4.93
},
{
"date": "2025-10-17 06:00:00-04:00",
"eps_estimate": 0.86,
"eps_actual": 0.91,
"surprise_pct": 5.47
},
{
"date": "2025-07-17 06:00:00-04:00",
"eps_estimate": 0.87,
"eps_actual": 0.9,
"surprise_pct": 3.85
},
{
"date": "2025-04-17 06:00:00-04:00",
"eps_estimate": 0.7,
"eps_actual": 0.71,
"surprise_pct": 1.42
},
{
"date": "2025-01-21 06:00:00-05:00",
"eps_estimate": 0.87,
"eps_actual": 0.9,
"surprise_pct": 3.01
},
{
"date": "2024-10-18 06:00:00-04:00",
"eps_estimate": 0.82,
"eps_actual": 0.85,
"surprise_pct": 3.17
},
{
"date": "2024-07-19 06:00:00-04:00",
"eps_estimate": 0.84,
"eps_actual": 0.86,
"surprise_pct": 1.79
}
],
"analyst": {
"target_mean": 57.15789,
"target_low": 49.0,
"target_high": 61.0,
"recommendation": "buy",
"num_analysts": 19,
"upside_pct": 3.77
},
"fundamentals": {
"trailing_pe": 15.6034,
"forward_pe": 11.235359,
"peg_ratio": null,
"market_cap": 49574670336,
"revenue_growth": 0.115,
"earnings_growth": 0.208,
"roe": 0.121929996,
"debt_to_equity": null,
"dividend_yield": 2.9,
"beta": 0.977
},
"technical": {
"rsi_14": 71.83,
"trend": "bullish"
}
},
"INCY": {
"ticker": "INCY",
"name": "Incyte Corporation",
"price_action": {
"current": 108.39,
"1yr_ago": 74.13,
"1yr_return_pct": 46.22,
"52wk_high": 110.57,
"52wk_low": 55.17,
"pct_from_52wk_high": -1.97,
"50d_ma": 101.9,
"200d_ma": 84.49,
"above_50d_ma": true,
"above_200d_ma": true
},
"insider_transactions": [
{
"date": "2026-01-16 00:00:00",
"insider": "MORRISSEY MICHAEL JAMES",
"transaction": "",
"shares": "7426",
"value": "0"
},
{
"date": "2026-01-16 00:00:00",
"insider": "HEESON LEE",
"transaction": "",
"shares": "8911",
"value": "0"
},
{
"date": "2026-01-07 00:00:00",
"insider": "ISSA MOHAMED KHAIRIE",
"transaction": "",
"shares": "10856",
"value": "1184064"
},
{
"date": "2026-01-05 00:00:00",
"insider": "STEIN STEVEN H.",
"transaction": "",
"shares": "15634",
"value": "1589978"
},
{
"date": "2025-12-31 00:00:00",
"insider": "BAKER BROS ADVISORS L.P.",
"transaction": "",
"shares": "656",
"value": "0"
},
{
"date": "2025-12-31 00:00:00",
"insider": "HARRIGAN EDMUND P. M.D.",
"transaction": "",
"shares": "245",
"value": "24199"
},
{
"date": "2025-12-31 00:00:00",
"insider": "CLANCY PAUL J.",
"transaction": "",
"shares": "241",
"value": "23804"
},
{
"date": "2025-12-19 00:00:00",
"insider": "TRAY THOMAS R",
"transaction": "",
"shares": "3374",
"value": "336350"
},
{
"date": "2025-12-19 00:00:00",
"insider": "TRAY THOMAS R",
"transaction": "",
"shares": "2774",
"value": "265638"
},
{
"date": "2025-12-17 00:00:00",
"insider": "MORRISSEY MICHAEL JAMES",
"transaction": "",
"shares": "58331",
"value": "5675002"
}
],
"top_institutional_holders": [
{
"holder": "Baker Bros. Advisors, LP",
"shares": 30743663,
"pct_held": "0.1566"
},
{
"holder": "Vanguard Group Inc",
"shares": 19911434,
"pct_held": "0.1014"
},
{
"holder": "Blackrock Inc.",
"shares": 17894297,
"pct_held": "0.0911"
},
{
"holder": "Dodge & Cox Inc.",
"shares": 13932416,
"pct_held": "0.071"
},
{
"holder": "State Street Corporation",
"shares": 9676796,
"pct_held": "0.0493"
}
],
"institutional_pct": 1.05931,
"earnings": [
{
"date": "2026-02-10 08:00:00-05:00",
"eps_estimate": 1.95,
"eps_actual": null,
"surprise_pct": null
},
{
"date": "2025-10-28 07:00:00-04:00",
"eps_estimate": 1.64,
"eps_actual": 2.26,
"surprise_pct": 38.04
},
{
"date": "2025-07-29 07:00:00-04:00",
"eps_estimate": 1.47,
"eps_actual": 1.57,
"surprise_pct": 6.59
},
{
"date": "2025-04-29 07:00:00-04:00",
"eps_estimate": 1.03,
"eps_actual": 1.16,
"surprise_pct": 12.32
},
{
"date": "2025-02-10 07:00:00-05:00",
"eps_estimate": 1.55,
"eps_actual": 1.43,
"surprise_pct": -8.01
},
{
"date": "2024-10-29 07:00:00-04:00",
"eps_estimate": 1.09,
"eps_actual": 1.07,
"surprise_pct": -2.03
},
{
"date": "2024-07-30 07:00:00-04:00",
"eps_estimate": 1.11,
"eps_actual": -1.82,
"surprise_pct": -264.53
},
{
"date": "2024-04-30 07:00:00-04:00",
"eps_estimate": 0.83,
"eps_actual": 0.64,
"surprise_pct": -23.09
}
],
"analyst": {
"target_mean": 104.22727,
"target_low": 70.0,
"target_high": 135.0,
"recommendation": "buy",
"num_analysts": 22,
"upside_pct": -3.84
},
"fundamentals": {
"trailing_pe": 18.371185,
"forward_pe": 13.606971,
"peg_ratio": null,
"market_cap": 21279418368,
"revenue_growth": 0.2,
"earnings_growth": 2.907,
"roe": 0.30389,
"debt_to_equity": 0.887,
"dividend_yield": null,
"beta": 0.847
},
"technical": {
"rsi_14": 54.22,
"trend": "bullish"
}
}
}

View File

@ -0,0 +1,71 @@
{
"date": "2026-02-08",
"filters": "GARP",
"results": [
{
"ticker": "BAC",
"name": "Bank of America Corporation",
"mcap_b": 412.8,
"rev_growth": 13.2,
"trail_pe": 14.8,
"fwd_pe": 11.4,
"peg": "N/A",
"eps_growth": 20.9,
"roe": 10.2,
"quick": "N/A",
"de": "N/A"
},
{
"ticker": "CFG",
"name": "Citizens Financial Group, Inc.",
"mcap_b": 29.3,
"rev_growth": 10.7,
"trail_pe": 17.6,
"fwd_pe": 10.8,
"peg": "N/A",
"eps_growth": 35.9,
"roe": 7.2,
"quick": "N/A",
"de": "N/A"
},
{
"ticker": "FITB",
"name": "Fifth Third Bancorp",
"mcap_b": 49.6,
"rev_growth": 11.5,
"trail_pe": 15.6,
"fwd_pe": 11.2,
"peg": "N/A",
"eps_growth": 20.8,
"roe": 12.2,
"quick": "N/A",
"de": "N/A"
},
{
"ticker": "INCY",
"name": "Incyte Corporation",
"mcap_b": 21.3,
"rev_growth": 20.0,
"trail_pe": 18.4,
"fwd_pe": 13.6,
"peg": "N/A",
"eps_growth": 290.7,
"roe": 30.4,
"quick": 2.86,
"de": 0.9
}
],
"stats": {
"scanned": 503,
"revenue": 316,
"pe": 121,
"fwd_pe": 32,
"peg": 0,
"eps": 14,
"roe": 1,
"quick": 11,
"de": 4,
"mcap": 0,
"err": 0
}
}

295
data/stock_deep_dive.py Normal file
View File

@ -0,0 +1,295 @@
#!/usr/bin/env python3
"""Deep dive analysis on BAC, CFG, FITB, INCY + expanded GARP scan."""
import yfinance as yf
import json
import datetime
import numpy as np
from pathlib import Path
TARGETS = ["BAC", "CFG", "FITB", "INCY"]
def safe_get(d, *keys, default=None):
for k in keys:
if isinstance(d, dict):
d = d.get(k, default)
else:
return default
return d
def analyze_stock(ticker_str):
print(f"\n{'='*60}\nAnalyzing {ticker_str}...")
t = yf.Ticker(ticker_str)
info = t.info or {}
result = {"ticker": ticker_str, "name": info.get("longName", ticker_str)}
# 1. Price action (1yr)
hist = t.history(period="1y")
if not hist.empty:
prices = hist["Close"]
result["price_action"] = {
"current": round(float(prices.iloc[-1]), 2),
"1yr_ago": round(float(prices.iloc[0]), 2),
"1yr_return_pct": round(float((prices.iloc[-1] / prices.iloc[0] - 1) * 100), 2),
"52wk_high": round(float(prices.max()), 2),
"52wk_low": round(float(prices.min()), 2),
"pct_from_52wk_high": round(float((prices.iloc[-1] / prices.max() - 1) * 100), 2),
"50d_ma": round(float(prices.tail(50).mean()), 2),
"200d_ma": round(float(prices.tail(200).mean()), 2) if len(prices) >= 200 else None,
}
cur = float(prices.iloc[-1])
ma50 = result["price_action"]["50d_ma"]
ma200 = result["price_action"]["200d_ma"]
result["price_action"]["above_50d_ma"] = cur > ma50
result["price_action"]["above_200d_ma"] = cur > ma200 if ma200 else None
# 2. Insider activity
try:
insiders = t.insider_transactions
if insiders is not None and not insiders.empty:
recent = insiders.head(10)
txns = []
for _, row in recent.iterrows():
txns.append({
"date": str(row.get("Start Date", "")),
"insider": str(row.get("Insider", "")),
"transaction": str(row.get("Transaction", "")),
"shares": str(row.get("Shares", "")),
"value": str(row.get("Value", "")),
})
result["insider_transactions"] = txns
else:
result["insider_transactions"] = []
except:
result["insider_transactions"] = []
# 3. Institutional ownership
try:
inst = t.institutional_holders
if inst is not None and not inst.empty:
top5 = []
for _, row in inst.head(5).iterrows():
top5.append({
"holder": str(row.get("Holder", "")),
"shares": int(row.get("Shares", 0)) if row.get("Shares") else 0,
"pct_held": str(row.get("pctHeld", "")),
})
result["top_institutional_holders"] = top5
result["institutional_pct"] = info.get("heldPercentInstitutions")
except:
result["top_institutional_holders"] = []
# 4. Earnings surprises
try:
earn = t.earnings_dates
if earn is not None and not earn.empty:
surprises = []
for idx, row in earn.head(8).iterrows():
s = {
"date": str(idx),
"eps_estimate": row.get("EPS Estimate"),
"eps_actual": row.get("Reported EPS"),
"surprise_pct": row.get("Surprise(%)"),
}
# clean NaN
s = {k: (None if isinstance(v, float) and np.isnan(v) else v) for k, v in s.items()}
surprises.append(s)
result["earnings"] = surprises
else:
result["earnings"] = []
except:
result["earnings"] = []
# 5. Analyst consensus
result["analyst"] = {
"target_mean": info.get("targetMeanPrice"),
"target_low": info.get("targetLowPrice"),
"target_high": info.get("targetHighPrice"),
"recommendation": info.get("recommendationKey"),
"num_analysts": info.get("numberOfAnalystOpinions"),
}
if result["price_action"].get("current") and info.get("targetMeanPrice"):
upside = (info["targetMeanPrice"] / result["price_action"]["current"] - 1) * 100
result["analyst"]["upside_pct"] = round(upside, 2)
# 6. Key fundamentals snapshot
result["fundamentals"] = {
"trailing_pe": info.get("trailingPE"),
"forward_pe": info.get("forwardPE"),
"peg_ratio": info.get("pegRatio"),
"market_cap": info.get("marketCap"),
"revenue_growth": info.get("revenueGrowth"),
"earnings_growth": info.get("earningsGrowth"),
"roe": info.get("returnOnEquity"),
"debt_to_equity": info.get("debtToEquity"),
"dividend_yield": info.get("dividendYield"),
"beta": info.get("beta"),
}
# 7. Technical - RSI approximation
if not hist.empty and len(hist) >= 14:
delta = hist["Close"].diff()
gain = delta.where(delta > 0, 0).rolling(14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
result["technical"] = {
"rsi_14": round(float(rsi.iloc[-1]), 2) if not np.isnan(rsi.iloc[-1]) else None,
"trend": "bullish" if cur > ma50 and (ma200 is None or cur > ma200) else "bearish" if cur < ma50 and (ma200 and cur < ma200) else "mixed",
}
print(f" Done: {result['name']} @ ${result['price_action']['current']}")
return result
# ============================================================
# EXPANDED GARP SCAN
# ============================================================
def get_broad_ticker_list():
"""Get a broad list of US tickers to screen."""
import urllib.request
# Use Wikipedia's S&P 500 + S&P 400 midcap for broader coverage
# Plus some known Russell 1000 members not in S&P 500
# Start with S&P 500
sp500 = []
try:
import pandas as pd
tables = pd.read_html("https://en.wikipedia.org/wiki/List_of_S%26P_500_companies")
sp500 = tables[0]["Symbol"].str.replace(".", "-", regex=False).tolist()
except:
pass
# S&P 400 midcap
sp400 = []
try:
import pandas as pd
tables = pd.read_html("https://en.wikipedia.org/wiki/List_of_S%26P_400_companies")
sp400 = tables[0]["Symbol"].str.replace(".", "-", regex=False).tolist()
except:
pass
all_tickers = list(set(sp500 + sp400))
print(f"Total tickers to screen: {len(all_tickers)}")
return all_tickers
def garp_screen(tickers, exclude=None):
"""Apply GARP filters to ticker list."""
exclude = set(exclude or [])
passed = []
for i, tick in enumerate(tickers):
if tick in exclude:
continue
if i % 50 == 0:
print(f" Screening {i}/{len(tickers)}...")
try:
t = yf.Ticker(tick)
info = t.info or {}
mc = info.get("marketCap", 0) or 0
if mc < 5e9:
continue
tpe = info.get("trailingPE")
if tpe is None or tpe >= 25 or tpe <= 0:
continue
fpe = info.get("forwardPE")
if fpe is None or fpe >= 15 or fpe <= 0:
continue
rg = info.get("revenueGrowth")
if rg is None or rg < 0.10:
continue
eg = info.get("earningsGrowth")
if eg is None or eg < 0.15:
continue
roe = info.get("returnOnEquity")
if roe is None or roe < 0.05:
continue
# Optional filters
peg = info.get("pegRatio")
if peg is not None and peg > 1.2:
continue
dte = info.get("debtToEquity")
if dte is not None and dte > 35:
continue
passed.append({
"ticker": tick,
"name": info.get("longName", tick),
"market_cap": mc,
"trailing_pe": round(tpe, 2),
"forward_pe": round(fpe, 2),
"peg": round(peg, 2) if peg else None,
"revenue_growth": round(rg * 100, 1),
"earnings_growth": round(eg * 100, 1),
"roe": round(roe * 100, 1),
"debt_to_equity": round(dte, 1) if dte else None,
})
print(f" ✅ PASS: {tick} (PE:{tpe:.1f} FPE:{fpe:.1f} RG:{rg*100:.0f}% EG:{eg*100:.0f}%)")
except Exception as e:
continue
return passed
# ============================================================
# MAIN
# ============================================================
if __name__ == "__main__":
output_dir = Path("/home/wdjones/.openclaw/workspace/data")
output_dir.mkdir(exist_ok=True)
# Deep dive on 4 stocks
print("=" * 60)
print("DEEP DIVE ANALYSIS")
print("=" * 60)
analyses = {}
for tick in TARGETS:
analyses[tick] = analyze_stock(tick)
# Save deep dive
with open(output_dir / "stock-analysis-deep-dive.json", "w") as f:
json.dump(analyses, f, indent=2, default=str)
print(f"\nSaved deep dive to stock-analysis-deep-dive.json")
# Expanded GARP scan
print("\n" + "=" * 60)
print("EXPANDED GARP SCAN (S&P 500 + S&P 400 MidCap)")
print("=" * 60)
tickers = get_broad_ticker_list()
new_passes = garp_screen(tickers, exclude=set(TARGETS))
with open(output_dir / "garp-expanded-scan.json", "w") as f:
json.dump(new_passes, f, indent=2, default=str)
print(f"\nExpanded scan found {len(new_passes)} additional stocks")
print("Saved to garp-expanded-scan.json")
# Print summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
for tick, data in analyses.items():
pa = data.get("price_action", {})
an = data.get("analyst", {})
fu = data.get("fundamentals", {})
te = data.get("technical", {})
print(f"\n{tick} - {data['name']}")
print(f" Price: ${pa.get('current')} | 1yr Return: {pa.get('1yr_return_pct')}%")
print(f" From 52wk High: {pa.get('pct_from_52wk_high')}%")
print(f" PE: {fu.get('trailing_pe')} | Fwd PE: {fu.get('forward_pe')} | PEG: {fu.get('peg_ratio')}")
print(f" Target: ${an.get('target_mean')} ({an.get('upside_pct')}% upside) | Rec: {an.get('recommendation')}")
print(f" RSI: {te.get('rsi_14')} | Trend: {te.get('trend')}")
print(f" Insiders: {len(data.get('insider_transactions', []))} recent txns")
if new_passes:
print(f"\nNew GARP Candidates ({len(new_passes)}):")
for s in sorted(new_passes, key=lambda x: x.get("forward_pe", 99)):
print(f" {s['ticker']:6s} PE:{s['trailing_pe']:5.1f} FPE:{s['forward_pe']:5.1f} RG:{s['revenue_growth']:5.1f}% EG:{s['earnings_growth']:5.1f}% ROE:{s['roe']:5.1f}%")