Skill: stepuuu/portfolio-daily-tracker (version 1.2.0)

--- name: portfolio-daily-tracker description: Track and report multi-group stock portfolios with daily snapshots, live Yahoo Finance prices, P&L analytics, and push notifications (Feishu/Telegram). Supports A-shares, HK, US markets. Use when asked about holdings, buy/sell/rebalance positions, generate daily portfolio reports, check drawdown or returns, update fund/cash balances, or run the full snapshot-report-push pipeline. version: 1.2.0 setup: scripts/setup.sh env: OPENAI_API_KEY: description: OpenAI API key for AI chat features required: false FEISHU_WEBHOOK: description: Feishu/Lark webhook URL for push notifications required: false TELEGRAM_BOT_TOKEN: description: Telegram bot token for push notifications required: false PORTFOLIO_DIR: description: Override default portfolio data directory path required: false requires: - python3 >= 3.9 - pip packages: yfinance, pandas, requests, fastapi, uvicorn - Engine scripts installed via setup.sh (clones repo with portfolio_manager.py, portfolio_snapshot.py, portfolio_report.py) --- # Portfolio Daily Tracker Skill ## Prerequisites / 前置条件 This skill requires the engine scripts from the main repository. Run setup first: ```bash bash scripts/setup.sh [target_dir] ``` This clones the repo, creates data directories, copies config templates, and installs Python dependencies. The Python engine scripts (`portfolio_manager.py`, `portfolio_snapshot.py`, `portfolio_report.py`, `portfolio_daily_update.py`) are located in `engine/scripts/` after setup. ## 触发条件 当用户提到以下内容时使用此技能： - 持仓变动（买入、卖出、加仓、减仓、调仓） - 查询当前持仓、市值、盈亏 - 询问投资组合报告、投资表现 - 提到具体股票名称+数量变化 - 询问回撤、收益率、资产走势 - 基金/现金余额变更 ## 系统概述 投资组合跟踪系统采用每日快照模式： - **持仓文件**: `portfolio/holdings/YYYY-MM-DD.json` — 每天独立的持仓记录 - **快照文件**: `portfolio/snapshots/YYYY-MM-DD.json` — 每日计算后的完整数据（含价格、市值、盈亏） - **配置文件**: `portfolio/config.json` — 组别定义、API配置 - **历史CSV**: `portfolio/history.csv` — 时序数据汇总 数据目录: `$PROJECT_ROOT/engine/portfolio/`（可通过环境变量 `PORTFOLIO_DIR` 覆盖） ## 投资组合分组 系统支持多个投资组合分组，每组有独立的成本基数和持仓。例如： - **进攻组** (Growth): 高成长个股 + 科技股 + 基金 + 现金 - **稳健组** (Income): 蓝筹 + ETF + 债券 + 现金 注意：同一只股票可以同时出现在不同组中（分别持有不同数量），更新时需要指定组别。 ## 持仓变更操作 当用户说"卖了500股某股"、"加了200股某股"等，执行以下步骤： ### 1. 解析用户意图 - 识别股票名称 → 映射到 ticker（参见下方 Ticker 格式表） - 识别操作类型：买入/加仓(+quantity), 卖出/减仓(-quantity) - 识别组别：如果用户未指定且股票存在于多个组中，询问"哪个组？" - 识别数量和可选的成本价 ### 2. 执行变更 ```bash cd $PROJECT_ROOT/engine python3 scripts/portfolio_manager.py update <ticker> --qty <new_total> [--cost <price>] [--group <组名>] ``` 常用命令： ```bash # 查看当前持仓 python3 scripts/portfolio_manager.py show # 更新持仓数量（设置为新的总量） python3 scripts/portfolio_manager.py update SHA:603259 --qty 4000 --group Growth # 添加新持仓 python3 scripts/portfolio_manager.py add NASDAQ:NVDA NVIDIA Growth --qty 10 --cost 120.0 --market us # 删除持仓 python3 scripts/portfolio_manager.py remove NASDAQ:META --group Growth # 显示分组信息 python3 scripts/portfolio_manager.py groups ``` ## 基金/现金变更 当用户说"今天基金涨了500块"、"基金现在值16万了"、"现金变成-50万"等： ### 基金更新 ```bash # 设置某组基金市值（单位：元） python3 scripts/portfolio_manager.py set-fund --group Growth --value 160000 # 设置另一组基金市值 python3 scripts/portfolio_manager.py set-fund --group Income --value 5000 ``` ### 现金更新 ```bash # 设置某组现金（可为负数，表示融资/杠杆） python3 scripts/portfolio_manager.py set-cash --group Growth --value -500000 # 设置另一组现金 python3 scripts/portfolio_manager.py set-cash --group Income --value 3300 ``` ### 解析用户意图 - "基金值16万" / "基金现在值16万" → set-fund --value 160000 - "基金涨了500" → 先读取当前 fund 值，加500后 set-fund - "基金赎回了1万" → 先读取当前 fund 值，减10000后 set-fund - "转入5万" / "充值5万" → 先读取当前 cash 值，加50000后 set-cash - "现金变成-48万" → set-cash --value -480000 ### 3. 确认变更 变更后读取并展示当天持仓文件，确认更新正确。 ## 查询操作 ### 查看最新快照 ```bash # 查看最新快照 ls -t engine/portfolio/snapshots/*.json | head -1 | xargs cat | python3 -m json.tool ``` ### 生成/查看报告 ```bash # 生成今日快照（需取价） python3 engine/scripts/portfolio_snapshot.py --date $(date +%Y-%m-%d) # 生成报告 python3 engine/scripts/portfolio_report.py --date $(date +%Y-%m-%d) ``` ### 查看历史数据 ```bash # 查看历史CSV cat engine/portfolio/history.csv # 查看特定日期快照 cat engine/portfolio/snapshots/2026-03-08.json | python3 -m json.tool ``` ## Ticker 格式表 | 市场 | 格式 | 示例 | Yahoo代码 | |------|------|------|-----------| | 上交所A股 | `SHA:XXXXXX` | `SHA:603259` | `603259.SS` | | 深交所A股 | `SHE:XXXXXX` | `SHE:002050` | `002050.SZ` | | 上交所ETF | `SHA:XXXXXX` | `SHA:513050` | `513050.SS` | | 港股 | `HKG:XXXX` | `HKG:0700` | `0700.HK` | | 纳斯达克 | `NASDAQ:XXXX` | `NASDAQ:GOOGL` | `GOOGL` | | 纽交所 | `NYSE:XXXX` | `NYSE:BRK.B` | `BRK-B` | > 用户通常用中文名或简称，需要你映射为 Ticker 格式。 > 如果不确定某只股票的 Ticker，先用 `portfolio_manager.py show` 查看已有持仓中的命名。 ## 交易信号短语识别 用户可能使用的中文表达： - "卖了/卖掉/清仓/减持" → 减少数量 - "买了/加了/加仓/建仓" → 增加数量 - "换仓/调仓 X→Y" → 卖出X + 买入Y - "成本调了/成本变成" → 更新 cost_price - "基金赎回了/基金买了" → 更新 fund 值 - "转入/充值" → 更新 cash ## 完整日报流水线 当用户要求生成日报时，运行完整管道： ```bash cd $PROJECT_ROOT/engine # 1. 获取最新价格并生成快照 python3 scripts/portfolio_snapshot.py --date $(date +%Y-%m-%d) # 2. 生成 Markdown 报告 python3 scripts/portfolio_report.py --date $(date +%Y-%m-%d) # 3.（可选）推送到飞书/Telegram # 在 scripts/portfolio-daily.sh 中可用 openclaw message send 推送 ``` ## Tools Provided 1. **get_tracker_snapshot** — 获取投资组合快照，包含分组持仓、融资杠杆、量化指标 2. **update_holdings** — 更新每日持仓，接受自然语言描述 3. **run_portfolio_pipeline** — 运行完整管道：快照→报告→推送 ## Example Interactions ``` User: 今日持仓有变化吗？ Agent: [calls get_tracker_snapshot to show current holdings] User: 卖了500股某股票，现金变为-48万 Agent: [calls update_holdings with changes, then run_portfolio_pipeline] User: 帮我生成今天的日报 Agent: [calls run_portfolio_pipeline] User: Growth 组的基金现在值16万了 Agent: [runs: portfolio_manager.py set-fund --group Growth --value 160000] ``` ## 注意事项 1. **每日文件不可变**: 修改当天的文件不影响历史文件 2. **如果今天没有文件**: 自动从最近一天复制 3. **同一股票跨组**: 需要指定组别才能准确更新 4. **基金和现金**: 使用 `set-fund` 和 `set-cash` 命令更新 5. **周末/节假日**: 可以修改持仓，但价格快照获取可能无变化 6. **融资/杠杆**: 现金为负值时自动计算融资额和杠杆比率 7. **回撤**: 基于利润的回撤算法，从 history.csv 全量历史数据计算（排除资金注入/取出影响） 8. **资金变动**: 成本增加/减少（如注资、取出）会被记录为 `capital_change`，日盈亏 `market_daily_change` 自动排除资金变动 9. **快照缺失容错**: 即使某天的快照 JSON 被删除，API 会从 CSV 历史数据自动合成，日期导航不会出现缺口 10. **CSV 11列格式**: `date,total_value,total_cost,total_profit,return_pct,daily_change,daily_change_pct,max_drawdown_pct,capital_change,market_daily_change,market_daily_change_pct` ## Changelog ### v1.2.0 (2026-03-10) - **fix**: 日盈亏正确排除资金注入/取出（`market_daily_change = daily_change - capital_change`） - **fix**: 日期导航使用 CSV + 快照文件并集，不再因快照缺失而跳过日期 - **fix**: 快照缺失时自动合成 synthetic snapshot（从 CSV + 最近快照重建） - **fix**: 回撤算法改为基于利润的计算方式，排除资金流影响 - **fix**: CSV 升级为 11 列格式，新增 `capital_change`/`market_daily_change`/`market_daily_change_pct` - **fix**: 支持 `成本增加/减少` 解析模式 ### v1.1.0 (2026-03-08) - Initial ClawHub release with multi-market support, AI chat, backtesting engine

