dromero-nttd commited on
Commit
f89d1ea
·
1 Parent(s): 0b483ce

feat: Establish initial Munger Engine framework with functional requirements, S&P 500 data fetching, and core stock analysis scripts.

Browse files
.gitignore CHANGED
@@ -39,3 +39,4 @@ yarn-error.log*
39
  # typescript
40
  *.tsbuildinfo
41
  next-env.d.ts
 
 
39
  # typescript
40
  *.tsbuildinfo
41
  next-env.d.ts
42
+
funct_reqs.md ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Functional Requirements Document: "The Munger Engine" (Unified TS Stack)
2
+
3
+ **Project Goal:** Automated paper-trading dashboard that executes a 200-Week Moving Average (WMA) strategy on high-quality stocks.
4
+ **Architecture:** Unified TypeScript Stack (Next.js + Node.js + Alpaca SDK).
5
+
6
+ ---
7
+
8
+ ## 1. System Architecture Overview
9
+
10
+ - **Core:** Next.js 14/15 (App Router) using Server Actions and API Routes.
11
+ - **Engine:** Automated logic running via Vercel Cron or GitHub Actions (Node.js).
12
+ - **Execution:** Alpaca Trade API (Node SDK) for paper trading.
13
+ - **Data:** `yahoo-finance2` for fundamental and technical metrics.
14
+
15
+ ---
16
+
17
+ ## 2. Technical Stack Specifications
18
+
19
+ - **Language:** TypeScript (Strict Mode).
20
+ - **Frontend:** Tailwind CSS, Lucide React (Icons), Shadcn/UI (Components).
21
+ - **Backend:** Next.js Route Handlers (Serverless Functions).
22
+ - **External APIs:**
23
+ - `@alpacahq/alpaca-trade-api` (Execution).
24
+ - `yahoo-finance2` (Market Data).
25
+
26
+ ---
27
+
28
+ ## 3. Backend Logic & Strategy (TypeScript)
29
+
30
+ ### 3.1 The "Forever Stock" Filter
31
+
32
+ Every asset in the watchlist must be validated against these fundamental criteria:
33
+
34
+ - **Return on Equity (ROE):** > 15% (Source: `quoteSummary.financialData`).
35
+ - **Debt-to-Equity:** < 50 (Source: `quoteSummary.financialData`).
36
+ - **Market Cap:** Filter for "Large Cap" (> $10B).
37
+
38
+ ### 3.2 Strategy Execution Logic
39
+
40
+ - **Buy Trigger:** 1. Calculate $SMA_{200}$ using the last 200 weekly closing prices.
41
+ 2. Condition: $CurrentPrice \leq SMA_{200} \times 1.05$ (within 5% of the average).
42
+ 3. Action: Execute `buy` order via Alpaca.
43
+ - **Sell Trigger:**
44
+ 1. **Trend Death:** $CurrentPrice < SMA_{200} \times 0.90$ (10% breakdown).
45
+ 2. **Fundamental Decay:** $ROE < 10\%$ or $Debt/Equity > 100$.
46
+ 3. Action: Execute `liquidate` order via Alpaca.
47
+
48
+ ---
49
+
50
+ ## 4. Frontend Requirements (Next.js Dashboard)
51
+
52
+ ### 4.1 Component: Opportunity Heatmap
53
+
54
+ - **Visual Goal:** Show distance from the "Munger Entry Point".
55
+ - **Logic:** Calculate $(\frac{Price - SMA_{200}}{SMA_{200}}) \times 100$.
56
+ - **UI:** Color-coded cards (Green: < 3% distance, Yellow: 3-8%, Grey: > 8%).
57
+
58
+ ### 4.2 Component: Discipline Tracker
59
+
60
+ - **Visual Goal:** Gamify "Wait-and-See" investing.
61
+ - **Logic:** Days since `last_trade_event`.
62
+ - **UI:** Counter "Days of Discipline" with a progress bar towards a "Munger Badge".
63
+
64
+ ### 4.3 Component: Quality Scorecards
65
+
66
+ - **Visual Goal:** Display the "Why" behind the holding.
67
+ - **Logic:** Real-time fetch of ROE, Debt/Equity, and Net Profit Margin.
68
+ - **UI:** Dashboard cards with a "Strategy Compliance" status (e.g., "Active Support" or "Risk: Fundamental Decay").
69
+
70
+ ---
71
+
72
+ ## 5. Workflow & Scheduling
73
+
74
+ 1. **The Sync:** A Cron Job calls `/api/strategy/execute` every Sunday before market open.
75
+ 2. **The Scan:** The handler iterates through the `WATCHLIST`, fetches Yahoo Finance data, and compares it with Alpaca positions.
76
+ 3. **The Trade:** If signals match, the Alpaca SDK sends orders.
77
+ 4. **The Log:** All results are stored in a database (Supabase/Prisma) or a `state.json` for the frontend.
78
+
79
+ ---
80
+
81
+ ## 6. Safety Guardrails
82
+
83
+ - **Paper Trading Only:** The `paper` flag in the Alpaca client must be hardcoded to `true`.
84
+ - **Max Position Size:** No single stock should exceed 10% of the paper account's total buying power.
85
+ - **Error Handling:** Use `Zod` for validating Yahoo Finance responses to prevent engine crashes on missing data.
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -9,18 +9,23 @@
9
  "lint": "eslint"
