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
46 changes: 46 additions & 0 deletions .zcodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
env/
__pycache__/
.DS_Store
*.csv
src/
eval_results/
eval_data/
*.bak
*.egg-info/
.env
data/
.opencode/
.zcode/
.venv-p2/
build/
tradingagents/dataflows/data_cache/

# ===== ↑ 以上同步自 .gitignore(「从 .gitignore 同步」只重写以上部分)=====
.git/
.hg/
.svn/
node_modules/
bower_components/
jspm_packages/
site-packages/
venv/
coverage/
htmlcov/
lcov-report/
cmakefiles/
cmake-build-*/
bazel-*/
pods/
deriveddata/
storybook-static/
playwright-report/
test-results/
allure-results/
allure-report/
cdk.out/
*.dist-info/
eggs/
pip-wheel-metadata/
wheels/
# ----- ↑ 以上为 ZCode 默认排除规则(自定义规则请写在本行下方,不会被同步/恢复改动)-----
# 自定义规则写在下方(本行提示可删除)
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Traders 的用途是**研究、驗證與記錄一套交易決策流程**,而
flowchart TD
A[市場行情、新聞、社群、基本面、SEC/IR、總經濟] --> B[資料介面與交易日曆]
B --> C[Deterministic Screening]
C --> D[Top40 → LLM Screening → Top20]
C --> D[風險門檻 → 存活池公式排序 → 最多20隻]
D --> E[Analysis Roles]
E --> F[Bull / Bear / Research Manager]
F --> G[Trader Proposal]
Expand Down Expand Up @@ -170,7 +170,7 @@ flowchart TD
- 可使用 yfinance 作為部分資料 fallback。
- 手動 watchlist 或全市場 US equity auto-screening。
- 以完整交易日、價格、成交額、報酬、波動、量能與市值做確定性 gate。
- 對 Top40 使用獨立 Screening role,輸出嚴格驗證的 Top20 schema。
- 做多預設使用報告的剔除門檻與公式,直接產生最多 20 隻;可切回 Top40 → Screening LLM 的舊模式。
- 當日 selection cache 以日期、設定 fingerprint 與 SHA-256 seal 驗證完整性。

### Durable execution 與 recovery
Expand Down Expand Up @@ -421,9 +421,13 @@ Auto-screening 不是「自動買入清單」,而是決定研究資源優先
- 價格至少 US$5。
- 20 日平均成交額至少 US$20M。
- 市值門檻預設 US$300M;缺少或無效市值時 fail closed。
- 先以流動性、報酬、波動與量能等確定性因子產生 Top40。
- 再由獨立 Screening role 從 Top40 產生嚴格 Top20。
- 既有持倉即使跌出 Top20,仍可加入研究集合,以檢查企業論點是否失效。
- 做多預設再要求 `vol20 <= 0.28`、`trend > 0`、`r60 >= -0.25`。
- 對存活股票計算 `100 * (0.50*p(trend) + 0.30*p(r60) + 0.20*(1-p(vol20)))`,由高到低選最多 20 隻。百分位只在存活池內計算;同值平均排名,同分按 ticker 排列。
- 合格候選或產業容量不足時允許少於 20 隻,不放寬門檻補數;零候選時只審查持倉。
- 既有可安全分析的美股持倉優先占用 20 隻的分析預算,再用候選補滿。超過 20 隻持倉則明確停止,不靜默遺漏持倉;被擠出的候選記為 deferred。
- 做多公式的篩選階段不呼叫 LLM。每輪保存的分析集合在重啟時凍結恢復,波動等數值隨 as-of 日期送入 Risk Manager。
- `screening_method="legacy"` 保留舊價格公式與 Screening LLM;`auto` 在 `allow_shorts=True` 時使用雙向候選。兩者都先為持倉預留名額,篩選 LLM 與深度分析每帳戶每輪共用最多 20 檔;候選不足允許少選。明確指定 `exclusion` 加做空會拒絕啟動。
- 詳細設定、快取相容性與驗證見 [公式接入說明](docs/EXCLUSION_SCREENING.md)。
- 當日 selection cache 會綁定日期、設定 fingerprint、資料 feed 與完整性 seal;不合法或過期時拒絕沿用。