path: SKILL_EN.md
content: ---
name: portfolio-daily-tracker
description: Track and report multi-group stock portfolios with daily snapshots, live Yahoo Finance prices, P&L analytics, and push notifications (Feishu/Telegram). Supports A-shares, HK, US markets. Use when asked about holdings, buy/sell/rebalance positions, generate daily portfolio reports, check drawdown or returns, update fund/cash balances, or run the full snapshot-report-push pipeline.
version: 1.2.0
setup: scripts/setup.sh
env:
  OPENAI_API_KEY:
    description: OpenAI API key for AI chat features
    required: false
  FEISHU_WEBHOOK:
    description: Feishu/Lark webhook URL for push notifications
    required: false
  TELEGRAM_BOT_TOKEN:
    description: Telegram bot token for push notifications
    required: false
  PORTFOLIO_DIR:
    description: Override default portfolio data directory path
    required: false
requires:
  - python3 >= 3.9
  - pip packages: yfinance, pandas, requests, fastapi, uvicorn
  - Engine scripts installed via setup.sh (clones repo with portfolio_manager.py, portfolio_snapshot.py, portfolio_report.py)
---

# Portfolio Daily Tracker Skill

## Prerequisites

This skill requires the engine scripts from the main repository. Run setup first:
```bash
bash scripts/setup.sh [target_dir]
```
This clones the repo, creates data directories, copies config templates, and installs Python dependencies. The Python engine scripts (`portfolio_manager.py`, `portfolio_snapshot.py`, `portfolio_report.py`, `portfolio_daily_update.py`) are located in `engine/scripts/` after setup.