10
  },
11
  "dependencies": {
 
 
 
12
  "next": "16.1.2",
13
  "react": "19.2.3",
14
- "react-dom": "19.2.3"
 
15
  },
16
  "devDependencies": {
17
  "@tailwindcss/postcss": "^4",
18
- "@types/node": "^20",
19
  "@types/react": "^19",
20
  "@types/react-dom": "^19",
21
  "eslint": "^9",
22
  "eslint-config-next": "16.1.2",
23
  "tailwindcss": "^4",
 
24
  "typescript": "^5"
25
  }
26
  }
 
9
  "lint": "eslint"
10
  },
11
  "dependencies": {
12
+ "@alpacahq/alpaca-trade-api": "^3.1.3",
13
+ "cheerio": "^1.1.2",
14
+ "dotenv": "^17.2.3",
15
  "next": "16.1.2",
16
  "react": "19.2.3",
17
+ "react-dom": "19.2.3",
18
+ "yahoo-finance2": "^3.11.2"
19
  },
20
  "devDependencies": {
21
  "@tailwindcss/postcss": "^4",
22
+ "@types/node": "^20.19.30",
23
  "@types/react": "^19",
24
  "@types/react-dom": "^19",
25
  "eslint": "^9",
26
  "eslint-config-next": "16.1.2",
27
  "tailwindcss": "^4",
28
+ "tsx": "^4.21.0",
29
  "typescript": "^5"
30
  }
31
  }
