@cablate/banini-tracker 2.0.10 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -15
- package/dist/index.js +10 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
# banini-tracker
|
|
6
6
|
|
|
7
|
-
追蹤「股海冥燈」巴逆逆(8zz)的 Facebook 社群貼文,透過 Apify 抓取、AI 反指標分析、Telegram
|
|
7
|
+
追蹤「股海冥燈」巴逆逆(8zz)的 Facebook 社群貼文,透過 Apify 抓取、AI 反指標分析、Telegram 即時推送,並自動追蹤預測準確度。
|
|
8
8
|
|
|
9
9
|
- 辨識她提到的標的(個股、ETF、原物料)
|
|
10
10
|
- 判斷她的操作(買入 / 被套 / 停損)
|
|
11
11
|
- 反轉推導(她停損 → 可能反彈、她買入 → 可能下跌)
|
|
12
12
|
- 推導連鎖效應(油價跌 → 製造業利多 → 電子股受惠)
|
|
13
|
+
- 自動記錄預測,追蹤 5 個交易日的實際走勢
|
|
13
14
|
|
|
14
15
|
> **Claude Code 使用者?** 直接把 [`skill/SKILL.md`](skill/SKILL.md) 加到你的 `.claude/skills/` 就能用。Claude 自己當分析引擎,不需要額外 LLM。
|
|
15
16
|
|
|
16
17
|
支援兩種使用模式:
|
|
17
|
-
- **常駐排程**:Docker 部署,自動盤中/盤後排程 + LLM 分析 + Telegram 推送
|
|
18
|
+
- **常駐排程**:Docker 部署,自動盤中/盤後排程 + LLM 分析 + Telegram 推送 + 預測追蹤
|
|
18
19
|
- **CLI 工具**:`npx @cablate/banini-tracker`,搭配 Claude Code 等 AI 手動執行分析
|
|
19
20
|
|
|
20
21
|
## 快速開始(常駐排程)
|
|
@@ -26,7 +27,7 @@ cp .env.example .env
|
|
|
26
27
|
|
|
27
28
|
# 2. Docker 部署
|
|
28
29
|
docker build -t banini-tracker .
|
|
29
|
-
docker run -d --name banini --env-file .env banini-tracker
|
|
30
|
+
docker run -d --name banini --env-file .env -v banini-data:/data banini-tracker
|
|
30
31
|
|
|
31
32
|
# 3. 或本地直接跑
|
|
32
33
|
npm install && npm run start
|
|
@@ -34,18 +35,24 @@ npm install && npm run start
|
|
|
34
35
|
|
|
35
36
|
### 排程規則
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
| 排程 | 時間 | 說明 |
|
|
39
|
+
|------|------|------|
|
|
40
|
+
| 早晨補漏 | 每天 08:00 | 抓前一晚 22:00 後的貼文(3 篇) |
|
|
41
|
+
| 盤中 | 週一~五 09:07-13:07 每 30 分 | 抓 08:30 後的貼文(1 篇) |
|
|
42
|
+
| 追蹤更新 | 週一~五 15:00 | 更新預測追蹤(收盤後抓 OHLC) |
|
|
43
|
+
| 盤後 | 每天 23:03 | 抓 13:30 後的貼文(3 篇) |
|
|
44
|
+
|
|
45
|
+
每個排程只抓自己時間窗口內的貼文,搭配 seen.json 去重,確保無死角且不重複。
|
|
39
46
|
|
|
40
47
|
### npm scripts
|
|
41
48
|
|
|
42
49
|
| 指令 | 說明 |
|
|
43
50
|
|------|------|
|
|
44
|
-
| `npm run start` |
|
|
51
|
+
| `npm run start` | 常駐排程模式(全部排程自動跑) |
|
|
45
52
|
| `npm run dev` | 單次執行(FB 3 篇) |
|
|
46
53
|
| `npm run dry` | 只抓取,不呼叫 LLM |
|
|
47
|
-
| `npm run market` | 盤中模式(FB
|
|
48
|
-
| `npm run evening` |
|
|
54
|
+
| `npm run market` | 盤中模式(FB 1 篇) |
|
|
55
|
+
| `npm run evening` | 盤後模式(FB 3 篇) |
|
|
49
56
|
|
|
50
57
|
### .env 設定
|
|
51
58
|
|
|
@@ -60,8 +67,37 @@ TG_CHANNEL_ID=-100...
|
|
|
60
67
|
# 影片轉錄(選填,啟用後自動轉錄影片貼文)
|
|
61
68
|
TRANSCRIBER=groq
|
|
62
69
|
GROQ_API_KEY=gsk_...
|
|
70
|
+
|
|
71
|
+
# FinMind API(選填,免費可用,註冊可提高額度)
|
|
72
|
+
FINMIND_TOKEN=...
|
|
73
|
+
|
|
74
|
+
# 資料目錄(Docker 建議掛載 /data)
|
|
75
|
+
DATA_DIR=/data
|
|
63
76
|
```
|
|
64
77
|
|
|
78
|
+
## 預測追蹤系統
|
|
79
|
+
|
|
80
|
+
LLM 分析出標的後,系統自動:
|
|
81
|
+
|
|
82
|
+
1. **映射股票代碼**:台股名稱 → 代碼(2230 檔上市 + 上櫃)
|
|
83
|
+
2. **記錄基準價格**:以貼文發佈時間查對應交易日收盤價
|
|
84
|
+
3. **追蹤 5 個交易日**:每天 15:00 收盤後抓 OHLC,記錄漲跌幅
|
|
85
|
+
4. **同股票取代**:新預測自動取代同標的舊預測(supersede 機制)
|
|
86
|
+
|
|
87
|
+
勝敗判定在查詢時決定,支援多維度分析(不同持有天數、信心度分群、操作類型)。
|
|
88
|
+
|
|
89
|
+
### 資料儲存
|
|
90
|
+
|
|
91
|
+
使用 SQLite(better-sqlite3),資料表:
|
|
92
|
+
|
|
93
|
+
| 表 | 用途 |
|
|
94
|
+
|----|------|
|
|
95
|
+
| `posts` | 所有貼文原文(即時 + 歷史回測統一來源) |
|
|
96
|
+
| `predictions` | 預測記錄(標的、方向、基準價、狀態) |
|
|
97
|
+
| `price_snapshots` | 每日 OHLC 快照(5 天追蹤期) |
|
|
98
|
+
|
|
99
|
+
資料庫位置:`$DATA_DIR/banini.db`(Docker 掛載 `/data`,本地 `~/.banini-tracker/`)
|
|
100
|
+
|
|
65
101
|
## CLI 工具模式
|
|
66
102
|
|
|
67
103
|
不需 clone repo,任何環境直接用:
|
|
@@ -76,8 +112,11 @@ npx @cablate/banini-tracker init \
|
|
|
76
112
|
# 抓取 Facebook 最新 3 篇
|
|
77
113
|
npx @cablate/banini-tracker fetch -s fb -n 3 --mark-seen
|
|
78
114
|
|
|
115
|
+
# 抓取指定日期區間(回測用)
|
|
116
|
+
npx @cablate/banini-tracker fetch --since 2025-04-01 --until 2025-05-01 -n 100
|
|
117
|
+
|
|
79
118
|
# 推送結果到 Telegram
|
|
80
|
-
npx @cablate/banini-tracker push -
|
|
119
|
+
npx @cablate/banini-tracker push -f report.txt
|
|
81
120
|
```
|
|
82
121
|
|
|
83
122
|
### CLI 指令
|
|
@@ -97,6 +136,8 @@ npx @cablate/banini-tracker push -m "分析結果..."
|
|
|
97
136
|
```
|
|
98
137
|
-s, --source <source> 來源:fb(預設 fb)
|
|
99
138
|
-n, --limit <n> 每個來源抓幾篇(預設 3)
|
|
139
|
+
--since <date> 只抓此時間之後的貼文(YYYY-MM-DD / ISO 時間戳 / 相對時間如 "2 months")
|
|
140
|
+
--until <date> 只抓此時間之前的貼文
|
|
100
141
|
--no-dedup 不去重
|
|
101
142
|
--mark-seen 輸出後自動標記已讀
|
|
102
143
|
```
|
|
@@ -105,7 +146,7 @@ npx @cablate/banini-tracker push -m "分析結果..."
|
|
|
105
146
|
|
|
106
147
|
```
|
|
107
148
|
-m, --message <text> 直接帶訊息
|
|
108
|
-
-f, --file <path>
|
|
149
|
+
-f, --file <path> 從檔案讀取(推薦多行內容用這個)
|
|
109
150
|
--parse-mode <mode> HTML / Markdown / none(預設 HTML)
|
|
110
151
|
```
|
|
111
152
|
|
|
@@ -117,7 +158,7 @@ npx @cablate/banini-tracker push -m "分析結果..."
|
|
|
117
158
|
|
|
118
159
|
1. `fetch` 抓貼文 → Claude 讀 JSON
|
|
119
160
|
2. Claude 分析 + WebSearch 查最新走勢
|
|
120
|
-
3. Claude 組報告 → `push` 推送 Telegram
|
|
161
|
+
3. Claude 組報告 → `push -f` 推送 Telegram
|
|
121
162
|
|
|
122
163
|
詳見 [`skill/SKILL.md`](skill/SKILL.md)。
|
|
123
164
|
|
|
@@ -125,14 +166,14 @@ npx @cablate/banini-tracker push -m "分析結果..."
|
|
|
125
166
|
|
|
126
167
|
| 項目 | 單次費用 | 頻率 | 月估算 |
|
|
127
168
|
|------|---------|------|--------|
|
|
128
|
-
| Facebook 抓取(Apify) | ~$0.
|
|
169
|
+
| Facebook 抓取(Apify) | ~$0.005/篇 | ~270 篇/月 | ~$1.35 |
|
|
129
170
|
| LLM 分析(常駐模式) | 依模型而定 | 同上 | 依模型定價 |
|
|
130
171
|
| 影片轉錄(Groq Whisper) | ~$0.006/分鐘 | 視影片數量 | 極低 |
|
|
172
|
+
| 股價查詢(FinMind) | 免費 | 每日收盤後 | $0 |
|
|
131
173
|
| Telegram 推送 | 免費 | — | $0 |
|
|
132
174
|
|
|
133
|
-
>
|
|
134
|
-
>
|
|
135
|
-
> CLI 模式搭配 Claude Code 使用則不需 LLM 費用,Claude 自己分析
|
|
175
|
+
> CLI 模式搭配 Claude Code 使用不需 LLM 費用,Claude 自己分析。
|
|
176
|
+
> 回測歷史資料加日期篩選:~$7/千篇($5 基本 + $2 date filter add-on)。
|
|
136
177
|
|
|
137
178
|
## 為什麼只用 Facebook?
|
|
138
179
|
|
package/dist/index.js
CHANGED
|
@@ -267,42 +267,20 @@ async function runInner(opts) {
|
|
|
267
267
|
writeFileSync(outFile, JSON.stringify({ timestamp: new Date().toISOString(), posts: newPosts, analysis }, null, 2), 'utf-8');
|
|
268
268
|
console.log(`結果已存檔: ${outFile}`);
|
|
269
269
|
}
|
|
270
|
-
/**
|
|
271
|
-
* 產生台北時間今天指定時分的 ISO 時間戳
|
|
272
|
-
* 用於 Apify onlyPostsNewerThan 參數
|
|
273
|
-
*/
|
|
274
|
-
function taipeiToday(hours, minutes = 0) {
|
|
275
|
-
const now = new Date();
|
|
276
|
-
const taipeiStr = now.toLocaleString('en-US', { timeZone: 'Asia/Taipei' });
|
|
277
|
-
const taipeiNow = new Date(taipeiStr);
|
|
278
|
-
taipeiNow.setHours(hours, minutes, 0, 0);
|
|
279
|
-
// 轉回 UTC:台北 = UTC+8
|
|
280
|
-
const utc = new Date(taipeiNow.getTime() - 8 * 60 * 60 * 1000);
|
|
281
|
-
return utc.toISOString();
|
|
282
|
-
}
|
|
283
|
-
function taipeiYesterday(hours, minutes = 0) {
|
|
284
|
-
const now = new Date();
|
|
285
|
-
const taipeiStr = now.toLocaleString('en-US', { timeZone: 'Asia/Taipei' });
|
|
286
|
-
const taipeiNow = new Date(taipeiStr);
|
|
287
|
-
taipeiNow.setDate(taipeiNow.getDate() - 1);
|
|
288
|
-
taipeiNow.setHours(hours, minutes, 0, 0);
|
|
289
|
-
const utc = new Date(taipeiNow.getTime() - 8 * 60 * 60 * 1000);
|
|
290
|
-
return utc.toISOString();
|
|
291
|
-
}
|
|
292
270
|
// ── 入口 ────────────────────────────────────────────────────
|
|
293
271
|
if (isCronMode) {
|
|
294
|
-
// 早晨補漏:每天 08:00
|
|
272
|
+
// 早晨補漏:每天 08:00
|
|
295
273
|
cron.schedule('0 8 * * *', () => {
|
|
296
|
-
run({ maxPosts: 3, isDryRun: false, label: '早晨'
|
|
274
|
+
run({ maxPosts: 3, isDryRun: false, label: '早晨' })
|
|
297
275
|
.catch((err) => console.error('[早晨] 執行失敗:', err));
|
|
298
276
|
}, { timezone: 'Asia/Taipei' });
|
|
299
|
-
// 盤中:週一到五 09:00-13:30,每 30
|
|
277
|
+
// 盤中:週一到五 09:00-13:30,每 30 分鐘
|
|
300
278
|
cron.schedule('7,37 9-12 * * 1-5', () => {
|
|
301
|
-
run({ maxPosts: 1, isDryRun: false, label: '盤中'
|
|
279
|
+
run({ maxPosts: 1, isDryRun: false, label: '盤中' })
|
|
302
280
|
.catch((err) => console.error('[盤中] 執行失敗:', err));
|
|
303
281
|
}, { timezone: 'Asia/Taipei' });
|
|
304
282
|
cron.schedule('7 13 * * 1-5', () => {
|
|
305
|
-
run({ maxPosts: 1, isDryRun: false, label: '盤中'
|
|
283
|
+
run({ maxPosts: 1, isDryRun: false, label: '盤中' })
|
|
306
284
|
.catch((err) => console.error('[盤中] 執行失敗:', err));
|
|
307
285
|
}, { timezone: 'Asia/Taipei' });
|
|
308
286
|
// 追蹤更新:週一到五 15:00(收盤後更新預測追蹤)
|
|
@@ -310,16 +288,16 @@ if (isCronMode) {
|
|
|
310
288
|
updateTracking()
|
|
311
289
|
.catch((err) => console.error('[追蹤更新] 執行失敗:', err));
|
|
312
290
|
}, { timezone: 'Asia/Taipei' });
|
|
313
|
-
// 盤後:每天晚上 23:03
|
|
291
|
+
// 盤後:每天晚上 23:03
|
|
314
292
|
cron.schedule('3 23 * * *', () => {
|
|
315
|
-
run({ maxPosts: 3, isDryRun: false, label: '盤後'
|
|
293
|
+
run({ maxPosts: 3, isDryRun: false, label: '盤後' })
|
|
316
294
|
.catch((err) => console.error('[盤後] 執行失敗:', err));
|
|
317
295
|
}, { timezone: 'Asia/Taipei' });
|
|
318
296
|
console.log('=== 巴逆逆排程已啟動 ===');
|
|
319
|
-
console.log(' 早晨:每天 08:00
|
|
320
|
-
console.log(' 盤中:週一~五 09:07/09:37/10:07/.../13:07(
|
|
297
|
+
console.log(' 早晨:每天 08:00(3 篇)');
|
|
298
|
+
console.log(' 盤中:週一~五 09:07/09:37/10:07/.../13:07(1 篇)');
|
|
321
299
|
console.log(' 追蹤更新:週一~五 15:00(預測追蹤判定)');
|
|
322
|
-
console.log(' 盤後:每天 23:03(
|
|
300
|
+
console.log(' 盤後:每天 23:03(3 篇)');
|
|
323
301
|
console.log(' 按 Ctrl+C 停止\n');
|
|
324
302
|
}
|
|
325
303
|
else {
|