## Trigger Conditions

Use this skill when the user mentions:
- Position changes (buy, sell, add, reduce, rebalance)
- Query current holdings, market value, P&L
- Portfolio reports, investment performance
- Specific stock names + quantity changes
- Drawdown, return rate, asset trends
- Fund / cash balance changes

## System Overview

The portfolio tracking system uses a daily snapshot model:
- **Holdings file**: `portfolio/holdings/YYYY-MM-DD.json` — independent holding records per day
- **Snapshot file**: `portfolio/snapshots/YYYY-MM-DD.json` — computed data with prices, market value, P&L
- **Config file**: `portfolio/config.json` — group definitions, API configuration
- **History CSV**: `portfolio/history.csv` — time series summary

Data directory: `$PROJECT_ROOT/engine/portfolio/` (override with `PORTFOLIO_DIR` env var)

## Portfolio Groups

The system supports multiple portfolio groups, each with independent cost basis and positions. For example:
- **Growth**: High-growth stocks + tech + funds + cash
- **Income**: Blue-chips + ETFs + bonds + cash

Note: The same stock can appear in different groups (held in different quantities). When updating, you must specify the group.

## Position Change Operations

When the user says "sold 500 shares of X", "added 200 shares of Y", etc., follow these steps:

### 1. Parse user intent
- Recognize stock name → map to ticker (see Ticker Format table below)
- Recognize action type: buy/add (+quantity), sell/reduce (-quantity)
- Recognize group: if unspecified and stock exists in multiple groups, ask "which group?"
- Recognize quantity and optional cost price