scripts/debug_yf.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yahooFinance from 'yahoo-finance2';
2
+
3
+ console.log('Type:', typeof yahooFinance);
4
+ console.log('Value:', yahooFinance);
5
+
6
+ try {
7
+ const result = await yahooFinance.quote('AAPL');
8
+ console.log('Success:', result);
9
+ } catch(e) {
10
+ console.error('Error:', e.message);
11
+ }
scripts/fetch_sp500.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as cheerio from 'cheerio';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ async function getSP500FromWiki() {
6
+ const url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies';
7
+ console.log('Fetching S&P 500 from:', url);
8
+
9
+ try {
10
+ const res = await fetch(url);
11
+ if (!res.ok) throw new Error(`Status: ${res.status}`);
12
+
13
+ const html = await res.text();
14
+ const $ = cheerio.load(html);
15
+ const symbols: string[] = [];
16
+
17
+ // The first table usually has id="constituents"
18
+ const table = $('#constituents');
19
+
20
+ if (table.length === 0) {
21
+ throw new Error('Could not find constituents table on Wikipedia page.');
22
+ }
23
+
24
+ // Iterate over rows in the tbody
25
+ table.find('tbody tr').each((i, row) => {
26
+ // Skip header row if it's th
27
+ const firstCell = $(row).find('td').first();
28
+ let symbol = firstCell.text().trim();
29
+
30
+ // Yahoo Finance prefers dashes for classes like BRK.B -> BRK-B
31
+ if (symbol) {
32
+ symbol = symbol.replace(/\./g, '-');
33
+ symbols.push(symbol);
34
+ }
35
+ });
36
+
37
+ console.log(`Successfully extracted ${symbols.length} symbols.`);
38
+ // Sample
39
+ console.log(symbols.slice(0, 5));
40
+
41
+ fs.writeFileSync(path.resolve(__dirname, 'sp500_symbols.json'), JSON.stringify(symbols, null, 2));
42
+ console.log('Saved to scripts/sp500_symbols.json');
43
+
44
+ } catch (e: any) {
45
+ console.error('Scrape failed:', e.message);
46
+ }
47
+ }
48
+
49
+ getSP500FromWiki();
scripts/find_forever_stocks.ts ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import YahooFinance from 'yahoo-finance2';
2
+ import dotenv from 'dotenv';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+
6
+ dotenv.config({ path: path.resolve(__dirname, '../.env') });
7
+ const yahooFinance = new (YahooFinance as any)();
8
+
9
+ // Load Watchlist from JSON
10
+ let WATCHLIST: string[] = [];
11
+ try {
12
+ const jsonPath = path.resolve(__dirname, 'sp500_symbols.json');
13
+ if (fs.existsSync(jsonPath)) {
14
+ WATCHLIST = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
15
+ console.log(`Loaded ${WATCHLIST.length} symbols from S&P 500 list.`);
16
+ } else {
17
+ console.warn("Warning: sp500_symbols.json not found. Using fallback list.");
18
+ // Fallback
19
+ WATCHLIST = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'BRK-B', 'V', 'JNJ', 'WMT', 'PG'];
20
+ }
21
+ } catch (e) {
22
+ console.error("Error loading watchlist:", e);
23
+ }
24
+
25
+ function getLastSunday(): Date {
26
+ const d = new Date();
27
+ const day = d.getDay();
28
+ const diff = d.getDate() - day;
29
+ const sunday = new Date(d.setDate(diff));
30
+ sunday.setHours(23, 59, 59, 999);
31
+ return sunday;
32
+ }
33
+
34
+ interface StrategyResult {
35
+ symbol: string;
36
+ price: number;
37
+ wma200: number;
38
+ roe: number;
39
+ debtToEquity: number;
40
+ isQuality: boolean;
41
+ lastCandleColor: 'GREEN' | 'RED';
42
+ touchedWMA: boolean;
43
+ signal: 'BUY_TRIGGER' | 'WATCHLIST' | 'IGNORE' | 'WAIT';
44
+ details: string;
45
+ }
46
+
47
+ async function deepDive(symbol: string) {
48
+ console.log(`\n🔍 DEEP DIVE ANALYSIS: ${symbol}`);
49
+ console.log('================================================');
50
+
51
+ try {
52
+ const result = await yahooFinance.quoteSummary(symbol, {
53
+ modules: [
54
+ 'financialData',
55
+ 'defaultKeyStatistics',
56
+ 'recommendationTrend',
57
+ 'earnings',
58
+ 'summaryDetail'
59
+ ]
60
+ });
61
+
62
+ const fin = result.financialData;
63
+ const stats = result.defaultKeyStatistics;
64
+ const summary = result.summaryDetail;
65
+ const recs = result.recommendationTrend?.trend?.[0];
66
+
67
+ console.log('💰 VALUATION');
68
+ console.log(` • Current Price: $${fin?.currentPrice}`);
69
+ console.log(` • Target Mean: $${fin?.targetMeanPrice}`);
70
+ console.log(` • Forward PE: ${summary?.forwardPE?.toFixed(2)}`);
71
+ console.log(` • PEG Ratio: ${stats?.pegRatio}`);
72
+ console.log(` • Price/Book: ${stats?.priceToBook?.toFixed(2)}`);
73
+
74
+ console.log('\n🏰 ECONOMIC MOAT (Profitability)');
75
+ console.log(` • Gross Margin: ${(fin?.grossMargins * 100).toFixed(2)}%`);
76
+ console.log(` • Oper. Margin: ${(fin?.operatingMargins * 100).toFixed(2)}%`);
77
+ console.log(` • ROE: ${(fin?.returnOnEquity * 100).toFixed(2)}%`);
78
+ console.log(` • ROA: ${(fin?.returnOnAssets * 100).toFixed(2)}%`);
79
+
80
+ console.log('\n🛡️ FINANCIAL HEALTH');
81
+ console.log(` • Total Cash: $${(fin?.totalCash / 1e9).toFixed(2)}B`);
82
+ console.log(` • Total Debt: $${(fin?.totalDebt / 1e9).toFixed(2)}B`);
83
+ console.log(` • Debt/Equity: ${fin?.debtToEquity}%`);
84
+ console.log(` • Current Ratio: ${fin?.currentRatio}`);
85
+ console.log(` • Free Cash Flow: $${(fin?.freeCashflow / 1e9).toFixed(2)}B`);
86
+
87
+ console.log('\n🧠 SENTIMENT & GROWTH');
88
+ console.log(` • Analyst Rec: ${fin?.recommendationKey?.toUpperCase()} (Buy: ${recs?.buy}, Hold: ${recs?.hold})`);
89
+ console.log(` • Short Float: ${(stats?.shortPercentOfFloat * 100).toFixed(2)}%`);
90
+ console.log('================================================\n');
91
+
92
+ } catch (e: any) {
93
+ console.error("Deep dive failed:", e.message);
94
+ }
95
+ }
96
+
97
+ async function analyzeStock(symbol: string, refDate: Date): Promise<StrategyResult | null> {
98
+ try {
99
+ const queryOptions = { period1: '2019-01-01', period2: refDate, interval: '1wk' as const };
100
+ const history = await yahooFinance.chart(symbol, queryOptions);
101
+ const quotes = history.quotes.filter((q: any) => q.close && q.open);
102
+
103
+ if (quotes.length < 205) return null;
104
+
105
+ const latestIndex = quotes.length - 1;
106
+ const currentPrice = quotes[latestIndex].close as number;
107
+
108
+ // Simple MA 200 Calculation
109
+ const calculateMA200 = (endIndex: number) => {
110
+ if (endIndex < 199) return 0;
111
+ const slice = quotes.slice(endIndex - 199, endIndex + 1).map((q: any) => q.close as number);
112
+ return slice.reduce((a: number, b: number) => a + b, 0) / slice.length;
113
+ };
114
+
115
+ const MA200 = calculateMA200(latestIndex);
116
+
117
+ const summary = await yahooFinance.quoteSummary(symbol, { modules: ['financialData'] });
118
+ const roe = summary.financialData?.returnOnEquity || 0;
119
+ const debtToEquity = summary.financialData?.debtToEquity || 999;
120
+
121
+ // Quality Check
122
+ const isQuality = roe > 0.15 && debtToEquity < 60;
123
+
124
+ if (!isQuality) return {
125
+ symbol, price: currentPrice, wma200: MA200, roe, debtToEquity, isQuality,
126
+ lastCandleColor: 'RED', touchedWMA: false, signal: 'IGNORE', details: 'Low Quality'
127
+ };
128
+
129
+ // Strategy Logic
130
+ let touchedWMA = false;
131
+ for (let i = 0; i < 4; i++) {
132
+ const idx = latestIndex - i;
133
+ const candle = quotes[idx];
134
+ const ma = calculateMA200(idx);
135
+ if (candle.low <= ma) touchedWMA = true;
136
+ }
137
+
138
+ const lastCandle = quotes[latestIndex];
139
+ const isGreen = lastCandle.close > lastCandle.open;
140
+ let signal: 'BUY_TRIGGER' | 'WATCHLIST' | 'IGNORE' | 'WAIT' = 'WATCHLIST';
141
+
142
+ if (touchedWMA) {
143
+ if (isGreen) signal = 'BUY_TRIGGER';
144
+ else signal = 'WAIT';
145
+ } else {
146
+ const dist = (currentPrice - MA200) / MA200;
147
+ if (dist > 0 && dist < 0.05) signal = 'WATCHLIST';
148
+ else if (dist < 0) signal = 'WAIT';
149
+ else signal = 'IGNORE';
150
+ }
151
+
152
+ return {
153
+ symbol, price: currentPrice, wma200: MA200, roe, debtToEquity, isQuality,
154
+ lastCandleColor: isGreen ? 'GREEN' : 'RED', touchedWMA, signal,
155
+ details: touchedWMA ? (isGreen ? 'Rebound Confirm' : 'Testing Support') : `Dist: ${(((currentPrice - MA200) / MA200) * 100).toFixed(1)}%`
156
+ };
157
+
158
+ } catch (error: any) {
159
+ return null;
160
+ }
161
+ }
162
+
163
+ async function main() {
164
+ const lastSunday = getLastSunday();
165
+ console.log(`=== MUNGER SCREENER (S&P 500 FULL) ===`);
166
+ console.log(`Ref Date: ${lastSunday.toISOString().split('T')[0]}`);
167
+ console.log(`Analyzing ${WATCHLIST.length} companies...`);
168
+
169
+ const results: StrategyResult[] = [];
170
+ const buys: string[] = [];
171
+
172
+ // Chunking to show progress nicely
173
+ const chunkSize = 5; // Smaller chunks for large list stability
174
+ for (let i = 0; i < WATCHLIST.length; i += chunkSize) {
175
+ const chunk = WATCHLIST.slice(i, i + chunkSize);
176
+ const promises = chunk.map(symbol => analyzeStock(symbol, lastSunday));
177
+ const chunkResults = await Promise.all(promises);
178
+
179
+ chunkResults.forEach((res) => {
180
+ if (res) {
181
+ results.push(res);
182
+ process.stdout.write(res.isQuality ? (res.signal === 'BUY_TRIGGER' ? '!' : '+') : '.');
183
+ if (res.signal === 'BUY_TRIGGER') {
184
+ buys.push(res.symbol);
185
+ }
186
+ } else {
187
+ process.stdout.write('x');
188
+ }
189
+ });
190
+ // Delay to respect API limits
191
+ await new Promise(r => setTimeout(r, 200));
192
+ }
193
+
194
+ console.log('\n\n--- Analysis Complete ---');
195
+ console.log(`Found ${buys.length} Buy Triggers.`);
196
+
197
+ if (buys.length > 0) {
198
+ const uniqueBuys = [...new Set(buys)];
199
+ for (const symbol of uniqueBuys) {
200
+ await deepDive(symbol);
201
+ }
202
+ } else {
203
+ console.log("No stocks triggered Deep Dive criteria this week.");
204
+ }
205
+ }
206
+
207
+ main();
scripts/munger-test.ts ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import YahooFinance from 'yahoo-finance2';
2
+ import Alpaca from '@alpacahq/alpaca-trade-api';
3
+ import dotenv from 'dotenv';
4
+ import path from 'path';
5
+
6
+ // Load environment variables from .env file
7
+ dotenv.config({ path: path.resolve(__dirname, '../.env') });
8
+
9
+ // Fix for yahoo-finance2 v3+ where default export is the class
10
+ const yahooFinance = new (YahooFinance as any)();
11
+
12
+ // @ts-ignore
13
+ const alpaca = new Alpaca({
14
+ keyId: process.env.ALPACA_KEY,
15
+ secretKey: process.env.ALPACA_SECRET,
16
+ paper: true,
17
+ });
18
+
19
+ interface StockMetrics {
20
+ symbol: string;
21
+ price: number;
22
+ wma200: number;
23
+ roe: number;
24
+ isQuality: boolean;
25
+ }
26
+
27
+ async function getMungerMetrics(symbol: string): Promise<StockMetrics> {
28
+ // 1. Obtener Histórico para WMA
29
+ const queryOptions = { period1: '2021-01-01', interval: '1wk' as const };
30
+ const history = await yahooFinance.chart(symbol, queryOptions);
31
+
32
+ const closes = history.quotes.map((q: any) => q.close).filter((c: any): c is number => !!c);
33
+
34
+ // Calcular 200-WMA (Simple Moving Average de 200 periodos semanales)
35
+ const slice = closes.slice(-200);
36
+ const wma200 = slice.reduce((a: number, b: number) => a + b, 0) / slice.length;
37
+
38
+ // 2. Obtener Fundamentales
39
+ const summary = await yahooFinance.quoteSummary(symbol, { modules: ['financialData', 'defaultKeyStatistics'] });
40
+ const roe = summary.financialData?.returnOnEquity || 0;
41
+ const debtToEquity = summary.financialData?.debtToEquity || 0;
42
+
43
+ return {
44
+ symbol,
45
+ price: closes[closes.length - 1],
46
+ wma200,
47
+ roe,
48
+ isQuality: roe > 0.15 && debtToEquity < 50
49
+ };
50
+ }
51
+
52
+ async function checkAlpacaConnection() {
53
+ console.log("Checking Alpaca Connection...");
54
+ try {
55
+ const account = await alpaca.getAccount();
56
+ console.log("Alpaca Connection Successful!");
57
+ console.log(`Account Status: ${account.status}`);
58
+ console.log(`Buying Power: ${account.buying_power}`);
59
+ return true;
60
+ } catch (error: any) {
61
+ console.error("Alpaca Connection Failed:", error.message);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ async function main() {
67
+
68
+ // Check Alpaca first
69
+ const alpacaOk = await checkAlpacaConnection();
70
+ if (!alpacaOk) {
71
+ console.log("Skipping Munger metrics due to credential failure (optional).");
72
+ // We can continue or stop. Let's continue for now to show other parts work.
73
+ }
74
+
75
+ console.log("\nStarting Munger Metrics Calculation...");
76
+ const symbol = 'AAPL';
77
+ try {
78
+ // Suppress warnings?
79
+ // yahooFinance.suppressNotices(['yahooSurvey']);
80
+
81
+ const metrics = await getMungerMetrics(symbol);
82
+ console.log(`Metrics for ${symbol}:`);
83
+ console.log(JSON.stringify(metrics, null, 2));
84
+ } catch (error) {
85
+ console.error("Error calculating metrics:", error);
86
+ }
87
+ }
88
+
89
+ main();
scripts/sp500_symbols.json ADDED
@@ -0,0 +1,505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "MMM",
3
+ "AOS",
4
+ "ABT",
5
+ "ABBV",
6
+ "ACN",
7
+ "ADBE",
8
+ "AMD",
9
+ "AES",
10
+ "AFL",
11
+ "A",
12
+ "APD",
13
+ "ABNB",
14
+ "AKAM",
15
+ "ALB",
16
+ "ARE",
17
+ "ALGN",
18
+ "ALLE",
19
+ "LNT",
20
+ "ALL",
21
+ "GOOGL",
22
+ "GOOG",
23
+ "MO",
24
+ "AMZN",
25
+ "AMCR",
26
+ "AEE",
27
+ "AEP",
28
+ "AXP",
29
+ "AIG",
30
+ "AMT",
31
+ "AWK",
32
+ "AMP",
33
+ "AME",
34
+ "AMGN",
35
+ "APH",
36
+ "ADI",
37
+ "AON",
38
+ "APA",
39
+ "APO",
40
+ "AAPL",
41
+ "AMAT",
42
+ "APP",
43
+ "APTV",
44
+ "ACGL",
45
+ "ADM",
46
+ "ARES",
47
+ "ANET",
48
+ "AJG",
49
+ "AIZ",
50
+ "T",
51
+ "ATO",
52
+ "ADSK",
53
+ "ADP",
54
+ "AZO",
55
+ "AVB",
56
+ "AVY",
57
+ "AXON",
58
+ "BKR",
59
+ "BALL",
60
+ "BAC",
61
+ "BAX",
62
+ "BDX",
63
+ "BRK-B",
64
+ "BBY",
65
+ "TECH",
66
+ "BIIB",
67
+ "BLK",
68
+ "BX",
69
+ "XYZ",
70
+ "BK",
71
+ "BA",
72
+ "BKNG",
73
+ "BSX",
74
+ "BMY",
75
+ "AVGO",
76
+ "BR",
77
+ "BRO",
78
+ "BF-B",
79
+ "BLDR",
80
+ "BG",
81
+ "BXP",
82
+ "CHRW",
83
+ "CDNS",
84
+ "CPT",
85
+ "CPB",
86
+ "COF",
87
+ "CAH",
88
+ "CCL",
89
+ "CARR",
90
+ "CVNA",
91
+ "CAT",
92
+ "CBOE",
93
+ "CBRE",
94
+ "CDW",
95
+ "COR",
96
+ "CNC",
97
+ "CNP",
98
+ "CF",
99
+ "CRL",
100
+ "SCHW",
101
+ "CHTR",
102
+ "CVX",
103
+ "CMG",
104
+ "CB",
105
+ "CHD",
106
+ "CI",
107
+ "CINF",
108
+ "CTAS",
109
+ "CSCO",
110
+ "C",
111
+ "CFG",
112
+ "CLX",
113
+ "CME",
114
+ "CMS",
115
+ "KO",
116
+ "CTSH",
117
+ "COIN",
118
+ "CL",
119
+ "CMCSA",
120
+ "FIX",
121
+ "CAG",
122
+ "COP",
123
+ "ED",
124
+ "STZ",
125
+ "CEG",
126
+ "COO",
127
+ "CPRT",
128
+ "GLW",
129
+ "CPAY",
130
+ "CTVA",
131
+ "CSGP",
132
+ "COST",
133
+ "CTRA",
134
+ "CRH",
135
+ "CRWD",
136
+ "CCI",
137
+ "CSX",
138
+ "CMI",
139
+ "CVS",
140
+ "DHR",
141
+ "DRI",
142
+ "DDOG",
143
+ "DVA",
144
+ "DAY",
145
+ "DECK",
146
+ "DE",
147
+ "DELL",
148
+ "DAL",
149
+ "DVN",
150
+ "DXCM",
151
+ "FANG",
152
+ "DLR",
153
+ "DG",
154
+ "DLTR",
155
+ "D",
156
+ "DPZ",
157
+ "DASH",
158
+ "DOV",
159
+ "DOW",
160
+ "DHI",
161
+ "DTE",
162
+ "DUK",
163
+ "DD",
164
+ "ETN",
165
+ "EBAY",
166
+ "ECL",
167
+ "EIX",
168
+ "EW",
169
+ "EA",
170
+ "ELV",
171
+ "EME",
172
+ "EMR",
173
+ "ETR",
174
+ "EOG",
175
+ "EPAM",
176
+ "EQT",
177
+ "EFX",
178
+ "EQIX",
179
+ "EQR",
180
+ "ERIE",
181
+ "ESS",
182
+ "EL",
183
+ "EG",
184
+ "EVRG",
185
+ "ES",
186
+ "EXC",
187
+ "EXE",
188
+ "EXPE",
189
+ "EXPD",
190
+ "EXR",
191
+ "XOM",
192
+ "FFIV",
193
+ "FDS",
194
+ "FICO",
195
+ "FAST",
196
+ "FRT",
197
+ "FDX",
198
+ "FIS",
199
+ "FITB",
200
+ "FSLR",
201
+ "FE",
202
+ "FISV",
203
+ "F",
204
+ "FTNT",
205
+ "FTV",
206
+ "FOXA",
207
+ "FOX",
208
+ "BEN",
209
+ "FCX",
210
+ "GRMN",
211
+ "IT",
212
+ "GE",
213
+ "GEHC",
214
+ "GEV",
215
+ "GEN",
216
+ "GNRC",
217
+ "GD",
218
+ "GIS",
219
+ "GM",
220
+ "GPC",
221
+ "GILD",
222
+ "GPN",
223
+ "GL",
224
+ "GDDY",
225
+ "GS",
226
+ "HAL",
227
+ "HIG",
228
+ "HAS",
229
+ "HCA",
230
+ "DOC",
231
+ "HSIC",
232
+ "HSY",
233
+ "HPE",
234
+ "HLT",
235
+ "HOLX",
236
+ "HD",
237
+ "HON",
238
+ "HRL",
239
+ "HST",
240
+ "HWM",
241
+ "HPQ",
242
+ "HUBB",
243
+ "HUM",
244
+ "HBAN",
245
+ "HII",
246
+ "IBM",
247
+ "IEX",
248
+ "IDXX",
249
+ "ITW",
250
+ "INCY",
251
+ "IR",
252
+ "PODD",
253
+ "INTC",
254
+ "IBKR",
255
+ "ICE",
256
+ "IFF",
257
+ "IP",
258
+ "INTU",
259
+ "ISRG",
260
+ "IVZ",
261
+ "INVH",
262
+ "IQV",
263
+ "IRM",
264
+ "JBHT",
265
+ "JBL",
266
+ "JKHY",
267
+ "J",
268
+ "JNJ",
269
+ "JCI",
270
+ "JPM",
271
+ "KVUE",
272
+ "KDP",
273
+ "KEY",
274
+ "KEYS",
275
+ "KMB",
276
+ "KIM",
277
+ "KMI",
278
+ "KKR",
279
+ "KLAC",
280
+ "KHC",
281
+ "KR",
282
+ "LHX",
283
+ "LH",
284
+ "LRCX",
285
+ "LW",
286
+ "LVS",
287
+ "LDOS",
288
+ "LEN",
289
+ "LII",
290
+ "LLY",
291
+ "LIN",
292
+ "LYV",
293
+ "LMT",
294
+ "L",
295
+ "LOW",
296
+ "LULU",
297
+ "LYB",
298
+ "MTB",
299
+ "MPC",
300
+ "MAR",
301
+ "MRSH",
302
+ "MLM",
303
+ "MAS",
304
+ "MA",
305
+ "MTCH",
306
+ "MKC",
307
+ "MCD",
308
+ "MCK",
309
+ "MDT",
310
+ "MRK",
311
+ "META",
312
+ "MET",
313
+ "MTD",
314
+ "MGM",
315
+ "MCHP",
316
+ "MU",
317
+ "MSFT",
318
+ "MAA",
319
+ "MRNA",
320
+ "MOH",
321
+ "TAP",
322
+ "MDLZ",
323
+ "MPWR",
324
+ "MNST",
325
+ "MCO",
326
+ "MS",
327
+ "MOS",
328
+ "MSI",
329
+ "MSCI",
330
+ "NDAQ",
331
+ "NTAP",
332
+ "NFLX",
333
+ "NEM",
334
+ "NWSA",
335
+ "NWS",
336
+ "NEE",
337
+ "NKE",
338
+ "NI",
339
+ "NDSN",
340
+ "NSC",
341
+ "NTRS",
342
+ "NOC",
343
+ "NCLH",
344
+ "NRG",
345
+ "NUE",
346
+ "NVDA",
347
+ "NVR",
348
+ "NXPI",
349
+ "ORLY",
350
+ "OXY",
351
+ "ODFL",
352
+ "OMC",
353
+ "ON",
354
+ "OKE",
355
+ "ORCL",
356
+ "OTIS",
357
+ "PCAR",
358
+ "PKG",
359
+ "PLTR",
360
+ "PANW",
361
+ "PSKY",
362
+ "PH",
363
+ "PAYX",
364
+ "PAYC",
365
+ "PYPL",
366
+ "PNR",
367
+ "PEP",
368
+ "PFE",
369
+ "PCG",
370
+ "PM",
371
+ "PSX",
372
+ "PNW",
373
+ "PNC",
374
+ "POOL",
375
+ "PPG",
376
+ "PPL",
377
+ "PFG",
378
+ "PG",
379
+ "PGR",
380
+ "PLD",
381
+ "PRU",
382
+ "PEG",
383
+ "PTC",
384
+ "PSA",
385
+ "PHM",
386
+ "PWR",
387
+ "QCOM",
388
+ "DGX",
389
+ "Q",
390
+ "RL",
391
+ "RJF",
392
+ "RTX",
393
+ "O",
394
+ "REG",
395
+ "REGN",
396
+ "RF",
397
+ "RSG",
398
+ "RMD",
399
+ "RVTY",
400
+ "HOOD",
401
+ "ROK",
402
+ "ROL",
403
+ "ROP",
404
+ "ROST",
405
+ "RCL",
406
+ "SPGI",
407
+ "CRM",
408
+ "SNDK",
409
+ "SBAC",
410
+ "SLB",
411
+ "STX",
412
+ "SRE",
413
+ "NOW",
414
+ "SHW",
415
+ "SPG",
416
+ "SWKS",
417
+ "SJM",
418
+ "SW",
419
+ "SNA",
420
+ "SOLV",
421
+ "SO",
422
+ "LUV",
423
+ "SWK",
424
+ "SBUX",
425
+ "STT",
426
+ "STLD",
427
+ "STE",
428
+ "SYK",
429
+ "SMCI",
430
+ "SYF",
431
+ "SNPS",
432
+ "SYY",
433
+ "TMUS",
434
+ "TROW",
435
+ "TTWO",
436
+ "TPR",
437
+ "TRGP",
438
+ "TGT",
439
+ "TEL",
440
+ "TDY",
441
+ "TER",
442
+ "TSLA",
443
+ "TXN",
444
+ "TPL",
445
+ "TXT",
446
+ "TMO",
447
+ "TJX",
448
+ "TKO",
449
+ "TTD",
450
+ "TSCO",
451
+ "TT",
452
+ "TDG",
453
+ "TRV",
454
+ "TRMB",
455
+ "TFC",
456
+ "TYL",
457
+ "TSN",
458
+ "USB",
459
+ "UBER",
460
+ "UDR",
461
+ "ULTA",
462
+ "UNP",
463
+ "UAL",
464
+ "UPS",
465
+ "URI",
466
+ "UNH",
467
+ "UHS",
468
+ "VLO",
469
+ "VTR",
470
+ "VLTO",
471
+ "VRSN",
472
+ "VRSK",
473
+ "VZ",
474
+ "VRTX",
475
+ "VTRS",
476
+ "VICI",
477
+ "V",
478
+ "VST",
479
+ "VMC",
480
+ "WRB",
481
+ "GWW",
482
+ "WAB",
483
+ "WMT",
484
+ "DIS",
485
+ "WBD",
486
+ "WM",
487
+ "WAT",
488
+ "WEC",
489
+ "WFC",
490
+ "WELL",
491
+ "WST",
492
+ "WDC",
493
+ "WY",
494
+ "WSM",
495
+ "WMB",
496
+ "WTW",
497
+ "WDAY",
498
+ "WYNN",
499
+ "XEL",
500
+ "XYL",
501
+ "YUM",
502
+ "ZBRA",
503
+ "ZBH",
504
+ "ZTS"
505
+ ]
scripts/test.js ADDED
@@ -0,0 +1 @@
 
 
1
+ console.log("Hello from the scripts folder! Use this space to test solutions.");