Initial commit of integrated agent system
This commit is contained in:
119
backend/skills/SKILL_TEMPLATE.md
Normal file
119
backend/skills/SKILL_TEMPLATE.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Skill Template (Anthropic + AgentScope Aligned)
|
||||
|
||||
> 用于定义可执行、可路由、可评估的技能规范。
|
||||
> 建议所有 `SKILL.md` 至少覆盖以下 6 个部分。
|
||||
|
||||
---
|
||||
|
||||
## Frontmatter Spec
|
||||
|
||||
All `SKILL.md` files should begin with a YAML frontmatter block:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill_name # Required. Unique identifier for the skill.
|
||||
description: ... # Required. One-line description of the skill.
|
||||
version: "1.0.0" # Optional. Semantic version string.
|
||||
tools: [...] # Optional. Tools provided or used by this skill.
|
||||
allowed_tools: [...] # Optional. List of tool names permitted when this skill is active.
|
||||
denied_tools: [...] # Optional. List of tool names denied when this skill is active.
|
||||
---
|
||||
```
|
||||
|
||||
### Frontmatter Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | string | Unique skill identifier (kebab-case recommended). |
|
||||
| `description` | string | Human-readable one-line description. |
|
||||
| `version` | string | Semantic version (e.g., `"1.0.0"`). |
|
||||
| `tools` | list[string] | Tools provided by or associated with this skill. |
|
||||
| `allowed_tools` | list[string] | Enumerates which tools are **permitted** when this skill is active. If set, only these tools may be used. |
|
||||
| `denied_tools` | list[string] | Enumerates which tools are **forbidden** when this skill is active. Denied tools take precedence over `allowed_tools`. |
|
||||
|
||||
### Tool Restriction Rules
|
||||
|
||||
- If **only** `allowed_tools` is set: only those tools are accessible.
|
||||
- If **only** `denied_tools` is set: all tools except those are accessible.
|
||||
- If **both** are set: `allowed_tools` defines the initial set, then `denied_tools` removes from it.
|
||||
- **Denial takes precedence**: a tool in `denied_tools` is always blocked even if also in `allowed_tools`.
|
||||
|
||||
---
|
||||
|
||||
## 1) When to use
|
||||
|
||||
- 明确触发条件(任务类型、关键词、场景)。
|
||||
- 明确不应使用该技能的边界(避免误触发)。
|
||||
|
||||
## 2) Required inputs
|
||||
|
||||
- 列出最小必要输入(如 `tickers`、价格、组合状态、风险约束)。
|
||||
- 声明输入缺失时的处理规则(终止 / 降级 / 请求补充)。
|
||||
|
||||
## 3) Decision procedure
|
||||
|
||||
- 采用固定步骤,确保可复现。
|
||||
- 每一步说明目标、判据和产物(例如中间结论)。
|
||||
- 标明冲突处理逻辑(信号冲突、数据冲突、置信度冲突)。
|
||||
|
||||
## 4) Tool call policy
|
||||
|
||||
- 说明优先使用哪些工具组与工具。
|
||||
- 规定何时可以“无工具直接结论”,何时必须工具先证据后结论。
|
||||
- 规定工具失败、超时、返回异常时的替代动作。
|
||||
|
||||
## 5) Output schema
|
||||
|
||||
- 定义标准输出字段,便于下游 Agent 消费与评估。
|
||||
- 推荐包含:`signal`、`confidence`、`reasons`、`risks`、`invalidation`、`next_action`。
|
||||
- 若是组合决策技能,必须包含每个 ticker 的 `action` 与 `quantity`。
|
||||
|
||||
## 6) Failure fallback
|
||||
|
||||
- 规定在数据不足、信号冲突、风险超限、工具不可用时的降级策略。
|
||||
- 默认优先“保守 + 可解释 + 可执行”的输出。
|
||||
|
||||
## Optional: Evaluation hooks
|
||||
|
||||
定义技能的可评估指标,用于后续记忆/反思阶段写入长期经验。
|
||||
|
||||
### 支持的指标类型
|
||||
|
||||
| 指标类型 | 描述 | 适用技能 |
|
||||
|---------|------|---------|
|
||||
| `hit_rate` | 信号命中率 - 决策信号与实际结果的符合程度 | sentiment_review, technical_review |
|
||||
| `risk_violation` | 风控违例率 - 触发风控规则的次数 | risk_review, portfolio_decisioning |
|
||||
| `position_deviation` | 仓位偏离率 - 建议仓位与实际执行仓位的偏差 | portfolio_decisioning |
|
||||
| `pnl_attribution` | P&L 归因一致性 - 收益归因与实际收益的匹配度 | fundamental_review, valuation_review |
|
||||
| `signal_consistency` | 信号一致性 - 多来源信号的一致程度 | sentiment_review |
|
||||
| `decision_latency` | 决策延迟 - 从输入到决策的耗时 | portfolio_decisioning |
|
||||
| `tool_usage` | 工具使用率 - 工具调用次数与成功率的比值 | 所有技能 |
|
||||
| `custom` | 自定义指标 | 特定业务场景 |
|
||||
|
||||
### 使用方式
|
||||
|
||||
```python
|
||||
from backend.agents.base.evaluation_hook import EvaluationHook, MetricType
|
||||
|
||||
# 在技能执行开始时
|
||||
evaluation_hook.start_evaluation(
|
||||
skill_name="technical_review",
|
||||
inputs={"tickers": ["AAPL"], "prices": {...}}
|
||||
)
|
||||
|
||||
# 在技能执行过程中添加指标
|
||||
evaluation_hook.add_metric(
|
||||
name="signal_confidence",
|
||||
metric_type=MetricType.HIT_RATE,
|
||||
value=0.85,
|
||||
metadata={"method": "rsi", "threshold": 30}
|
||||
)
|
||||
|
||||
# 在技能完成时记录结果
|
||||
evaluation_hook.record_outputs({"signal": "buy", "confidence": 0.8})
|
||||
evaluation_hook.complete_evaluation(success=True)
|
||||
```
|
||||
|
||||
### 评估结果存储
|
||||
|
||||
评估结果自动保存到 `runs/{run_id}/evaluations/{agent_id}/{skill_name}_{timestamp}.json`
|
||||
1
backend/skills/__init__.py
Normal file
1
backend/skills/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
1
backend/skills/builtin/__init__.py
Normal file
1
backend/skills/builtin/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
49
backend/skills/builtin/fundamental_review/SKILL.md
Normal file
49
backend/skills/builtin/fundamental_review/SKILL.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: 基本面分析
|
||||
description: 当用户要求“基本面分析”“看财务质量”“分析盈利能力”“判断公司质量”或“评估长期盈利韧性”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 基本面分析
|
||||
|
||||
当用户希望从公司质量、资产负债表强度、盈利能力或长期盈利韧性出发判断标的时,使用这个技能。
|
||||
|
||||
## 1) When to use
|
||||
|
||||
- 适用于需要判断“公司基本面质量是否支撑当前估值/交易观点”的任务。
|
||||
- 优先在中长期视角下使用(财务稳健性、盈利韧性、成长持续性)。
|
||||
- 当任务明确以短线事件驱动为主时,不应单独依赖本技能,应与情绪/技术信号联合。
|
||||
|
||||
## 2) Required inputs
|
||||
|
||||
- 最少输入:`tickers`、关键财务指标(盈利、成长、偿债、效率)。
|
||||
- 推荐输入:行业背景、公司阶段、近期重大事件。
|
||||
- 若关键数据缺失(例如利润质量或现金流质量无法判断),必须在结论中显式标注“不足信息风险”,并降低置信度。
|
||||
|
||||
## 3) Decision procedure
|
||||
|
||||
1. 先做四维诊断:盈利能力、成长质量、财务健康度、经营效率。
|
||||
2. 区分“结构性优势”与“周期性改善/短期噪音”。
|
||||
3. 识别关键风险与失效条件(invalidation),明确什么情况会推翻当前判断。
|
||||
4. 合成最终观点:`signal + confidence + drivers + risks`。
|
||||
|
||||
## 4) Tool call policy
|
||||
|
||||
- 优先使用基本面与财务相关工具组获取证据,再形成结论。
|
||||
- 在数据完备且任务允许时,可补充估值相关工具进行交叉验证。
|
||||
- 若工具失败或返回异常:保留已验证证据,明确未验证部分,不允许伪造数据。
|
||||
|
||||
## 5) Output schema
|
||||
|
||||
- `signal`: `bullish | bearish | neutral`
|
||||
- `confidence`: `0-100`
|
||||
- `reasons`: 2-4 条核心驱动
|
||||
- `risks`: 1-3 条关键风险
|
||||
- `invalidation`: 触发观点失效的条件
|
||||
- `next_action`: 对 PM 的可执行建议(如“仅小仓位试错/等待下一季报确认”)
|
||||
|
||||
## 6) Failure fallback
|
||||
|
||||
- 数据稀疏或矛盾时:默认 `neutral` 或低置信度方向结论。
|
||||
- 不允许因单一亮点指标给出高置信度信号。
|
||||
- 当财务质量优劣混杂时,优先保守结论并附加“需补充验证”的下一步建议。
|
||||
50
backend/skills/builtin/portfolio_decisioning/SKILL.md
Normal file
50
backend/skills/builtin/portfolio_decisioning/SKILL.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: 组合决策
|
||||
description: 当用户要求“组合决策”“给出最终仓位”“整合分析结论”“输出交易决策”或“形成组合操作方案”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 组合决策
|
||||
|
||||
当用户需要把团队分析转化为最终交易决策时,使用这个技能。
|
||||
|
||||
## 1) When to use
|
||||
|
||||
- 适用于“最终下单前”的收口阶段:将多方观点转成单一可执行指令。
|
||||
- 必须在获取分析师观点与风险审查后触发,不应跳过上游输入。
|
||||
- 当任务只要求研究观点、未要求执行决策时,不强制触发。
|
||||
|
||||
## 2) Required inputs
|
||||
|
||||
- 最少输入:`analyst_signals`、`risk_warnings`、`portfolio_state`、`cash`、`margin_requirement`、`prices`。
|
||||
- 推荐输入:会议共识摘要、历史表现偏差、当前组合拥挤度。
|
||||
- 若缺失关键执行约束(现金/保证金/价格),应降级为“条件决策草案”,不可直接给激进仓位。
|
||||
|
||||
## 3) Decision procedure
|
||||
|
||||
1. 汇总并比较 analyst 信号,识别共识与分歧。
|
||||
2. 将风险警示映射到仓位上限与禁开条件。
|
||||
3. 在资金与保证金约束下,为每个 ticker 生成候选动作与数量。
|
||||
4. 对冲突信号执行保守仲裁:降低仓位、提高触发门槛或改为 `hold`。
|
||||
5. 逐个 ticker 记录最终决策,并给出组合级理由。
|
||||
|
||||
## 4) Tool call policy
|
||||
|
||||
- 必须使用决策工具记录每个 ticker 的最终 `action/quantity`。
|
||||
- 在讨论阶段如发现当前团队能力不足,可使用团队工具动态创建或移除 analyst(再继续讨论)。
|
||||
- 若风险工具提示阻断项,优先遵循阻断,不得绕过。
|
||||
- 工具调用失败时:重试一次;仍失败则输出结构化“未完成决策清单”和人工处理建议。
|
||||
|
||||
## 5) Output schema
|
||||
|
||||
- `decisions`: 每个 ticker 的 `{action: long|short|hold, quantity, confidence, reasoning}`
|
||||
- `portfolio_rationale`: 组合层面的配置逻辑与取舍依据
|
||||
- `constraint_check`: 资金、保证金、集中度是否满足
|
||||
- `conflict_resolution`: 对信号冲突的处理说明
|
||||
- `pending_items`: 未决事项与补充数据需求(若有)
|
||||
|
||||
## 6) Failure fallback
|
||||
|
||||
- 当分析师信号与风险结论显著冲突时,默认采用更小仓位或 `hold`。
|
||||
- 当约束校验失败(现金/保证金不足)时,自动下调数量,不输出不可执行指令。
|
||||
- 当任务要求完整清单时,不允许遗漏 ticker;无法决策时必须显式标记 `hold` 并说明原因。
|
||||
48
backend/skills/builtin/risk_review/SKILL.md
Normal file
48
backend/skills/builtin/risk_review/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: 风险审查
|
||||
description: 当用户要求“风险审查”“看组合风险”“检查集中度”“评估波动风险”或“确认仓位风险边界”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 风险审查
|
||||
|
||||
当用户需要识别集中度、波动率、杠杆和情景风险时,使用这个技能。
|
||||
|
||||
## 1) When to use
|
||||
|
||||
- 适用于下单前风险闸门、仓位复核、组合再平衡前的约束审查。
|
||||
- 当需要把“风险观点”转成“可执行限制”时必须使用本技能。
|
||||
- 若任务仅为单纯行情解读且不涉及仓位执行,可不独立触发。
|
||||
|
||||
## 2) Required inputs
|
||||
|
||||
- 最少输入:`portfolio positions`、`cash/margin`、`proposed decisions`、`current prices`。
|
||||
- 推荐输入:波动率指标、流动性指标、相关性/主题暴露。
|
||||
- 若缺失关键风险数据,必须输出“暂定限制”并标明待补数据项。
|
||||
|
||||
## 3) Decision procedure
|
||||
|
||||
1. 按 ticker、行业主题、净敞口做集中度盘点。
|
||||
2. 评估波动、流动性与杠杆压力,识别潜在连锁风险。
|
||||
3. 将风险分级:`fatal blocker / major caution / manageable`。
|
||||
4. 将每类风险映射为明确限制(仓位上限、减仓条件、禁开仓条件)。
|
||||
|
||||
## 4) Tool call policy
|
||||
|
||||
- 优先调用风险工具组量化集中度、保证金压力、波动暴露。
|
||||
- 无量化证据时,不给“无风险”结论;只能给保守警示。
|
||||
- 工具失败时应回退到规则化约束(更低仓位上限、更严格止损条件)。
|
||||
|
||||
## 5) Output schema
|
||||
|
||||
- `risk_level`: `low | medium | high | critical`
|
||||
- `warnings`: 按严重度排序的风险列表(含原因)
|
||||
- `limits`: 可执行限制(仓位/杠杆/单票上限)
|
||||
- `blockers`: 必须先解决的阻断项
|
||||
- `recommendation_to_pm`: 对 PM 的执行建议(允许/限制/禁止)
|
||||
|
||||
## 6) Failure fallback
|
||||
|
||||
- 关键数据缺失或工具不可用时:默认提高一级风险等级并收紧仓位限制。
|
||||
- 无法确认保证金与流动性安全时,默认禁止新增高风险敞口。
|
||||
- 明确区分“硬阻断”与“可带条件执行”的风险,避免含糊建议。
|
||||
22
backend/skills/builtin/sentiment_review/SKILL.md
Normal file
22
backend/skills/builtin/sentiment_review/SKILL.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: 情绪分析
|
||||
description: 当用户要求“情绪分析”“看新闻情绪”“分析市场心理”“判断事件驱动信号”或“检查内幕行为”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 情绪分析
|
||||
|
||||
当用户需要基于近期催化剂、新闻语气或行为层面的市场信号做判断时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 回顾近期新闻并识别主导叙事。
|
||||
2. 检查内幕活动,寻找确认或冲突信号。
|
||||
3. 区分可持续的情绪变化和短暂噪音。
|
||||
4. 说明情绪如何改变短期交易展望。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要把注意力误判为真实信念。
|
||||
- 当情绪很强但缺乏基本面支持时,要明确指出。
|
||||
- 对催化剂时间窗口风险要说清楚。
|
||||
22
backend/skills/builtin/technical_review/SKILL.md
Normal file
22
backend/skills/builtin/technical_review/SKILL.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: 技术分析
|
||||
description: 当用户要求“技术分析”“看走势”“判断入场时机”“分析动量”“评估波动率”或“判断市场状态”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 技术分析
|
||||
|
||||
当用户需要从入场时机、趋势质量或短期市场结构出发判断标的时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 评估趋势方向和强度。
|
||||
2. 检查动量与均值回归条件。
|
||||
3. 在给出激进建议前先审视波动率。
|
||||
4. 将当前形态转化为带有明确风险意识的交易观点。
|
||||
|
||||
## 约束
|
||||
|
||||
- 区分趋势延续和过度透支。
|
||||
- 当信号冲突时避免给出高确定性判断。
|
||||
- 将波动率视为仓位输入,而不仅仅是方向输入。
|
||||
31
backend/skills/builtin/valuation_review/SKILL.md
Normal file
31
backend/skills/builtin/valuation_review/SKILL.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: 估值分析
|
||||
description: 当用户要求“估值分析”“看合理价值”“判断高估低估”“测算安全边际”或“比较多种估值方法”时,应使用此技能。
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 估值分析
|
||||
|
||||
当用户需要判断一只股票是低估、高估还是定价合理时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 条件允许时,使用不止一种估值方法。
|
||||
2. 对比内在价值估计与当前市场价格。
|
||||
3. 解释估值判断背后的关键假设。
|
||||
4. 明确安全边际,以及哪些因素会压缩或扩大它。
|
||||
|
||||
## 可复用资源
|
||||
|
||||
- `scripts/dcf_report.py`
|
||||
用于贴现现金流估值的确定性计算和报告生成。
|
||||
- `scripts/owner_earnings_report.py`
|
||||
用于 owner earnings 估值的确定性计算和报告生成。
|
||||
- `scripts/multiple_valuation_report.py`
|
||||
用于 EV/EBITDA 和 Residual Income 两类估值报告生成。
|
||||
|
||||
## 约束
|
||||
|
||||
- 将估值视为区间,而不是一个精确点值。
|
||||
- 明确说明假设敏感性。
|
||||
- 当输入稀疏或不稳定时,避免给出高置信度判断。
|
||||
1
backend/skills/builtin/valuation_review/__init__.py
Normal file
1
backend/skills/builtin/valuation_review/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
1
backend/skills/customized/.gitkeep
Normal file
1
backend/skills/customized/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
21
backend/skills/customized/portfolio_decisioning/SKILL.md
Normal file
21
backend/skills/customized/portfolio_decisioning/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: 组合决策
|
||||
description: 整合分析师观点与风险反馈,形成明确的组合层决策。
|
||||
---
|
||||
|
||||
# 组合决策
|
||||
|
||||
当你负责把团队分析转化为最终交易决策时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 行动前先阅读分析师结论和风险警示。
|
||||
2. 评估当前组合、现金和保证金约束。
|
||||
3. 使用决策工具为每个 ticker 记录一个明确决策。
|
||||
4. 在全部决策记录完成后,总结组合层面的整体理由。
|
||||
|
||||
## 约束
|
||||
|
||||
- 仓位大小必须遵守资金和保证金限制。
|
||||
- 当分析师信心与风险信号不一致时,优先采用更小仓位。
|
||||
- 当任务要求完整决策清单时,不要让任何 ticker 处于未决状态。
|
||||
21
backend/skills/customized/risk_review/SKILL.md
Normal file
21
backend/skills/customized/risk_review/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: 风险审查
|
||||
description: 在最终仓位和执行前,评估组合与市场风险。
|
||||
---
|
||||
|
||||
# 风险审查
|
||||
|
||||
当你需要识别集中度、波动率、杠杆和情景风险时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 按 ticker 和主题检查拟议敞口。
|
||||
2. 识别集中度、波动率、流动性和杠杆方面的风险点。
|
||||
3. 按严重程度排序风险警示。
|
||||
4. 将风险结论转化为给投资经理的具体限制或注意事项。
|
||||
|
||||
## 约束
|
||||
|
||||
- 聚焦可执行的风险控制措施。
|
||||
- 当数据支持时尽量量化限制。
|
||||
- 明确区分致命阻断项和可管理风险。
|
||||
21
backend/skills/customized/sentiment_review/SKILL.md
Normal file
21
backend/skills/customized/sentiment_review/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: 情绪分析
|
||||
description: 分析新闻流、市场心理和内幕行为,识别事件驱动型信号。
|
||||
---
|
||||
|
||||
# 情绪分析
|
||||
|
||||
当任务依赖近期催化剂、新闻语气或行为层面的市场信号时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 回顾近期新闻并识别主导叙事。
|
||||
2. 检查内幕活动,寻找确认或冲突信号。
|
||||
3. 区分可持续的情绪变化和短暂噪音。
|
||||
4. 说明情绪如何改变短期交易展望。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要把注意力误判为真实信念。
|
||||
- 当情绪很强但缺乏基本面支持时,要明确指出。
|
||||
- 对催化剂时间窗口风险要说清楚。
|
||||
21
backend/skills/customized/technical_review/SKILL.md
Normal file
21
backend/skills/customized/technical_review/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: 技术分析
|
||||
description: 评估价格行为、动量和波动率,用于判断时机和市场状态。
|
||||
---
|
||||
|
||||
# 技术分析
|
||||
|
||||
当任务对入场时机、趋势质量或短期市场结构敏感时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 评估趋势方向和强度。
|
||||
2. 检查动量与均值回归条件。
|
||||
3. 在给出激进建议前先审视波动率。
|
||||
4. 将当前形态转化为带有明确风险意识的交易观点。
|
||||
|
||||
## 约束
|
||||
|
||||
- 区分趋势延续和过度透支。
|
||||
- 当信号冲突时避免给出高确定性判断。
|
||||
- 将波动率视为仓位输入,而不仅仅是方向输入。
|
||||
21
backend/skills/customized/valuation_review/SKILL.md
Normal file
21
backend/skills/customized/valuation_review/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: 估值分析
|
||||
description: 使用多种估值视角评估合理价值和安全边际。
|
||||
---
|
||||
|
||||
# 估值分析
|
||||
|
||||
当任务需要判断一只股票是低估、高估还是定价合理时,使用这个技能。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 条件允许时,使用不止一种估值方法。
|
||||
2. 对比内在价值估计与当前市场价格。
|
||||
3. 解释估值判断背后的关键假设。
|
||||
4. 明确安全边际,以及哪些因素会压缩或扩大它。
|
||||
|
||||
## 约束
|
||||
|
||||
- 将估值视为区间,而不是一个精确点值。
|
||||
- 明确说明假设敏感性。
|
||||
- 当输入稀疏或不稳定时,避免给出高置信度判断。
|
||||
Reference in New Issue
Block a user