### 2. Execute change
```bash
cd $PROJECT_ROOT/engine
python3 scripts/portfolio_manager.py update <ticker> --qty <new_total> [--cost <price>] [--group <group_name>]
```

Common commands:
```bash
# View current holdings
python3 scripts/portfolio_manager.py show

# Update position quantity (set new total)
python3 scripts/portfolio_manager.py update SHA:603259 --qty 4000 --group Growth

# Add new position
python3 scripts/portfolio_manager.py add NASDAQ:NVDA NVIDIA Growth --qty 10 --cost 120.0 --market us

# Remove position
python3 scripts/portfolio_manager.py remove NASDAQ:META --group Growth

# Show group info
python3 scripts/portfolio_manager.py groups
```

## Fund / Cash Changes

When the user says "fund went up 500", "fund is now worth 160k", "cash changed to -500k":

### Fund update
```bash
# Set fund market value for a group (unit: yuan)
python3 scripts/portfolio_manager.py set-fund --group Growth --value 160000

# Set another group's fund value
python3 scripts/portfolio_manager.py set-fund --group Income --value 5000
```

### Cash update
```bash
# Set group cash (negative = margin/leverage)
python3 scripts/portfolio_manager.py set-cash --group Growth --value -500000

# Set another group's cash
python3 scripts/portfolio_manager.py set-cash --group Income --value 3300
```

### Parse user intent
- "fund is worth 160k" → set-fund --value 160000
- "fund went up 500" → read current fund value, add 500, then set-fund
- "redeemed 10k from fund" → read current fund value, subtract 10000, then set-fund
- "deposited 50k" → read current cash value, add 50000, then set-cash
- "cash changed to -480k" → set-cash --value -480000

### 3. Confirm changes
After changes, read and display today's holding file to confirm the update is correct.

## Query Operations

### View latest snapshot
```bash
# View latest snapshot
ls -t engine/portfolio/snapshots/*.json | head -1 | xargs cat | python3 -m json.tool
```

### Generate/view reports
```bash
# Generate today's snapshot (requires fetching prices)
python3 engine/scripts/portfolio_snapshot.py --date $(date +%Y-%m-%d)

# Generate report
python3 engine/scripts/portfolio_report.py --date $(date +%Y-%m-%d)
```

### View historical data
```bash
# View history CSV
cat engine/portfolio/history.csv

# View a specific date's snapshot
cat engine/portfolio/snapshots/2026-03-08.json | python3 -m json.tool
```

## Ticker Format Table

| Market | Format | Example | Yahoo Code |
|--------|--------|---------|------------|
| Shanghai A-shares | `SHA:XXXXXX` | `SHA:603259` | `603259.SS` |
| Shenzhen A-shares | `SHE:XXXXXX` | `SHE:002050` | `002050.SZ` |
| Shanghai ETF | `SHA:XXXXXX` | `SHA:513050` | `513050.SS` |
| Hong Kong | `HKG:XXXX` | `HKG:0700` | `0700.HK` |
| NASDAQ | `NASDAQ:XXXX` | `NASDAQ:GOOGL` | `GOOGL` |
| NYSE | `NYSE:XXXX` | `NYSE:BRK.B` | `BRK-B` |

> Users often use Chinese names or abbreviations — you need to map them to Ticker format.
> If unsure about a stock's ticker, use `portfolio_manager.py show` to check existing holdings.

## Trading Signal Phrase Recognition

Common expressions users may use:
- "sold / cleared / reduced" → decrease quantity
- "bought / added / initiated" → increase quantity
- "swapped / rebalanced X→Y" → sell X + buy Y
- "cost changed to" → update cost_price
- "redeemed fund / bought fund" → update fund value
- "deposited / transferred in" → update cash

## Full Daily Report Pipeline

When the user asks to generate a daily report, run the full pipeline:

