Spaces:
Sleeping
Sleeping
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 +1 -0
- funct_reqs.md +85 -0
- package-lock.json +0 -0
- package.json +7 -2
- scripts/debug_yf.ts +11 -0
- scripts/fetch_sp500.ts +49 -0
- scripts/find_forever_stocks.ts +207 -0
- scripts/munger-test.ts +89 -0
- scripts/sp500_symbols.json +505 -0
- scripts/test.js +1 -0
.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.");
|