Skip to content

Commit dff6ced

Browse files
furicclaude
andcommitted
Refocus intraday alerts on STRONG BUY signals only
Alerts now trigger only for: - Upgrades to STRONG BUY from any other level - Downgrades from STRONG BUY to any other level - Confidence changes ≥10 while staying at STRONG BUY Default threshold changed from 5 to 10. Removes noise from BUY-level confidence changes that aren't actionable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 71bb80a commit dff6ced

8 files changed

Lines changed: 34 additions & 41 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ src/index.ts (entry point — parses --weekly/--intraday flags, wires modules)
4141
→ src/analyze.ts # Allocation gaps, P/E signals, ETF overlap discounts, portfolio beta, dividend estimate
4242
→ src/aiAnalysis.ts # Gemini AI: buy recs + confidence + limit prices + value ratings + crypto bottom signals
4343
→ src/state.ts # Save/load morning baseline for intraday comparison
44-
→ src/intradayCompare.ts # Compare current AI recs vs morning baseline, detect strengthening
44+
→ src/intradayCompare.ts # Compare current AI recs vs morning baseline, alert on STRONG BUY changes
4545
→ src/email.ts # Daily dark-themed HTML email + Resend
4646
→ src/intradayEmail.ts # Intraday alert email (focused, only triggered tickers)
4747
→ src/weeklyEmail.ts # Weekly rebalancing HTML email + Resend

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ config.json + .env
132132

133133
Weekly mode (`--weekly`) skips news and AI, producing a focused rebalancing report.
134134

135-
Intraday mode (`--intraday`) re-fetches prices, technicals, and AI (skipping news), compares against the morning baseline, and alerts only when buy signals strengthen significantly.
135+
Intraday mode (`--intraday`) re-fetches prices, technicals, and AI (skipping news), compares against the morning baseline, and alerts only for STRONG BUY-related changes: upgrades to STRONG BUY, downgrades from STRONG BUY, or confidence shifts ≥10 while at STRONG BUY.
136136

137137
## Updating Your Portfolio
138138