```bash
cd $PROJECT_ROOT/engine

# 1. Fetch latest prices and generate snapshot
python3 scripts/portfolio_snapshot.py --date $(date +%Y-%m-%d)

# 2. Generate Markdown report
python3 scripts/portfolio_report.py --date $(date +%Y-%m-%d)

# 3. (Optional) Push to Feishu/Telegram
# In scripts/portfolio-daily.sh you can use openclaw message send to push
```

## Tools Provided

1. **get_tracker_snapshot** — Get portfolio snapshot including grouped holdings, margin/leverage, and quant metrics
2. **update_holdings** — Update daily holdings, accepts natural language description
3. **run_portfolio_pipeline** — Run the full pipeline: snapshot → report → push

## Example Interactions
```
User: Any changes to my portfolio today?
Agent: [calls get_tracker_snapshot to show current holdings]

User: Sold 500 shares of stock X, cash is now -480k
Agent: [calls update_holdings with changes, then run_portfolio_pipeline]

User: Generate today's daily report
Agent: [calls run_portfolio_pipeline]

User: Growth group fund is now worth 160k
Agent: [runs: portfolio_manager.py set-fund --group Growth --value 160000]
```

## Important Notes

1. **Daily files are immutable**: Modifying today's file does not affect historical files
2. **If no file exists for today**: Automatically copies from the most recent day
3. **Same stock across groups**: Must specify which group to update accurately
4. **Fund and cash**: Use `set-fund` and `set-cash` commands to update
5. **Weekends/holidays**: Can modify holdings, but price snapshots may not change
6. **Margin/leverage**: When cash is negative, margin amount and leverage ratio are auto-calculated
7. **Drawdown**: Profit-based drawdown algorithm, calculated from full history.csv (excludes capital injection/withdrawal impact)
8. **Capital changes**: Cost increases/decreases (injections, withdrawals) are recorded as `capital_change`; daily P&L `market_daily_change` automatically excludes capital flow
9. **Snapshot fault tolerance**: If a day's snapshot JSON is missing, the API auto-synthesizes from CSV history + closest snapshot; date navigator never has gaps
10. **CSV 11-column format**: `date,total_value,total_cost,total_profit,return_pct,daily_change,daily_change_pct,max_drawdown_pct,capital_change,market_daily_change,market_daily_change_pct`

## Changelog

### v1.2.0 (2026-03-10)
- **fix**: Daily P&L correctly excludes capital injections/withdrawals (`market_daily_change = daily_change - capital_change`)
- **fix**: Date navigator uses CSV + snapshot file union, no longer skips dates with missing snapshots
- **fix**: Auto-synthesize snapshot when JSON is missing (rebuilt from CSV + closest snapshot)
- **fix**: Drawdown algorithm changed to profit-based, excluding capital flow impact
- **fix**: CSV upgraded to 11-column format with `capital_change`/`market_daily_change`/`market_daily_change_pct`
- **fix**: Support `成本增加/减少` (cost delta) parsing pattern

### v1.1.0 (2026-03-08)
- Initial ClawHub release with multi-market support, AI chat, backtesting engine


path: examples/config.example.json
content: {
  "_comment": "OpenClaw configuration example for portfolio tracker integration",
  "feishu_chat_id": "your_chat_id_here",
  "notification_schedule": {
    "notify_time": "18:00",
    "pipeline_time": "19:00",
    "timezone": "Asia/Shanghai",
    "skip_weekends": true,
    "holidays": ["2026-01-01", "2026-01-26"]
  },
  "message_templates": {
    "notify": "📊 投资组合每日更新 — {date}\n\n今日持仓已基于昨日自动生成。\n请告诉我今日变化：\n• 回复「未变化」→ 直接生成日报\n• 告诉我变化内容",
    "report_header": "📊 投资组合日报 — {date}"
  }
}


path: scripts/daily-pipeline.sh
content: #!/usr/bin/env bash
# portfolio-daily-tracker skill — Daily pipeline runner
# Runs: snapshot → report → (optional) push notification
#
# Usage:
#   bash daily-pipeline.sh [date] [--push]
#
# Examples:
#   bash daily-pipeline.sh                  # today, no push
#   bash daily-pipeline.sh 2026-03-10       # specific date
#   bash daily-pipeline.sh --push           # today + push notifications
set -euo pipefail

