@clawhub-dcsight-dd719661c6
Save and track stock investment strategies with entry/exit conditions in a local Markdown file and check current market data to recommend next steps.
---
name: stock-scheme-tracker
description: Save and track per-stock investment strategies with entry/exit conditions. Triggers on "保存策略", "跟踪策略", "检查股票策略", "核对建仓条件", "scheme", "stock strategy". Two modes: save (extract strategy from analysis → append to tracking file) and check (evaluate all conditions against user-provided market data, output actionable recommendations).
version: 1.1.0
requires:
anyBins:
- python3
- python
env:
- STOCK_SCHEME_PATH
primaryEnv: STOCK_SCHEME_PATH
emoji: 📈
---
# Stock Strategy Tracker 📈
Save per-stock investment strategies extracted from analysis reports into a persistent tracking file, then periodically check if market conditions have triggered any entry, stop-loss, or take-profit conditions. Provide actionable next-step recommendations.
---
## Quick Start
### Install
Copy the skill directory to your agent's skills folder:
```bash
# OpenClaw
cp -r stock-scheme-tracker ~/.openclaw/skills/
# WorkBuddy
cp -r stock-scheme-tracker ~/.workbuddy/skills/
# Claude Code (project-level)
cp -r stock-scheme-tracker .claude/skills/
```
### Configure
Set the environment variable to point to your tracking file:
```bash
# Add to your shell profile (~/.zshrc, ~/.bashrc, etc.)
export STOCK_SCHEME_PATH="$HOME/Documents/stock/scheme.md"
```
Or configure via your agent's config:
```json
// OpenClaw: ~/.openclaw/openclaw.json
{
"skills": {
"entries": {
"stock-scheme-tracker": {
"env": {
"STOCK_SCHEME_PATH": "~/Documents/stock/scheme.md"
}
}
}
}
}
```
If `STOCK_SCHEME_PATH` is not set, the skill defaults to `~/Documents/stock/scheme.md`.
### Verify
```bash
echo $STOCK_SCHEME_PATH
# Should output your configured path
```
---
## Workflow Decision Tree
1. **Determine mode**: Did the user just complete an analysis (→ save mode), or do they want to review existing strategies (→ check mode)?
2. **Save mode**: Extract strategy elements from the analysis, format them, and append to the scheme file.
3. **Check mode**: Read all strategies from the scheme file, fetch latest market data for each stock, evaluate every condition, and output a summary with next-step recommendations.
## Save Mode
### When to Use
- Immediately after completing any stock investment analysis (e.g., DCF valuation, Buffett framework, technical analysis, or any investment report).
- The user says "保存策略", "把策略记下来", "跟踪这个票", "save strategy", "track this stock", or any variant.
### Extraction Rules
From the analysis text, extract these fields. If a field is not mentioned, mark it as "未明确" rather than fabricating.
| Field | Description | Example |
|-------|-------------|---------|
| `stock_code` | Stock code | 688111 |
| `stock_name` | Stock name | 金山办公 |
| `analysis_date` | Analysis date | 2026-04-22 |
| `recommendation` | Core recommendation | 建议观察,等待更好入场时机 |
| `entry_conditions` | Entry conditions (price/valuation/events) | PE≤45-50;股价180-200元 |
| `stop_loss` | Stop-loss condition | 跌破前期低点或技术位 |
| `target_price` | Target price / take-profit range | 机构目标价400元 |
| `exit_signals` | Exit signals (assumption invalidation) | 月活环比下滑;市场份额跌破80% |
| `position_size` | Suggested position size | 轻仓试探 |
| `time_horizon` | Holding period | 3年 |
| `key_assumptions` | Key assumptions | AI商业化持续推进 |
| `open_questions` | Open questions | WPS AI付费转化率是多少 |
| `current_price` | Price at analysis | ~250元 |
| `current_pe` | PE at analysis | 62.46 |
| `market_cap` | Market cap at analysis | 1147亿 |
### Output Format
Append a new section to the scheme file (path from `STOCK_SCHEME_PATH` env var) using the exact format defined in `references/scheme_format.md`. Use Markdown table for entry conditions so they can be machine-parsed later.
### Important
- If the stock already exists in the scheme file, update the existing section rather than duplicating it. Preserve historical entries by appending a date-stamped update note.
- After saving, briefly confirm to the user what was saved.
## Check Mode
### When to Use
- User says "检查策略", "核对建仓条件", "看看哪些票可以买", "策略跟踪", "check strategy", or any variant.
- Can be invoked standalone without any prior analysis.
### Steps
1. Read the scheme file (path from `STOCK_SCHEME_PATH` env var).
2. For each tracked stock, fetch the latest market data (price, PE, key metrics). Use available financial data tools or web search.
3. Evaluate each `entry_conditions` row: compare current value vs target value.
4. Mark each condition as `✅ 已触发`, `❌ 未触发`, or `⚠️ 接近触发`.
5. Check if any `exit_signals` have occurred.
6. Output a clean summary table and specific next-step recommendations per stock.
### Output Format
```markdown
## 策略核对报告 — 2026-04-23
### 金山办公 (688111)
| 条件类型 | 目标 | 当前 | 状态 |
|----------|------|------|------|
| PE建仓 | ≤45-50 | 62.46 | ❌ 未触发 |
| 股价建仓 | 180-200 | 250 | ❌ 未触发 |
**建议**: 继续观察,等待PE回落至50以下或股价跌破200再考虑建仓。
```
## Core Tasks
Here are natural-language prompts that trigger this skill:
- "我刚分析完金山办公,帮我保存策略"
- "检查一下我跟踪的股票,哪些条件触发了"
- "核对建仓条件"
- "跟踪策略"
- "把刚才的分析结论记录到策略跟踪文件"
- "I just finished analyzing Apple, save the strategy"
- "Check all my stock strategies"
## Environment Variable Contract
| Variable | Purpose | Required | Set Via |
|----------|---------|----------|---------|
| `STOCK_SCHEME_PATH` | Path to the strategy tracking Markdown file | No (defaults to `~/Documents/stock/scheme.md`) | Shell profile or agent config |
## Security & Guardrails
1. **Local file only**: Both scripts only read/write a local Markdown file specified by `STOCK_SCHEME_PATH`. No network calls are made by `save_scheme.py` or `check_scheme.py`.
2. **Market data source**: During check mode, market data is fetched via the agent's available financial data tools or web search. This involves querying public market quotes (stock codes and metric names only) — no private strategy content or position details are transmitted.
3. **No trading execution**: This skill provides analysis and recommendations only. It does NOT execute any trades or connect to brokerage accounts.
4. **No credential collection**: This skill only requires `STOCK_SCHEME_PATH` (a file path). It never asks for API keys, tokens, passwords, or any other credentials.
5. **Data integrity**: The save script uses atomic write patterns and preserves historical entries. Existing stock sections are updated in-place rather than overwritten.
6. **Path validation**: The scripts validate that `STOCK_SCHEME_PATH` points to a `.md` file and will create parent directories if needed.
## Troubleshooting
| Problem | Cause | Fix |
|---------|-------|-----|
| "No such file or directory" | `STOCK_SCHEME_PATH` not set or points to invalid path | Set env var or rely on default `~/Documents/stock/scheme.md` |
| Strategy not found during check | Scheme file is empty or format is corrupted | Verify the file uses the format in `references/scheme_format.md` |
| Duplicate entries for same stock | Save mode ran twice without updating | The script auto-detects duplicates; if using manual edit, follow the update convention |
| Chinese characters garbled in output | Terminal encoding mismatch | Ensure your terminal uses UTF-8 (`export LANG=en_US.UTF-8` or `zh_CN.UTF-8`) |
## Release Notes
- **1.1.0** — Fixed metadata consistency (top-level requires/env), added credential collection disclaimer, clarified market data query scope in security section.
- **1.0.0** — Initial release. Save and check modes with Markdown-based tracking file.
## Links
- [Scheme Format Reference](references/scheme_format.md)
- [Save Script](scripts/save_scheme.py)
- [Check Script](scripts/check_scheme.py)
## Publisher
* **Publisher:** @dc
* **License:** MIT-0
* **Source:** https://github.com/dc-stock/stock-scheme-tracker
FILE:scripts/save_scheme.py
#!/usr/bin/env python3
"""
Save stock strategy to the scheme tracking file.
The file path is determined by:
1. --path argument
2. STOCK_SCHEME_PATH environment variable
3. Default: ~/Documents/stock/scheme.md
Reads JSON from stdin, formats it, and appends/updates the scheme file.
Usage:
echo '{"stock_code":"688111","stock_name":"金山办公",...}' | python3 save_scheme.py [--path /path/to/scheme.md]
"""
import sys
import json
import os
import re
import argparse
from datetime import datetime
from pathlib import Path
def get_scheme_path():
"""Resolve scheme file path from arg, env var, or default."""
env_path = os.environ.get("STOCK_SCHEME_PATH", "")
if env_path:
return Path(env_path).expanduser()
return Path.home() / "Documents" / "stock" / "scheme.md"
def ensure_file(scheme_path):
"""Ensure scheme.md exists with proper header."""
scheme_path.parent.mkdir(parents=True, exist_ok=True)
if not scheme_path.exists():
header = f"""# 股票策略跟踪
> 最后更新:{datetime.now().strftime('%Y-%m-%d')}
"""
scheme_path.write_text(header, encoding="utf-8")
def parse_entry_conditions(conditions):
"""Convert entry_conditions list to markdown table rows."""
rows = []
for cond in conditions:
name = cond.get("name", "")
target = cond.get("target", "")
current = cond.get("current", "")
triggered = cond.get("triggered", "❌ 未触发")
note = cond.get("note", "")
rows.append(f"| {name} | {target} | {current} | {triggered} | {note} |")
return "\n".join(rows)
def format_strategy(data):
"""Format a single strategy entry as markdown."""
stock_code = data.get("stock_code", "")
stock_name = data.get("stock_name", "")
analysis_date = data.get("analysis_date", datetime.now().strftime("%Y-%m-%d"))
status = data.get("status", "观察中")
conclusion = data.get("conclusion", "")
current_price = data.get("current_price", "")
current_pe = data.get("current_pe", "")
market_cap = data.get("market_cap", "")
# Entry conditions table
entry_conditions = data.get("entry_conditions", [])
if entry_conditions:
entry_table = parse_entry_conditions(entry_conditions)
else:
entry_table = "| 条件 | 目标值 | 当前值 | 是否触发 | 备注 |\n|------|--------|--------|----------|------|"
# Optional fields
stop_loss = data.get("stop_loss", "")
target_price = data.get("target_price", "")
exit_signals = data.get("exit_signals", [])
key_assumptions = data.get("key_assumptions", [])
open_questions = data.get("open_questions", [])
position_size = data.get("position_size", "")
time_horizon = data.get("time_horizon", "")
# Build markdown
lines = [
f"## {stock_name} ({stock_code})",
"",
f"- **分析日期**: {analysis_date}",
f"- **当前状态**: {status}",
f"- **分析结论**: {conclusion}",
]
if current_price:
lines.append(f"- **分析时股价**: {current_price}")
if current_pe:
lines.append(f"- **分析时PE**: {current_pe}")
if market_cap:
lines.append(f"- **分析时市值**: {market_cap}")
if position_size:
lines.append(f"- **建议仓位**: {position_size}")
if time_horizon:
lines.append(f"- **持有周期**: {time_horizon}")
lines.extend([
"",
"### 建仓条件",
"",
"| 条件 | 目标值 | 当前值 | 是否触发 | 备注 |",
"|------|--------|--------|----------|------|",
])
if entry_conditions:
lines.append(entry_table)
else:
lines.append("| 未明确 | - | - | - | - |")
if stop_loss:
lines.extend(["", "### 止损线", "", f"- {stop_loss}"])
if target_price:
lines.extend(["", "### 目标价", "", f"- {target_price}"])
if exit_signals:
lines.extend(["", "### 退出信号", ""])
for i, sig in enumerate(exit_signals, 1):
lines.append(f"{i}. {sig}")
if key_assumptions:
lines.extend(["", "### 核心假设", ""])
for i, assumption in enumerate(key_assumptions, 1):
lines.append(f"{i}. {assumption}")
if open_questions:
lines.extend(["", "### 未解问题", ""])
for i, q in enumerate(open_questions, 1):
lines.append(f"{i}. {q}")
# History tracking
lines.extend([
"",
"### 历史跟踪",
"",
f"- {analysis_date}: 分析完成,建议{status}",
])
return "\n".join(lines) + "\n\n"
def extract_existing_section(content, stock_code):
"""Extract existing section for a stock if it exists."""
pattern = rf"(## [^\n]*\s*\({re.escape(stock_code)}\).*?)(?=\n## |\Z)"
match = re.search(pattern, content, re.DOTALL)
if match:
return match.group(1), match.start(), match.end()
return None, -1, -1
def update_history(content, start, end, new_entry):
"""Replace old section with updated entry, preserving history."""
old_section = content[start:end]
# Extract existing history
history_match = re.search(r"### 历史跟踪\n\n(.*?)(?=\n### |\Z)", old_section, re.DOTALL)
existing_history = []
if history_match:
existing_history = [line.strip("- ").strip() for line in history_match.group(1).strip().split("\n") if line.strip()]
# Add new history line
today = datetime.now().strftime("%Y-%m-%d")
new_history_line = f"- {today}: 策略更新"
# Prepend new history to existing
all_history = [new_history_line] + existing_history
history_block = "### 历史跟踪\n\n" + "\n".join(all_history)
# Replace history section in new_entry
new_entry_with_history = re.sub(
r"### 历史跟踪\n\n.*?\n\n",
history_block + "\n\n",
new_entry,
flags=re.DOTALL
)
return content[:start] + new_entry_with_history + content[end:]
def main():
parser = argparse.ArgumentParser(description="Save stock strategy to scheme file")
parser.add_argument("--path", type=str, default=None, help="Path to scheme.md (overrides STOCK_SCHEME_PATH env var)")
args = parser.parse_args()
scheme_path = Path(args.path) if args.path else get_scheme_path()
# Read JSON from stdin
raw = sys.stdin.read().strip()
if not raw:
print("Error: No JSON input provided", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
ensure_file(scheme_path)
content = scheme_path.read_text(encoding="utf-8")
stock_code = data.get("stock_code", "")
if not stock_code:
print("Error: stock_code is required", file=sys.stderr)
sys.exit(1)
new_entry = format_strategy(data)
existing, start, end = extract_existing_section(content, stock_code)
if existing:
# Update existing section
content = update_history(content, start, end, new_entry)
action = "updated"
else:
# Append new section
content = content.rstrip() + "\n\n" + new_entry
action = "added"
# Update last_updated timestamp
today = datetime.now().strftime("%Y-%m-%d")
content = re.sub(
r"> 最后更新:\d{4}-\d{2}-\d{2}",
f"> 最后更新:{today}",
content
)
scheme_path.write_text(content, encoding="utf-8")
print(f"Successfully {action} strategy for {data.get('stock_name', stock_code)} ({stock_code})")
if __name__ == "__main__":
main()
FILE:scripts/check_scheme.py
#!/usr/bin/env python3
"""
Check stock strategies in the scheme tracking file.
The file path is determined by:
1. --path argument
2. STOCK_SCHEME_PATH environment variable
3. Default: ~/Documents/stock/scheme.md
Two modes:
1. Parse mode: Extract all tracked stocks and their conditions as JSON
Usage: python3 check_scheme.py --parse [--path /path/to/scheme.md]
2. Report mode: Generate markdown report from JSON with latest market data
Usage: echo '{"stocks":[...]}' | python3 check_scheme.py --report
The JSON format for report mode:
{
"stocks": [
{
"code": "688111",
"name": "金山办公",
"status": "观察中",
"conditions": [
{"name": "PE(TTM)", "target": "≤45-50", "current": "62.46", "triggered": "❌ 未触发", "note": "3年分位点7.33%"}
],
"stop_loss": "跌破前期低点",
"target_price": "机构目标价400元",
"exit_signals": ["月活环比下滑"],
"recommendation": "继续观察,等待PE回落至50以下"
}
]
}
"""
import sys
import json
import re
import os
import argparse
from pathlib import Path
from datetime import datetime
def get_scheme_path():
"""Resolve scheme file path from arg, env var, or default."""
env_path = os.environ.get("STOCK_SCHEME_PATH", "")
if env_path:
return Path(env_path).expanduser()
return Path.home() / "Documents" / "stock" / "scheme.md"
def parse_scheme(scheme_path):
"""Parse scheme.md and return list of stock strategies."""
if not scheme_path.exists():
return []
content = scheme_path.read_text(encoding="utf-8")
# Split by stock sections (## Stock Name (CODE))
pattern = r"##\s+(.+?)\s*\(([A-Z0-9]+)\)\n"
matches = list(re.finditer(pattern, content))
stocks = []
for i, match in enumerate(matches):
name = match.group(1).strip()
code = match.group(2).strip()
start = match.start()
end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
section = content[start:end]
# Parse status
status_match = re.search(r"\*\*当前状态\*\*:\s*(.+)", section)
status = status_match.group(1).strip() if status_match else "未知"
# Parse entry conditions table
conditions = []
table_match = re.search(
r"###\s+建仓条件\n\n\|[^\n]+\|\n\|[-\s|]+\|\n(.*?)(?=\n###|\n\n##|\Z)",
section,
re.DOTALL
)
if table_match:
table_body = table_match.group(1).strip()
for line in table_body.split("\n"):
line = line.strip()
if not line or line.startswith("|") is False:
continue
cols = [c.strip() for c in line.strip("|").split("|")]
if len(cols) >= 4:
conditions.append({
"name": cols[0],
"target": cols[1],
"current": cols[2],
"triggered": cols[3] if len(cols) > 3 else "",
"note": cols[4] if len(cols) > 4 else ""
})
# Parse stop loss
stop_loss = ""
sl_match = re.search(r"###\s+止损线\n\n-?\s*(.+?)(?=\n###|\n\n##|\Z)", section, re.DOTALL)
if sl_match:
stop_loss = sl_match.group(1).strip()
# Parse target price
target_price = ""
tp_match = re.search(r"###\s+目标价\n\n-?\s*(.+?)(?=\n###|\n\n##|\Z)", section, re.DOTALL)
if tp_match:
target_price = tp_match.group(1).strip()
# Parse exit signals
exit_signals = []
es_match = re.search(r"###\s+退出信号\n\n(.*?)(?=\n###|\n\n##|\Z)", section, re.DOTALL)
if es_match:
for line in es_match.group(1).strip().split("\n"):
line = line.strip()
if line and (line[0].isdigit() or line.startswith("-")):
exit_signals.append(re.sub(r"^\d+\.\s*", "", line).strip("- "))
stocks.append({
"code": code,
"name": name,
"status": status,
"conditions": conditions,
"stop_loss": stop_loss,
"target_price": target_price,
"exit_signals": exit_signals
})
return stocks
def generate_report(data):
"""Generate markdown report from JSON with latest market data."""
stocks = data.get("stocks", [])
today = datetime.now().strftime("%Y-%m-%d")
lines = [
f"# 策略核对报告 — {today}",
"",
f"> 共跟踪 {len(stocks)} 只股票",
"",
]
triggered_count = 0
for stock in stocks:
code = stock.get("code", "")
name = stock.get("name", "")
status = stock.get("status", "")
conditions = stock.get("conditions", [])
recommendation = stock.get("recommendation", "")
stop_loss = stock.get("stop_loss", "")
target_price = stock.get("target_price", "")
exit_signals = stock.get("exit_signals", [])
lines.extend([
f"## {name} ({code})",
"",
f"- **当前状态**: {status}",
])
if conditions:
lines.extend([
"",
"| 条件 | 目标 | 当前 | 状态 | 备注 |",
"|------|------|------|------|------|",
])
for cond in conditions:
cname = cond.get("name", "")
target = cond.get("target", "")
current = cond.get("current", "")
triggered = cond.get("triggered", "")
note = cond.get("note", "")
lines.append(f"| {cname} | {target} | {current} | {triggered} | {note} |")
if "✅" in triggered or "已触发" in triggered:
triggered_count += 1
if stop_loss:
lines.extend(["", f"**止损线**: {stop_loss}"])
if target_price:
lines.extend(["", f"**目标价**: {target_price}"])
if exit_signals:
lines.extend(["", "**退出信号**:", ""])
for sig in exit_signals:
lines.append(f"- {sig}")
if recommendation:
lines.extend(["", f"**建议**: {recommendation}"])
lines.append("")
# Summary
lines.insert(4, f"> 其中 {triggered_count} 个条件已触发")
lines.insert(5, "")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Check stock strategies")
parser.add_argument("--parse", action="store_true", help="Parse scheme.md and output JSON")
parser.add_argument("--report", action="store_true", help="Generate report from JSON input")
parser.add_argument("--path", type=str, default=None, help="Path to scheme.md (overrides STOCK_SCHEME_PATH env var)")
args = parser.parse_args()
scheme_path = Path(args.path) if args.path else get_scheme_path()
if args.parse:
stocks = parse_scheme(scheme_path)
print(json.dumps({"stocks": stocks}, ensure_ascii=False, indent=2))
return
if args.report:
raw = sys.stdin.read().strip()
if not raw:
print("Error: No JSON input for report mode", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
report = generate_report(data)
print(report)
return
# Default: just parse and print summary
stocks = parse_scheme(scheme_path)
if not stocks:
print("没有找到任何股票策略。请先使用 save 模式保存策略。")
return
print(f"共跟踪 {len(stocks)} 只股票:")
for s in stocks:
print(f" - {s['name']} ({s['code']}): {s['status']}, {len(s['conditions'])} 个条件")
if __name__ == "__main__":
main()
FILE:references/scheme_format.md
# scheme.md 格式规范
这是策略跟踪文件(`STOCK_SCHEME_PATH` 环境变量指向的文件)的标准格式。所有条目必须遵循此格式,以便脚本能够正确解析。
> 默认路径:`~/Documents/stock/scheme.md`(可通过 `STOCK_SCHEME_PATH` 环境变量覆盖)
## 文件头部
文件以一级标题开头:
```markdown
# 股票策略跟踪
> 最后更新:YYYY-MM-DD
```
## 个股条目格式
每个股票一个二级标题,格式固定为 `## 股票名称 (股票代码)`。
```markdown
## 金山办公 (688111)
- **分析日期**: 2026-04-22
- **当前状态**: 观察中 / 已建仓 / 已清仓 / 暂停跟踪
- **分析结论**: 一句话核心建议
### 建仓条件
| 条件 | 目标值 | 当前值 | 是否触发 | 备注 |
|------|--------|--------|----------|------|
| PE(TTM) | ≤45-50 | 62.46 | ❌ 未触发 | 3年分位点7.33% |
| 股价 | 180-200元 | 250元 | ❌ 未触发 | 需大盘配合 |
### 止损线
- 跌破前期低点
- 月活环比下滑
### 目标价
- 机构目标价:400元
### 退出信号
1. 月活环比下滑
2. 政府IT支出削减
3. 市场份额跌破80%
### 核心假设
- AI商业化持续推进,付费转化率提升
### 未解问题
- WPS AI付费转化率具体是多少?
- 海外扩张进度如何?
### 历史跟踪
- 2026-04-22: 分析完成,PE 62.46,股价250元,建议观察
```
## 字段说明
| 字段 | 必填 | 说明 |
|------|------|------|
| 分析日期 | 是 | 完成分析的日期 |
| 当前状态 | 是 | 观察中/已建仓/已清仓/暂停跟踪 |
| 分析结论 | 是 | 一句话核心建议 |
| 建仓条件 | 是 | 表格形式,必须包含"条件、目标值、当前值、是否触发、备注"列 |
| 止损线 | 否 | 触发止损的具体条件 |
| 目标价 | 否 | 预期目标价位 |
| 退出信号 | 否 | 假设证伪时的退出条件 |
| 核心假设 | 否 | 投资决策依赖的关键假设 |
| 未解问题 | 否 | 影响决策但尚未验证的问题 |
| 历史跟踪 | 是 | 按时间倒序记录每次检查/操作 |
## 状态流转
```
观察中 → 已建仓(条件触发后买入)
已建仓 → 已清仓(止损或止盈)
已建仓 → 观察中(减仓后继续观察)
任何状态 → 暂停跟踪(逻辑证伪或长期不符合条件)
```
## 脚本解析约定
- `## 股票名称 (股票代码)` 作为分隔符
- 建仓条件表格使用 Markdown 标准表格语法
- `当前值` 和 `是否触发` 在 save 时填写分析时的数据
- `check` 模式会读取表格,用最新数据替换 `当前值` 和 `是否触发` 列
- `历史跟踪` 列表按时间倒序,新记录 prepend