config.example.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"totalPortfolioValueUSD": 50000,
2626
"intradayAlerts": {
2727
"enabled": true,
28-
"confidenceIncreaseThreshold": 5,
28+
"confidenceIncreaseThreshold": 10,
2929
"minConfidenceToAlert": 80,
3030
"actionUpgradesAlert": true,
3131
"onlyAlertForActions": ["STRONG BUY", "BUY"]

docs/configuration.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@ cp config.example.json config.json
3636
"totalPortfolioValueUSD": 50000,
3737
"intradayAlerts": {
3838
"enabled": true,
39-
"confidenceIncreaseThreshold": 5,
40-
"minConfidenceToAlert": 80,
41-
"actionUpgradesAlert": true,
42-
"onlyAlertForActions": ["STRONG BUY", "BUY"]
39+
"confidenceIncreaseThreshold": 10
4340
}
4441
}
4542
```
@@ -61,13 +58,15 @@ cp config.example.json config.json
6158

6259
The `intradayAlerts` section controls when intraday checks send alerts. All fields are optional — sensible defaults apply.
6360

61+
Alerts trigger only for STRONG BUY-related changes:
62+
1. **Upgraded to STRONG BUY** — any other level → STRONG BUY
63+
2. **Downgraded from STRONG BUY** — STRONG BUY → any other level
64+
3. **Confidence changed** — confidence shifted ≥ threshold while staying STRONG BUY
65+
6466
| Field | Default | Description |
6567
|-------|---------|-------------|
6668
| `enabled` | `true` | Master toggle. Set `false` to disable intraday alerts entirely. |
67-
| `confidenceIncreaseThreshold` | `5` | Minimum confidence increase (percentage points) to trigger an alert. |
68-
| `minConfidenceToAlert` | `80` | Only alert if the new confidence is at least this high. |
69-
| `actionUpgradesAlert` | `true` | Also alert when the action upgrades (e.g., BUY → STRONG BUY). |
70-
| `onlyAlertForActions` | `["STRONG BUY", "BUY"]` | Only consider tickers with these current actions. |
69+
| `confidenceIncreaseThreshold` | `10` | Minimum confidence change (absolute, percentage points) to trigger an alert for STRONG BUY tickers. |
7170

7271
---
7372

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const totalPortfolioValueUSD = json.totalPortfolioValueUSD;
3838
// ── Intraday alert config with defaults ─────────────────────────────
3939
const DEFAULT_INTRADAY: IntradayAlertConfig = {
4040
enabled: true,
41-
confidenceIncreaseThreshold: 5,
41+
confidenceIncreaseThreshold: 10,
4242
minConfidenceToAlert: 80,
4343
actionUpgradesAlert: true,
4444
onlyAlertForActions: ["STRONG BUY", "BUY"],

src/intradayCompare.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface IntradayAlert {
1717
valueRating?: string;
1818
bottomSignal?: string;
1919
analysisUrl?: string;
20-
triggerType: "confidence_increase" | "action_upgrade" | "new_signal";
20+
triggerType: "confidence_change" | "action_upgrade" | "action_downgrade";
2121
currentPrice: number;
2222
morningPrice: number;
2323
priceDelta: number;
@@ -45,41 +45,35 @@ export function compareWithBaseline(
4545
);
4646

4747
for (const rec of currentRecs) {
48-
if (!config.onlyAlertForActions.includes(rec.action)) continue;
49-
5048
const morning = baselineMap.get(rec.ticker);
5149
const morningAction = morning?.action ?? "N/A";
5250
const morningConfidence = morning?.confidence ?? 0;
5351
const morningPrice = baseline.prices[rec.ticker] ?? 0;
5452
const currentPrice = currentPrices[rec.ticker] ?? 0;
5553

5654
const confidenceDelta = rec.confidence - morningConfidence;
57-
const currentRank = ACTION_RANK[rec.action] ?? 0;
58-
const morningRank = ACTION_RANK[morningAction] ?? 0;
59-
const actionUpgraded = currentRank > morningRank;
55+
const wasStrongBuy = morningAction === "STRONG BUY";
56+
const isStrongBuy = rec.action === "STRONG BUY";
6057

6158
let triggerType: IntradayAlert["triggerType"] | null = null;
6259

63-
// Trigger 1: Confidence increased beyond threshold AND meets minimum
64-
if (
65-
confidenceDelta >= config.confidenceIncreaseThreshold &&
66-
rec.confidence >= config.minConfidenceToAlert
67-
) {
68-
triggerType = "confidence_increase";
60+
// Trigger 1: Downgraded FROM STRONG BUY to any other level
61+
if (wasStrongBuy && !isStrongBuy) {
62+
triggerType = "action_downgrade";
6963
}
7064

71-
// Trigger 2: Action upgraded (e.g., BUY → STRONG BUY)
72-
if (
73-
config.actionUpgradesAlert &&
74-
actionUpgraded &&
75-
rec.confidence >= config.minConfidenceToAlert
76-
) {
65+
// Trigger 2: Upgraded TO STRONG BUY from any other level
66+
if (!wasStrongBuy && isStrongBuy) {
7767
triggerType = "action_upgrade";
7868
}
7969

80-
// Trigger 3: New signal — ticker wasn't in morning recs at all
81-
if (!morning && rec.confidence >= config.minConfidenceToAlert) {
82-
triggerType = "new_signal";
70+
// Trigger 3: Confidence changed ≥ threshold while staying STRONG BUY
71+
if (
72+
wasStrongBuy &&
73+
isStrongBuy &&
74+
Math.abs(confidenceDelta) >= config.confidenceIncreaseThreshold
75+
) {
76+
triggerType = "confidence_change";
8377
}
8478

8579
if (triggerType) {

src/intradayEmail.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ function actionBadge(action: string): string {
4343
function triggerLabel(type: IntradayAlert["triggerType"]): string {
4444
switch (type) {
4545
case "action_upgrade":
46-
return "Action Upgraded";
47-
case "new_signal":
48-
return "New Signal";
49-
case "confidence_increase":
50-
return "Confidence Increased";
46+
return "Upgraded to STRONG BUY";
47+
case "action_downgrade":
48+
return "Downgraded from STRONG BUY";
49+
case "confidence_change":
50+
return "Confidence Changed";
5151
}
5252
}
5353

src/telegram.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,9 @@ function buildIntradayMessage(alerts: IntradayAlert[]): string {
273273
const triggerLabel =
274274
alert.triggerType === "action_upgrade"
275275
? "upgraded"
276-
: alert.triggerType === "new_signal"
277-
? "new signal"
278-
: "strengthened";
276+
: alert.triggerType === "action_downgrade"
277+
? "downgraded"
278+
: "confidence changed";
279279

280280
lines.push(
281281
`${actionEmoji(alert.currentAction)} <b>${alert.currentAction} ${alert.ticker}</b> (${triggerLabel})`

0 commit comments

Comments
 (0)