# Find project root (look for engine/scripts/)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -d "$SCRIPT_DIR/../../engine/scripts" ]; then
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
elif [ -d "./engine/scripts" ]; then
  PROJECT_ROOT="$(pwd)"
else
  echo "Error: Cannot find project root. Run from the portfolio-daily-tracker directory."
  exit 1
fi

# Parse arguments
DATE=""
PUSH=false
for arg in "$@"; do
  case "$arg" in
    --push) PUSH=true ;;
    *) DATE="$arg" ;;
  esac
done
DATE="${DATE:-$(date +%Y-%m-%d)}"

echo "=== Portfolio Daily Pipeline — $DATE ==="
cd "$PROJECT_ROOT/engine"

# Step 1: Snapshot
echo "[1/3] Generating snapshot..."
python3 scripts/portfolio_snapshot.py --date "$DATE"
echo "  ✓ Snapshot saved to portfolio/snapshots/$DATE.json"

# Step 2: Report
echo "[2/3] Generating report..."
python3 scripts/portfolio_report.py --date "$DATE"
echo "  ✓ Report generated"

# Step 3: Push (optional)
if [ "$PUSH" = true ]; then
  echo "[3/3] Pushing notifications..."
  if [ -f scripts/portfolio-daily.sh ]; then
    bash scripts/portfolio-daily.sh "$DATE"
    echo "  ✓ Notifications sent"
  else
    echo "  ⚠ portfolio-daily.sh not found, skipping push"
  fi
else
  echo "[3/3] Push skipped (use --push to enable)"
fi

echo ""
echo "=== Pipeline complete ==="


path: scripts/manage.sh
content: #!/usr/bin/env bash
# portfolio-daily-tracker skill — Position management helper
# Thin wrapper around portfolio_manager.py with friendlier interface
#
# Usage:
#   bash manage.sh show                              # show all holdings
#   bash manage.sh groups                             # show groups
#   bash manage.sh add <ticker> <name> <group> [--qty N] [--cost P]
#   bash manage.sh update <ticker> --qty N [--group G] [--cost P]
#   bash manage.sh remove <ticker> [--group G]
#   bash manage.sh set-fund --group G --value V
#   bash manage.sh set-cash --group G --value V
set -euo pipefail

# Find project root
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -d "$SCRIPT_DIR/../../engine/scripts" ]; then
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
elif [ -d "./engine/scripts" ]; then
  PROJECT_ROOT="$(pwd)"
else
  echo "Error: Cannot find project root. Run from the portfolio-daily-tracker directory."
  exit 1
fi

MANAGER="$PROJECT_ROOT/engine/scripts/portfolio_manager.py"

if [ ! -f "$MANAGER" ]; then
  echo "Error: portfolio_manager.py not found at $MANAGER"
  echo "Make sure the portfolio-daily-tracker repo is set up correctly."
  exit 1
fi

