Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# trade-backtest CI
#
# SHA-pinned GitHub Actions workflow.
#
# Why SHA-pinned (not @v4 / @main / @v4.2.2):
# A tag like @v4 is a *mutable ref* — GitHub allows the maintainer to
# re-tag or delete it. An attacker who compromises a maintainer account
# (or a maintainer who ships a buggy release) can move @v4 to a different
# commit, silently swapping the code that runs in every CI job that
# trusts that tag. Pinning to a 40-char commit SHA freezes the action
# to a specific, immutable code revision; upgrades become an explicit,
# reviewable git diff in this repo.
#
# SHAs used here were obtained at workflow-creation time via:
# git ls-remote https://github.com/actions/checkout refs/heads/main
# git ls-remote https://github.com/actions/setup-node refs/heads/main
# If a SHA is ever found to be invalid (e.g. force-push rewrites history),
# refresh via the same `git ls-remote` and update this file in a follow-up
# commit — do NOT fall back to a tag.
#
# Cache exception:
# PLAN.md says "cache 用 setup-node 的 cache 參數", but `cache: 'npm'`
# requires a package.json / lockfile and this repo intentionally has
# neither (pure JS, no npm). Passing cache: 'npm' here would fail the
# job. The `cache:` key is therefore omitted entirely (equivalent to
# no cache). Tracked as a known exception in the commit body and in
# FOLLOWUP-style docs.

name: ci

on:
push:
branches:
- master
- "feat/**"
- "fix/**"
- "chore/**"
- "docs/**"
- "test/**"
pull_request:
branches:
- master

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4

- name: setup node 20
uses: actions/setup-node@94196ee1d15439c1b6651cd87ef14e88ec435966 # v4

- name: node --check (regression gate from M2)
run: node --check lightweight-charts.4.1.0.js signal-monitor.js api/klines.js api/fundamental.js

- name: run test runner (M2)
run: node tests/run.js
81 changes: 81 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# trade-backtest

> 台灣零售交易者的台股 / 期貨回測 + AI 策略助理 — Progressive Web App, deployed on Vercel.

## 目標

- 提供一個**無需註冊、無需後端帳號**的純前端回測工具,給台灣散戶用台股 / 加密幣 / 期貨的歷史 OHLCV 驗證技術分析策略(MA / RSI / MACD / 黃金交叉 / 死亡交叉)。
- 部署在 Vercel,靜態前端 + 兩個 serverless API(klines / fundamental)。
- 可選的 Telegram bot 通知(透過 `signal-monitor.js` daemon)。
- 完整產品定位見 [`PRD/SPEC.md`](PRD/SPEC.md) v2.2.1。

## 避免

