Add per-agent skill workspaces and TraderView management

This commit is contained in:
2026-03-17 13:55:14 +08:00
parent 1f5ee3698e
commit 2daf5717ba
35 changed files with 4774 additions and 331 deletions

View File

@@ -1,21 +1,31 @@
---
name: valuation_review
description: Estimate fair value and margin of safety using multiple valuation lenses.
name: 估值分析
description: 当用户要求“估值分析”“看合理价值”“判断高估低估”“测算安全边际”或“比较多种估值方法”时,应使用此技能。
version: 1.0.0
---
# Valuation Review
# 估值分析
Use this skill when the task requires determining whether a stock is cheap, expensive, or fairly priced.
当用户需要判断一只股票是低估、高估还是定价合理时,使用这个技能。
## Workflow
## 工作流程
1. Use more than one valuation method when possible.
2. Compare intrinsic value estimates with current market pricing.
3. Explain the key assumptions behind the valuation view.
4. State the margin of safety and what could compress or expand it.
1. 条件允许时,使用不止一种估值方法。
2. 对比内在价值估计与当前市场价格。
3. 解释估值判断背后的关键假设。
4. 明确安全边际,以及哪些因素会压缩或扩大它。
## Guardrails
## 可复用资源
- Treat valuation as a range, not a single precise number.
- Call out assumption sensitivity.
- Avoid high-confidence calls when inputs are sparse or unstable.
- `scripts/dcf_report.py`
用于贴现现金流估值的确定性计算和报告生成。
- `scripts/owner_earnings_report.py`
用于 owner earnings 估值的确定性计算和报告生成。
- `scripts/multiple_valuation_report.py`
用于 EV/EBITDA 和 Residual Income 两类估值报告生成。
## 约束
- 将估值视为区间,而不是一个精确点值。
- 明确说明假设敏感性。
- 当输入稀疏或不稳定时,避免给出高置信度判断。

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Deterministic DCF report helpers for the valuation_review skill."""
from __future__ import annotations
import json
from typing import Iterable
def build_dcf_report(rows: Iterable[dict], current_date: str) -> str:
"""Render a DCF valuation report from normalized row inputs."""
lines = [f"=== DCF Valuation Analysis ({current_date}) ===\n"]
for row in rows:
error = row.get("error")
ticker = row["ticker"]
if error:
lines.append(f"{ticker}: {error}\n")
continue
current_fcf = float(row["current_fcf"])
growth_rate = float(row["growth_rate"])
market_cap = float(row["market_cap"])
discount_rate = float(row.get("discount_rate", 0.10))
terminal_growth = float(row.get("terminal_growth", 0.03))
num_years = int(row.get("num_years", 5))
pv_fcf = sum(
current_fcf
* (1 + growth_rate) ** year
/ (1 + discount_rate) ** year
for year in range(1, num_years + 1)
)
terminal_fcf = (
current_fcf
* (1 + growth_rate) ** num_years
* (1 + terminal_growth)
)
terminal_value = terminal_fcf / (discount_rate - terminal_growth)
pv_terminal = terminal_value / (1 + discount_rate) ** num_years
enterprise_value = pv_fcf + pv_terminal
value_gap = (enterprise_value - market_cap) / market_cap * 100
if value_gap > 20:
assessment = "SIGNIFICANTLY UNDERVALUED"
elif value_gap > 0:
assessment = "POTENTIALLY UNDERVALUED"
elif value_gap > -20:
assessment = "POTENTIALLY OVERVALUED"
else:
assessment = "SIGNIFICANTLY OVERVALUED"
lines.append(f"{ticker}:")
lines.append(f" Current FCF: ${current_fcf:,.0f}")
lines.append(f" DCF Enterprise Value: ${enterprise_value:,.0f}")
lines.append(f" Market Cap: ${market_cap:,.0f}")
lines.append(f" Value Gap: {value_gap:+.1f}% -> {assessment}")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Read normalized rows from stdin and emit a text report."""
payload = json.load(__import__("sys").stdin)
print(build_dcf_report(payload["rows"], payload["current_date"]))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Deterministic multiple-based valuation helpers for the valuation_review skill."""
from __future__ import annotations
import json
from typing import Iterable
def build_ev_ebitda_report(rows: Iterable[dict], current_date: str) -> str:
"""Render an EV/EBITDA valuation report from normalized row inputs."""
lines = [f"=== EV/EBITDA Valuation ({current_date}) ===\n"]
for row in rows:
error = row.get("error")
ticker = row["ticker"]
if error:
lines.append(f"{ticker}: {error}\n")
continue
current_multiple = float(row["current_multiple"])
median_multiple = float(row["median_multiple"])
current_ebitda = float(row["current_ebitda"])
market_cap = float(row["market_cap"])
net_debt = float(row["net_debt"])
implied_ev = median_multiple * current_ebitda
implied_equity = max(implied_ev - net_debt, 0.0)
value_gap = (
(implied_equity - market_cap) / market_cap * 100
if market_cap > 0
else 0.0
)
multiple_discount = (
(median_multiple - current_multiple) / median_multiple * 100
)
if multiple_discount > 10:
assessment = "TRADING BELOW HISTORICAL MULTIPLE"
elif multiple_discount > -10:
assessment = "NEAR HISTORICAL AVERAGE"
else:
assessment = "TRADING ABOVE HISTORICAL MULTIPLE"
lines.append(f"{ticker}:")
lines.append(f" Current EV/EBITDA: {current_multiple:.1f}x")
lines.append(f" Historical Median: {median_multiple:.1f}x")
lines.append(f" Multiple vs History: {multiple_discount:+.1f}%")
lines.append(f" Implied Equity Value: ${implied_equity:,.0f}")
lines.append(f" Value Gap: {value_gap:+.1f}% -> {assessment}")
lines.append("")
return "\n".join(lines)
def build_residual_income_report(rows: Iterable[dict], current_date: str) -> str:
"""Render a residual income valuation report from normalized row inputs."""
lines = [f"=== Residual Income Valuation ({current_date}) ===\n"]
for row in rows:
error = row.get("error")
ticker = row["ticker"]
if error:
lines.append(f"{ticker}: {error}\n")
continue
book_value = float(row["book_value"])
initial_ri = float(row["initial_ri"])
market_cap = float(row["market_cap"])
cost_of_equity = float(row.get("cost_of_equity", 0.10))
bv_growth = float(row.get("bv_growth", 0.03))
terminal_growth = float(row.get("terminal_growth", 0.03))
num_years = int(row.get("num_years", 5))
margin_of_safety = float(row.get("margin_of_safety", 0.20))
pv_ri = sum(
initial_ri * (1 + bv_growth) ** year / (1 + cost_of_equity) ** year
for year in range(1, num_years + 1)
)
terminal_ri = initial_ri * (1 + bv_growth) ** (num_years + 1)
terminal_value = terminal_ri / (cost_of_equity - terminal_growth)
pv_terminal = terminal_value / (1 + cost_of_equity) ** num_years
intrinsic_value = (book_value + pv_ri + pv_terminal) * (
1 - margin_of_safety
)
value_gap = (intrinsic_value - market_cap) / market_cap * 100
lines.append(f"{ticker}:")
lines.append(f" Book Value: ${book_value:,.0f}")
lines.append(f" Residual Income: ${initial_ri:,.0f}")
lines.append(
f" Intrinsic Value (w/ 20% MoS): ${intrinsic_value:,.0f}",
)
lines.append(f" Value Gap: {value_gap:+.1f}%")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Read normalized rows from stdin and emit one selected text report."""
payload = json.load(__import__("sys").stdin)
mode = payload["mode"]
if mode == "ev_ebitda":
print(build_ev_ebitda_report(payload["rows"], payload["current_date"]))
return
if mode == "residual_income":
print(build_residual_income_report(payload["rows"], payload["current_date"]))
return
raise ValueError(f"Unsupported mode: {mode}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Deterministic owner earnings valuation helpers for the valuation_review skill."""
from __future__ import annotations
import json
from typing import Iterable
def build_owner_earnings_report(rows: Iterable[dict], current_date: str) -> str:
"""Render an owner earnings valuation report from normalized row inputs."""
lines = [f"=== Owner Earnings Valuation ({current_date}) ===\n"]
for row in rows:
error = row.get("error")
ticker = row["ticker"]
if error:
lines.append(f"{ticker}: {error}\n")
continue
owner_earnings = float(row["owner_earnings"])
growth_rate = float(row["growth_rate"])
market_cap = float(row["market_cap"])
required_return = float(row.get("required_return", 0.15))
margin_of_safety = float(row.get("margin_of_safety", 0.25))
num_years = int(row.get("num_years", 5))
pv_earnings = sum(
owner_earnings
* (1 + growth_rate) ** year
/ (1 + required_return) ** year
for year in range(1, num_years + 1)
)
terminal_growth = min(growth_rate, 0.03)
terminal_earnings = (
owner_earnings
* (1 + growth_rate) ** num_years
* (1 + terminal_growth)
)
terminal_value = terminal_earnings / (
required_return - terminal_growth
)
pv_terminal = terminal_value / (1 + required_return) ** num_years
intrinsic_value = (pv_earnings + pv_terminal) * (1 - margin_of_safety)
value_gap = (intrinsic_value - market_cap) / market_cap * 100
if value_gap > 20:
assessment = "SIGNIFICANTLY UNDERVALUED"
elif value_gap > 0:
assessment = "POTENTIALLY UNDERVALUED"
elif value_gap > -20:
assessment = "POTENTIALLY OVERVALUED"
else:
assessment = "SIGNIFICANTLY OVERVALUED"
lines.append(f"{ticker}:")
lines.append(f" Owner Earnings: ${owner_earnings:,.0f}")
lines.append(
f" Intrinsic Value (w/ 25% MoS): ${intrinsic_value:,.0f}",
)
lines.append(f" Market Cap: ${market_cap:,.0f}")
lines.append(f" Value Gap: {value_gap:+.1f}% -> {assessment}")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Read normalized rows from stdin and emit a text report."""
payload = json.load(__import__("sys").stdin)
print(build_owner_earnings_report(payload["rows"], payload["current_date"]))
if __name__ == "__main__":
main()