if [ $# -eq 0 ]; then
  echo "Portfolio Manager — Quick Commands"
  echo ""
  echo "  bash manage.sh show                        Show all holdings"
  echo "  bash manage.sh groups                      Show portfolio groups"
  echo "  bash manage.sh add NASDAQ:AAPL Apple Growth --qty 100 --cost 150"
  echo "  bash manage.sh update SHA:603259 --qty 4000 --group Growth"
  echo "  bash manage.sh remove NASDAQ:META --group Growth"
  echo "  bash manage.sh set-fund --group Growth --value 160000"
  echo "  bash manage.sh set-cash --group Growth --value -500000"
  echo ""
  echo "Ticker formats:"
  echo "  Shanghai A-shares: SHA:XXXXXX (e.g. SHA:603259)"
  echo "  Shenzhen A-shares: SHE:XXXXXX (e.g. SHE:002050)"
  echo "  Hong Kong:         HKG:XXXX   (e.g. HKG:0700)"
  echo "  NASDAQ:            NASDAQ:XXXX (e.g. NASDAQ:GOOGL)"
  echo "  NYSE:              NYSE:XXXX   (e.g. NYSE:BRK.B)"
  exit 0
fi

python3 "$MANAGER" "$@"


path: scripts/setup.sh
content: #!/usr/bin/env bash
# portfolio-daily-tracker skill — Quick setup helper
# Clones the repo and sets up the basic directory structure.
#
# Usage:
#   bash setup.sh [target_dir]
#
# After running, follow instructions to configure config.json and .env
set -euo pipefail

TARGET="${1:-portfolio-daily-tracker}"

echo "=== Portfolio Daily Tracker — Setup ==="

# 1. Clone repo (if not already inside one)
if [ ! -d "$TARGET" ]; then
  echo "[1/4] Cloning repository..."
  git clone https://github.com/Stepuuu/portfolio-daily-tracker.git "$TARGET"
else
  echo "[1/4] Directory '$TARGET' already exists, skipping clone."
fi

cd "$TARGET"

# 2. Create data directories
echo "[2/4] Creating data directories..."
mkdir -p engine/portfolio/holdings engine/portfolio/snapshots

# 3. Copy example configs if real ones don't exist
echo "[3/4] Setting up config files..."
if [ ! -f engine/portfolio/config.json ] && [ -f engine/portfolio/config.example.json ]; then
  cp engine/portfolio/config.example.json engine/portfolio/config.json
  echo "  → Copied config.example.json → config.json (edit this!)"
fi

if [ ! -f .env ] && [ -f .env.example ]; then
  cp .env.example .env
  echo "  → Copied .env.example → .env (add your API keys here!)"
fi

# 4. Install Python dependencies
echo "[4/4] Installing Python dependencies..."
if command -v pip3 &>/dev/null; then
  pip3 install -r dashboard/requirements.txt 2>/dev/null || echo "  ⚠ pip install failed — install manually: pip3 install -r dashboard/requirements.txt"
else
  echo "  ⚠ pip3 not found — install manually: pip3 install -r dashboard/requirements.txt"
fi

echo ""
echo "=== Setup complete! ==="
echo ""
echo "Next steps:"
echo "  1. Edit engine/portfolio/config.json — define your portfolio groups"
echo "  2. Edit .env — add API keys (OPENAI_API_KEY, FEISHU_WEBHOOK, etc.)"
echo "  3. Create your first holdings file:"
echo "     python3 engine/scripts/portfolio_manager.py add NASDAQ:AAPL Apple Growth --qty 100 --cost 150.0"
echo "  4. Generate a snapshot:"
echo "     python3 engine/scripts/portfolio_snapshot.py --date \$(date +%Y-%m-%d)"
echo "  5. Generate a report:"
echo "     python3 engine/scripts/portfolio_report.py --date \$(date +%Y-%m-%d)"


path: tools/portfolio_tools.py
content: """
OpenClaw 🦞 Agent Tools for Portfolio Tracker

These tools integrate with the portfolio engine to enable
conversational portfolio management via AI agent.

Usage in your agent service:
    from openclaw.tools.portfolio_tools import get_tools
    tools = get_tools(portfolio_dir="/path/to/portfolio")
"""

import json, os, glob
from pathlib import Path
from typing import Dict, Any, List, Optional


def get_tracker_snapshot_tool(portfolio_dir: str = "") -> Dict:
    """Create a tool definition for reading portfolio snapshots.
    
    Returns a tool spec compatible with function-calling LLM APIs.
    """
    if not portfolio_dir:
        portfolio_dir = os.getenv("PORTFOLIO_DIR", "")

    async def get_tracker_snapshot(date: str = "") -> Dict:
        """Get detailed portfolio snapshot with groups, positions, leverage, quant metrics."""
        tracker_dir = Path(portfolio_dir) if portfolio_dir else Path("portfolio")
        snapshots_dir = tracker_dir / "snapshots"
        history_file = tracker_dir / "history.csv"

        # Load snapshot
        snapshot = None
        if date and snapshots_dir.exists():
            snap_file = snapshots_dir / f"{date}.json"
            if snap_file.exists():
                snapshot = json.loads(snap_file.read_text())
        elif snapshots_dir.exists():
            snap_files = sorted(snapshots_dir.glob("*.json"), reverse=True)
            if snap_files:
                snapshot = json.loads(snap_files[0].read_text())

        if not snapshot:
            return {"error": "No snapshot found"}

        result: Dict[str, Any] = {
            "date": snapshot.get("date"),
            "summary": snapshot.get("summary", {}),
            "groups": {},
        }

        for gname, g in snapshot.get("groups", {}).items():
            positions = [{
                "name": p["name"], "ticker": p["ticker"],
                "quantity": p["quantity"], "cost_price": p["cost_price"],
                "current_price": p["current_price"],
                "market_value_cny": p["market_value_cny"],
                "profit_cny": p["profit_cny"], "profit_pct": p["profit_pct"],
            } for p in g.get("positions", [])]

            cash = g.get("cash", 0)
            fund = g.get("fund", 0)
            pos_val = g.get("positions_value", 0)
            has_margin = cash < 0

            result["groups"][gname] = {
                "total_value": g["total_value"],
                "positions_value": pos_val,
                "fund": fund, "cash": cash,
                "profit": g.get("profit"),
                "return_pct": g.get("return_pct"),
                "has_margin": has_margin,
                "margin_amount": abs(cash) if has_margin else 0,
                "leverage_ratio": round((pos_val + fund) / g["total_value"], 2) if g["total_value"] != 0 and has_margin else 1.0,
                "positions": positions,
            }

        # Load recent history
        if history_file.exists():
            import csv
            with open(history_file) as f:
                rows = list(csv.DictReader(f))
            recent = rows[-10:]
            recent.reverse()
            result["recent_history"] = [{
                "date": r["date"],
                "total_value": float(r["total_value"]),
                "daily_change": float(r["daily_change"]),
                "daily_change_pct": float(r["daily_change_pct"]),
            } for r in recent]

        return result

    return {
        "name": "get_tracker_snapshot",
        "description": "获取投资组合跟踪器快照，包含分组持仓、融资杠杆、量化指标",
        "function": get_tracker_snapshot,
        "parameters": {
            "date": {"type": "string", "description": "日期 YYYY-MM-DD，留空获取最新", "required": False},
        }
    }


def get_update_holdings_tool(scripts_dir: str = "") -> Dict:
    """Create a tool definition for updating daily holdings."""

    async def update_holdings(date: str, changes_text: str) -> Dict:
        """Update holdings based on natural language description."""
        import sys
        if scripts_dir:
            sys.path.insert(0, scripts_dir)
        
        from portfolio_daily_update import (
            clone_holdings, load_holdings, save_holdings,
            parse_and_apply_changes
        )

        path, is_new = clone_holdings(date)
        if not path:
            return {"error": "Cannot create holdings file", "success": False}

        holdings = load_holdings(date)
        if not holdings:
            return {"error": "Cannot load holdings", "success": False}

        changes = parse_and_apply_changes(holdings, changes_text)
        has_real_changes = any(c["action"] not in ("no_change", "unknown") for c in changes)

        if has_real_changes:
            save_holdings(holdings, date)

        return {
            "success": True,
            "date": date,
            "changes_applied": [c["description"] for c in changes],
            "holdings_updated": has_real_changes,
        }

    return {
        "name": "update_holdings",
        "description": "更新每日持仓，接受自然语言描述（如'卖了500股xxx''现金变为xxx''未变化'）",
        "function": update_holdings,
        "parameters": {
            "date": {"type": "string", "description": "日期 YYYY-MM-DD", "required": True},
            "changes_text": {"type": "string", "description": "变更描述", "required": True},
        }
    }


def get_pipeline_tool(scripts_dir: str = "") -> Dict:
    """Create a tool definition for running the full portfolio pipeline."""

    async def run_portfolio_pipeline(date: str, send_report: bool = True) -> Dict:
        """Run full pipeline: snapshot → report → push → sync."""
        import sys
        if scripts_dir:
            sys.path.insert(0, scripts_dir)

        from portfolio_daily_update import clone_holdings, run_pipeline

        clone_holdings(date)
        success = run_pipeline(date, send_report=send_report)

        return {
            "success": success,
            "date": date,
            "message": f"{'✅' if success else '❌'} {date} pipeline {'completed' if success else 'failed'}",
        }

    return {
        "name": "run_portfolio_pipeline",
        "description": "运行完整管道：快照→报告→推送→同步Dashboard",
        "function": run_portfolio_pipeline,
        "parameters": {
            "date": {"type": "string", "description": "日期 YYYY-MM-DD", "required": True},
            "send_report": {"type": "boolean", "description": "是否推送飞书", "required": False},
        }
    }


def get_all_tools(portfolio_dir: str = "", scripts_dir: str = "") -> List[Dict]:
    """Get all portfolio tools as a list."""
    return [
        get_tracker_snapshot_tool(portfolio_dir),
        get_update_holdings_tool(scripts_dir),
        get_pipeline_tool(scripts_dir),
    ]


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)