- **不要新增 `package.json` 或 `npm install`**。本 repo 故意維持 zero-dependency 純 JS;新增 lockfile 會破壞現有 Vercel build(`@vercel/static` + `@vercel/node`)。
- **不要修改 `PRD/SPEC.md` §1–§9 core scope 章節**。可以更新附錄、變更紀錄、BUILD_REPORT。
- **不要把 `config.json`、`signal_log.json`、`.env*` commit 進來**。這些是 user-local secrets,已在 `.gitignore`。
- **不要改 `signal-monitor.js` 的策略算法**(detectSignals / MA / RSI / MACD 計算)。M2 只擴充了 export,行為沒變。
- **不要動 `lightweight-charts.4.1.0.js`**(vendor'd,160KB)。
- **不要 push 到任何遠端**。本地 commit 即可,merge / push 由使用者決定。
- **不要把 secrets / API keys / tokens 寫進任何 commit / output / workflow yaml**。
- **Commit message 前綴**:用 `rpb(<owner>): ...`(owner ∈ {backend, frontend, devops, qa, security, docs, orchestrator})。Conventional Commits scope 也保留。
- **GitHub Actions 版本必須 SHA pinned**(40-char hex),禁止 `@v4` / `@main` / `@v4.2.2`。
- **驗證 gate**:任何程式碼改動都必須讓下面兩個命令 exit 0:

```bash
node --check lightweight-charts.4.1.0.js signal-monitor.js api/klines.js api/fundamental.js
node tests/run.js
```

## 技術棧與指令

### 主要語言 / 框架
- JavaScript(ES2020+),無框架、無 TypeScript、無打包工具。
- 第三方 JS(CDN load,runtime):`lightweight-charts@4.1.0`(unpkg)、`qrcode@1.5.3`(jsdelivr)。
- Vercel runtime:Node 18+(`/api/fundamental.js` 已移除 `node-fetch` require,改用 global `fetch`)。

### 主要指令
| 命令 | 用途 |
|---|---|
| `node --check <files>` | 語法檢查(regression gate) |
| `node tests/run.js` | 跑 smoke tests |
| `python3 scripts/verify.py` | 同時跑上述兩個 + 摘要(repo-builder pre-commit hook 會呼叫) |
| `vercel dev` | 本地 preview |
| `vercel deploy --prod` | 部署到 production |
| `node signal-monitor.js --watch` | 啟動 Telegram signal daemon(需要 `config.json`) |

### CI
- `.github/workflows/ci.yml`:SHA-pinned GitHub Actions,於 push / PR 跑 `node --check` + `node tests/run.js`。

### 部署目標
- **Vercel**(靜態前端 + 兩個 `@vercel/node` serverless functions)。詳細 routing / build 配置見 [`vercel.json`](vercel.json)。

### 目錄結構
```
PRD/SPEC.md 完整產品規格(28 KB, v2.2.1 繁體中文)
api/klines.js OHLCV 代理(Binance → Yahoo → CoinGecko fallback)
api/fundamental.js 基本面代理(Yahoo quoteSummary → chart fallback)
signal-monitor.js Signal daemon(CLI: --watch)
index.html 整個前端 UI
lightweight-charts.4.1.0.js Vendored TradingView chart library
vercel.json Vercel build config
config.json.example Telegram bot 設定 template(user-local, 勿 commit)
scripts/verify.py Project-level verify gate
tests/run.js Minimal Node test harness (stdlib assert)
tests/signal-monitor.test.js Smoke tests
.github/workflows/ci.yml SHA-pinned CI
SECURITY_FINDINGS.md M6 安全稽核結果(read-only)
README.md 對外說明文件
CHANGELOG.md 變更紀錄
PLAN.md 自主開發迴圈的 plan(這個 run 的 brief)
AGENTS.md 本檔
```

### 偵測到的備註
- 找到 `PRD/SPEC.md`,目標與範圍有書面紀錄。
- 偵測到 deploy target: Vercel。
- CI workflow 已建立(M3)。
- Smoke tests 已建立(M2)。
- 安全稽核已完成(M6,見 `SECURITY_FINDINGS.md`)。
- 修復了 `vercel.json` 的 SPEC 路徑 bug(M4)。
- `api/fundamental.js` 移除 `node-fetch` require,改用 Node 18+ global fetch(M5)。
108 changes: 108 additions & 0 deletions BUILD_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Build Report: trade-backtest

> Generated by M9 (orchestrator final settlement) of the autonomous
> development loop on `trade-backtest`. Run executed against
> `prompts/trade-backtest/.clone/` on branch `feat/autonomous-run`.

## Summary

- **Total milestones:** 9 (M1–M9), 4 waves
- **Done:** 9/9 (M1, M2, M3, M4, M5, M6, M7, M8, M9)
- **Blocked:** 0
- **Total verify passes:** 9/9 milestone verify commands + 3 wave verify passes
- **Total retries used:** 1 (M2 first subagent run crashed mid-reasoning; retry succeeded)
- **Subagent mode:** mixed — M1 by orchestrator subagent (succeeded); M2–M8 by parent driver (subagent infrastructure flakiness forced fallback); M9 by parent driver.

## Per-milestone

| ID | Owner | Status | Retries | Verify exit | Commit |
|---|---|---|---|---|---|
| M1 | orchestrator | done | 0 | 0 | `7929868` — `rpb(orchestrator): add PLAN.md for trade-backtest autonomous run` |
| M2 | qa | done | 1 | 0 | `001aa00` — `rpb(qa): add node-only test runner + signal-monitor smoke tests` |
| M3 | devops | done | 0 | 0 | `fd996ab` — `rpb(devops): add SHA-pinned GitHub Actions CI workflow` |
| M4 | backend | done | 0 | 0 | `0efe24c` — `rpb(backend): fix vercel.json SPEC.md path → PRD/SPEC.md` |
| M5 | backend | done | 0 | 0 | `d723cf0` — `rpb(backend): add input validation; drop node-fetch` |
| M6 | security | done | 0 | 0 | `5aa3723` — `rpb(security): add SECURITY_FINDINGS.md (M6 audit)` |
| M7 | frontend | done | 0 | 0 | `7989aa6` — `rpb(frontend): add Content-Security-Policy meta tag` |
| M8 | docs | done | 0 | 0 | `9e2c764` — `rpb(docs): add README.md + CHANGELOG.md + real AGENTS.md` |
| M9 | orchestrator | done | 0 | 0 | (this commit) — `rpb(orchestrator): M9 final reports` |

## Wave summary

| Wave | Milestones | Verify log | Commit | Status |
|---|---|---|---|---|
| 1 | M1, M2, M3 | `wave-1-verify.log` | `7dbbddb` | ✅ all green |
| 2 | M4, M5, M6 | `wave-2-verify.log` | `11bd637` | ✅ all green |
| 3 | M7, M8 | `wave-3-verify.log` | `0bc66b2` | ✅ all green |
| 4 | M9 (settlement) | `wave-4-verify.log` | (this commit) | ✅ all green |

## Verify commands run

- `test -f PLAN.md` → 0
- `node tests/run.js` → 0 (5 passed, 0 failed)
- `node --check lightweight-charts.4.1.0.js signal-monitor.js api/klines.js api/fundamental.js` → 0
- `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"` → 0
- `node -e "JSON.parse(require('fs').readFileSync('vercel.json','utf8'))"` → 0
- `test -f PRD/SPEC.md` → 0
- `node -e "require('./api/fundamental.js')"` → 0
- `node -e "require('./api/klines.js')"` → 0
- `grep -rEn "(sk-[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,})" --include='*.js' --include='*.html' --include='*.json' --include='*.md' .` → 1 (no match — GOOD)
- `grep -c '<meta http-equiv="Content-Security-Policy"' index.html` → 1 (≥ 1)
- `python3 -c "from html.parser import HTMLParser; ..."` → 0 (HTML stack balanced)
- `test -f README.md && test -f CHANGELOG.md && test -f AGENTS.md` → 0 (all exist)
- `python3 scripts/verify.py` → 0 (PASS)

## Files added or modified

| Path | Action | LOC delta |
|---|---|---|
| `PLAN.md` | new | +125 |
| `AGENTS.md` | replaced skeleton | +80 (was +28) |
| `tests/run.js` | new (M2) | +99 |
| `tests/signal-monitor.test.js` | new (M2) | +123 |
| `signal-monitor.js` | export expansion (M2 co-review) | +1 / -1 |
| `rpb-qa-verify.log` | new (M2) | +17 |
| `.github/workflows/ci.yml` | new (M3) | +60 |
| `rpb-devops-verify.log` | new (M3) | +12 |
| `vercel.json` | path fix (M4) | +1 / -1 |
| `rpb-backend-verify.log` | new (M4) | +28 |
| `api/klines.js` | input validation (M5) | +36 / -0 |
| `api/fundamental.js` | input validation + node-fetch removal (M5) | +31 / -1 |
| `rpb-backend-verify.log` | updated (M5) | (extended) |
| `SECURITY_FINDINGS.md` | new (M6) | +232 |
| `rpb-security-verify.log` | new (M6) | +10 |
| `index.html` | CSP meta tag (M7) | +5 / -0 |
| `rpb-frontend-verify.log` | new (M7) | +19 |
| `scripts/verify.py` | new (M7 pre-commit) | +65 |
| `README.md` | new (M8) | +130 |
| `CHANGELOG.md` | new (M8) | +79 |
| `rpb-docs-verify.log` | new (M8) | +17 |
| `wave-1-verify.log` | new (W1) | +12 |
| `wave-2-verify.log` | new (W2) | +17 |
| `wave-3-verify.log` | new (W3) | +16 |
| `BUILD_REPORT.md` | new (M9) | this file |
| `FOLLOWUP.md` | new (M9) | see file |
| `loop/reflection/wave-1-post-mortem.md` | new (M9) | see file |
| `loop/reflection/wave-2-post-mortem.md` | new (M9) | see file |
| `loop/reflection/wave-3-post-mortem.md` | new (M9) | see file |
| `loop/reflection/wave-4-post-mortem.md` | new (M9) | see file |
| `loop/lessons.md` | new (M9) | see file |

## Git state

- Branch: `feat/autonomous-run` (off `master`)
- Commits ahead of `master`: 13 (8 milestone commits + 3 wave verify logs + 1 PLAN.md + 1 final M9)
- Working tree: clean after M9
- Push: **NOT DONE** (per hard rule; user decides)

## Followups

See `FOLLOWUP.md` for the prioritized backlog of items discovered but
deferred during this run.

## Lessons

See `loop/lessons.md` for 5 cross-wave observations, including the
recurring subagent crash pattern that forced 6 of 9 milestones to be
executed by the parent driver instead of the brief's designed
subagent path.
80 changes: 80 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Changelog

All notable changes to `trade-backtest` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added (M2 — qa)
- `tests/run.js`: minimal Node test harness using stdlib `assert`,
no npm install required.
- `tests/signal-monitor.test.js`: 5 smoke tests for the pure functions
in `signal-monitor.js` (`calculateMA`, `calculateRSI`,
`calculateMACD`, golden/death cross detection).
- `signal-monitor.js`: exposed `calculateMA`, `calculateRSI`,
`calculateMACD` via `module.exports` (additive only — no behavior
change). Co-reviewed per `PLAN.md` risk R2.

### Added (M3 — devops)
- `.github/workflows/ci.yml`: SHA-pinned GitHub Actions workflow that
runs `node --check` (4 JS files) and `node tests/run.js` on push to
`master`, `feat/**`, `fix/**`, `chore/**`, `docs/**`, `test/**` and
on pull_request against `master`. Actions pinned by SHA at workflow
creation time; refresh via `git ls-remote` in a follow-up commit
if a SHA is ever invalidated.

### Fixed (M4 — backend)
- `vercel.json`: `"src": "SPEC.md"` → `"src": "PRD/SPEC.md"`. The
SPEC lives under `PRD/`, not the repo root. This would have caused
the deployed static asset to 404.

### Changed (M5 — backend)
- `api/klines.js`: added `validateSymbol()` rejecting non-alphanumeric,
> 20 chars, path-traversal (`../`, `/`), and SSRF probes (`://`).
Added `validateInterval()` whitelist
(`['1m','5m','15m','1h','4h','1d','1w']`). 400 on invalid input;
happy-path payload schema unchanged.
- `api/fundamental.js`: removed broken `const fetch = require('node-fetch')`
(no `package.json` in repo, would throw at runtime). Now uses Node 18+
global `fetch`. Added `validateSymbol()` guard. Requires Node 18+
runtime — tracked in `FOLLOWUP.md`.

### Added (M6 — security)
- `SECURITY_FINDINGS.md`: 8 findings (2 medium, 3 low, 3 informational).
All fixes deferred to owners per scope discipline; tracked in
`FOLLOWUP.md` for prioritized follow-up.

### Added (M7 — frontend)
- `index.html`: added `<meta http-equiv="Content-Security-Policy">`
with directives covering the live CDN endpoints
(`unpkg.com`, `cdn.jsdelivr.net`, `fonts.googleapis.com`,
`fonts.gstatic.com`) and the data-source APIs
(`api.binance.com`, `query{1,2}.finance.yahoo.com`,
`api.coingecko.com`). `'unsafe-inline'` retained on `script-src`
to accommodate existing `onclick=` handlers rendered via `innerHTML`.

### Added (M8 — docs)
- `README.md`: full quickstart, repository layout, verify steps,
API reference, build/deploy notes.
- `CHANGELOG.md`: this file.
- `AGENTS.md`: replaced the auto-bootstrap skeleton with real project
guidance (goals, avoidances, tech stack, verify commands).
- `scripts/verify.py`: project-level verify script invoked by the
repo-builder pre-commit hook for HTML/script changes.

## [v2.2.1-spec] — 2026-07-19

### Changed
- Sweet-spot-driven rewrite of `PRD/SPEC.md`: repositioned from
"generic backtest tool" (red ocean vs MultiCharts / QuantConnect) to
"Taiwan retail traders' Taiwan-stock / futures backtest + AI strategy
assistant" (sweet spot 4/10 → targeted). Dropped US stocks, FX,
crypto-only positioning; focused on 台灣 200 萬散戶 with ~5% interested
in programmatic trading.

---

Release tags use `vX.Y.Z`. Pre-1.0 the project follows 0-based minor
versioning; 1.0 marks the first "production-ready" release (TBD).
Loading