這些規則可以降低資料與流程風險,但不能自動證明某個標的具有未來報酬。
Expand Down
46 changes: 26 additions & 20 deletions cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,9 @@ def _ask_role(role_label):
console.print(
create_question_box(
"Step 5c: Auto Screening (optional)",
"Enable the daily full-market screen? Screening derives a "
"validated Top20 and analyzes Top20 plus current holdings; "
"Enable the daily full-market screen? Screening selects "
"up to 20 candidates using the report formula; current holdings "
"take priority within the 20-stock analysis limit; "
"leave off to analyze the manual ticker above.",
"no",
)
Expand All @@ -551,25 +552,18 @@ def _ask_role(role_label):
if typer.confirm("Enable auto screening?", default=False):
from tradingagents.screening.llm import resolve_screening_config

screening_provider = (
typer.prompt("Screening provider", default="", show_default=False).strip() or None
)
screening_model = (
typer.prompt("Screening model", default="", show_default=False).strip() or None
)
screening_url = (
typer.prompt(
"Screening endpoint override (empty = provider default)",
default="",
show_default=False,
).strip()
or None
)
legacy = typer.confirm("Use legacy LLM screening instead of the report formula?", default=False)
screening_provider = screening_model = screening_url = None
if legacy:
screening_provider = typer.prompt("Screening provider", default="", show_default=False).strip() or None
screening_model = typer.prompt("Screening model", default="", show_default=False).strip() or None
screening_url = typer.prompt("Screening endpoint override (empty = provider default)", default="", show_default=False).strip() or None
screening_refresh = typer.confirm(
"Refresh: force a new scan even if today's selection exists?", default=False
)
screening_settings = {
"auto_screening_enabled": True,
"screening_method": "legacy" if legacy else "auto",
"screening_provider": screening_provider,
"screening_model": screening_model,
"screening_backend_url": screening_url,
Expand Down Expand Up @@ -842,6 +836,7 @@ def run_analysis():
"decision_model",
"decision_backend_url",
"auto_screening_enabled",
"screening_method",
"screening_provider",
"screening_model",
"screening_backend_url",
Expand All @@ -864,6 +859,8 @@ def run_analysis():
)
return
_display_screening_plan(plan)
from tradingagents.screening.context import screening_context_from_selection
config["_screening_context"] = screening_context_from_selection(plan.selection)
tickers = plan.deep_analysis_set
else:
tickers = [selections["ticker"]]
Expand All @@ -883,6 +880,9 @@ def _display_screening_plan(plan):
f"{'cached' if plan.cached else 'fresh scan'})"
)
table = Table(title="Top20 (research priority)")
quality = (plan.selection or {}).get("data_quality") or {}
if quality:
console.print(f"Input quality: {quality.get('counts', {})}; financial/business quality: unknown")
table.add_column("Rank", justify="right")
table.add_column("Symbol")
table.add_column("Score", justify="right")
Expand All @@ -895,6 +895,8 @@ def _display_screening_plan(plan):
str(entry.get("short_reason", ""))[:120],
)
console.print(table)
if plan.deferred_candidates:
console.print("Deferred by the analysis limit: " + ", ".join(plan.deferred_candidates))
if plan.overlap_holdings:
console.print(f"Top20 ∩ holdings: {', '.join(plan.overlap_holdings)}")
if plan.extra_holdings:
Expand Down Expand Up @@ -1459,7 +1461,8 @@ def _ask_url(role: str) -> None:
cfg[f"{role}_backend_url"] = typer.prompt(
f"{role} backend URL (required for {provider})").strip()

for _role in ("analysis", "decision", "screening"):
from tradingagents.screening.policy import screening_method
for _role in ("analysis", "decision") + (("screening",) if screening_method(cfg) == "legacy" else ()):
_ask_provider(_role)
_ask_model(_role)
_ask_url(_role)
Expand Down Expand Up @@ -1582,7 +1585,8 @@ def _run_time_valid(value) -> bool:
from tradingagents.llm_clients.roles import _resolve_provider_key

seen_providers: dict[str, str] = {}
for _role in ("analysis", "decision", "screening"):
from tradingagents.screening.policy import screening_method
for _role in ("analysis", "decision") + (("screening",) if screening_method(cfg) == "legacy" else ()):
provider = str(cfg.get(f"{_role}_provider") or "").lower()
seen_providers.setdefault(provider, _role)
for provider, _role in seen_providers.items():
Expand Down Expand Up @@ -1895,7 +1899,8 @@ def _apply_overrides(cfg: dict) -> None:
f"| notional ${float(cfg['base_trade_notional_usd']):,.0f}")
console.print(f"Analysts: {', '.join(cfg['analysts'])} "
f"| depth {cfg['research_depth']} | {cfg['output_language']}")
for _role in ("analysis", "decision", "screening"):
from tradingagents.screening.policy import screening_method
for _role in ("analysis", "decision") + (("screening",) if screening_method(cfg) == "legacy" else ()):
console.print(f"{_role}: {cfg[f'{_role}_provider']}/{cfg[f'{_role}_model']} "
f"endpoint={lr.sanitize_url(cfg.get(f'{_role}_backend_url')) or 'provider default'}")
if cfg.get("analysis_fallback_provider") and cfg.get("analysis_fallback_model"):
Expand Down Expand Up @@ -2181,9 +2186,10 @@ def _long_run_ab_locked(overrides: dict, *, resume: str | None = None) -> None:
console.print(f"[bold red]A/B startup accounts are not comparable: {sanitize_for_log(str(exc))}[/bold red]")
raise typer.Exit(code=1)

from tradingagents.screening.policy import screening_method
console.print("\n[bold]Full-market A/B Paper observation[/bold]")
console.print(f"Duration: {cfg['duration_calendar_days']} calendar days | daily at {cfg['run_time_et']} ET")
console.print(f"Shared screening: US equity universe → Top40 → Top20 | paper notional: ${float(cfg['base_trade_notional_usd']):,.0f}")
console.print(f"Shared screening: {screening_method(cfg)} | up to {runtime.get('screening_select_n', 20)} candidates | paper notional: ${float(cfg['base_trade_notional_usd']):,.0f}")
console.print(f"Traders account A: {account_refs['traders']} | Berkshire account B: {account_refs['berkshire']}")
console.print("Both arms use the same Trader, risk, sizing, execution, and short policies; only the analysis backend changes.")
if not typer.confirm("Authorize this full-market A/B PAPER test? The process must stay running; rerun `python -m cli.main long-run --mode ab` after a crash.", default=False):
Expand Down
34 changes: 34 additions & 0 deletions docs/DATA_QUALITY_GATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 篩選資料可信度與研究停止條件

2026-09-29。目標是最多20檔供LLM分析、輸入可追溯、未知不變成零。價格/身分通過不表示公司品質好或策略能獲利。現有公式與門檻保持原樣。

## 正式篩選

掃描在請求價格前檢查股票代號、broker asset_id、來源、active/tradable US_EQUITY及重複身分。衝突的所有列都停用。Alpaca資產ID只證明當前商品快照,沒有證明歷史發行公司/CIK或普通股類型。

價格要求可識別來源、SIP量、同一調整政策;61個完整權威交易日;OHLCV有限、價格正、成交量非負、low<=open/close<=high。缺日、缺欄、不合理值不補零、不向前填、不改as_of。市值必須有來源及有限正值,沿用既有最低市值/價格/ADV20門檻。

每個原始宇宙列記錄理由及分類:`data_gap`(資料缺口)、`data_error`(矛盾/不合理)、`eligibility`(資格不足)、`risk_policy`(原風險規則)、`usable`。這些分類不是好/爛公司的標籤。財報及企業品質明確為unknown,當前價格篩選不使用財報公式。

每日SelectionStore帶有`data_quality`。schema 7使舊快取失效;資料品質報告、來源、身分、市值與因子不一致的快取/凍結A/B名單不可用。入選最多20檔另保留完整61日OHLCV;讀取時核對hash、價格一致性並重算因子。未入選列保留排除理由或量測摘要/窗口hash。

已完成掃描在每日快取旁的`screening_snapshots/<交易日>-<hash>.json`留存完整選股與品質快照。每日快取替換不刪舊快照;快照寫入失敗停止該輪。這是可重播的本地紀錄,來源標籤及self-hash不是第三方數據正確性的保證。

持倉風險覆核、做多、做空共用每帳戶每輪最多20個LLM股票名額,不足不補。持倉不會自動被當成通過新股價格門檻;其未取得的量測明確不可推定。長跑journal保存品質摘要、因子context及快照路徑。舊policy/fingerprint不允許續跑新的分析,已授權execution-only recovery維持既有保護。

## 財報與歷史研究

`assess_financial_facts`接收已正規化的`net_income`、`operating_cash_flow`、`assets`,保留原始tag與來源。必須有值、單位、期間、qtrs、可取得日期及合併範圍;缺證據回傳unknown,明確矛盾回傳invalid。函式不猜custom tag,不把不同歸屬範圍湊成比率。聲明相同scope只是合同的一項;原文審查與來源查核仍需要另外完成。

歷史收益證明需要歷史商品身分、下市結果、價格調整/股息、當時可取得時間;使用財報再需期間/單位/合併範圍,做空另需歷史券源及成本。`historical_research_gate`對適用且未驗證項回傳STOPPED。READY僅表示輸入準備好,不表示alpha成立。

本次 [缺口報告](/Users/zongen/Downloads/codex/tradingBuffett/research/out/20260929-data-trust/REPORT.md) 分開保存真實SEC資料盤點與合成的正式流程示範,沒有新回測或參數搜尋。只讀既有2015–2020財報及已允許的2016–2020價格輸出。2021–2025研究價格保持封存。

```bash
.venv-p2/bin/python research/out/20260929-data-trust/audit.py
# 歷史研究准入檢查:目前預期exit 2(STOPPED),不是成功驗證策略。
.venv-p2/bin/python research/out/20260929-data-trust/audit.py --validate-for-research
.venv-p2/bin/python -m pytest tests/test_screening_data_quality.py -q
```

舊研究檔保留供審查/重現;本次沒有把停止條件改成收益門檻,也沒有禁止手動重現舊實驗。新的歷史收益結論必須先列出適用資料證據,通過准入後另外使用未反覆研究的樣本。
Loading
Loading