xiaohongshu
xiaohongshu
financial-data
金融数据处理技能:交易数据导入、持仓数据管理、风险指标计算。 Use when: 需要处理交易记录、导入持仓数据、计算风险指标、数据清洗验证。 Triggers: "交易", "持仓", "导入", "数据", "IBKR", "风险", "净值", "回撤"
Full skill instructions
💰 核心理念: 金融数据是投资决策的基础,必须确保数据准确性、完整性和一致性。垃圾进,垃圾出。
❌ 错误做法: 直接导入数据,假设数据正确
✅ 正确做法: 导入 → 验证 → 清洗 → 再验证 → 存储
❌ 错误做法: "这是券商数据,应该没问题"
✅ 正确做法: 任何外部数据都要经过完整验证流程
使用此技能当你需要:
此技能不适用于:
数据源 → 获取原始数据 → 格式验证 → 数据清洗 → 业务验证 → 存储 → 确认
↓ ↓
IBKR/Gmail/Drive 失败 → 记录错误 → 人工处理
| 问题 | 目的 |
|---|---|
| 1. 数据源是什么? | 确定解析格式(XML/CSV/JSON) |
| 2. 数据时间范围? | 避免重复导入或遗漏 |
| 3. 币种是什么? | 确定汇率转换需求 |
| 4. 有没有已存在的数据? | 决定是覆盖还是增量更新 |
| 5. 数据量有多大? | 评估是否需要分批处理 |
| 检查项 | 说明 | 严重程度 |
|---|---|---|
| 必填字段完整 | ticker, date, quantity 等 | 🔴 阻断 |
| 数值范围合理 | 价格 > 0, 数量 ≠ 0 | 🔴 阻断 |
| 日期格式正确 | YYYY-MM-DD | 🔴 阻断 |
| 币种有效 | USD/HKD/CNY | 🟡 警告 |
| 无重复记录 | 同一交易不重复 | 🟡 警告 |
| 数据连续性 | 无缺失日期 | 🟢 提示 |
IBKR 是主要数据源,通过 Flex Query API 获取数据。
配置要求:
// 环境变量
VITE_CORS_PROXY_URL=https://your-proxy.workers.dev
// Flex Query 配置
const IB_TOKEN = "your_token";
const IB_QUERY_ID = "your_query_id";
数据获取流程:
1. 请求生成报表 (SendRequest)
2. 等待报表生成 (轮询 GetStatement)
3. 解析 XML 响应
4. 提取各类数据:
- EquitySummaryByReportDateInBase → 账户摘要
- OpenPosition → 持仓数据
- Trade → 交易记录
- ChangeInNAV → 净值变化
- CashReportCurrency → 多币种现金
关键代码位置:
client/src/services/ibkrFlexQuery.ts - IBKR 数据获取client/src/services/ibkrData.ts - IBKR 数据处理从券商确认邮件中提取交易数据。
支持的邮件格式:
解析流程:
1. 通过 Gmail API 获取邮件
2. 解析邮件正文(HTML/纯文本)
3. 使用正则表达式提取交易信息
4. 验证并格式化数据
从 Google Drive 导入历史数据文件。
支持的文件格式:
CSV 格式要求:
date,ticker,action,quantity,price,fee,currency,notes
2025-01-15,AAPL,BUY,100,185.50,1.00,USD,加仓
2025-01-16,AAPL,SELL,50,188.00,1.00,USD,止盈
// 必填字段验证
const requiredFields = ['date', 'ticker', 'action', 'quantity', 'price'];
// 数值范围验证
const validations = {
price: (v: number) => v > 0,
quantity: (v: number) => v !== 0,
fee: (v: number) => v >= 0,
};
// 枚举值验证
const validActions = ['BUY', 'SELL', 'SHORT', 'COVER', 'DEPOSIT', 'WITHDRAW'];
const validCurrencies = ['USD', 'HKD', 'CNY'];
const validMarkets = ['US', 'HK', 'CN'];
// 持仓一致性检查
function validatePositions(positions: Position[], transactions: Transaction[]) {
// 1. 计算交易累计数量
const calculatedQty = calculateFromTransactions(transactions);
// 2. 与持仓数量对比
for (const pos of positions) {
const expected = calculatedQty[pos.ticker] || 0;
if (pos.quantity !== expected) {
console.warn(`持仓不一致: ${pos.ticker} 实际=${pos.quantity} 计算=${expected}`);
}
}
}
// 净值连续性检查
function validateNetWorthHistory(records: NetWorthRecord[]) {
const sorted = records.sort((a, b) => a.date.localeCompare(b.date));
for (let i = 1; i < sorted.length; i++) {
const prev = sorted[i - 1];
const curr = sorted[i];
// 检查日期连续性(工作日)
const daysDiff = getBusinessDaysDiff(prev.date, curr.date);
if (daysDiff > 1) {
console.warn(`净值数据缺失: ${prev.date} 到 ${curr.date}`);
}
// 检查异常波动(单日变化超过 10%)
const changePercent = (curr.netWorth - prev.netWorth) / prev.netWorth * 100;
if (Math.abs(changePercent) > 10) {
console.warn(`异常波动: ${curr.date} 变化 ${changePercent.toFixed(2)}%`);
}
}
}
// 统一股票代码格式
function normalizeSymbol(symbol: string, market: Market): string {
switch (market) {
case 'HK':
// 港股:补齐到 5 位数字
return symbol.replace(/^0+/, '').padStart(5, '0');
case 'CN':
// A股:保持 6 位数字
return symbol.padStart(6, '0');
case 'US':
default:
// 美股:大写字母
return symbol.toUpperCase().replace(/[^A-Z]/g, '');
}
}
// 统一日期格式为 YYYY-MM-DD
function normalizeDate(dateStr: string): string {
// 处理 IBKR 格式: 20250115
if (/^\d{8}$/.test(dateStr)) {
return `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}`;
}
// 处理 MM/DD/YYYY 格式
if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(dateStr)) {
const [m, d, y] = dateStr.split('/');
return `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
}
// 已经是标准格式
return dateStr;
}
// 汇率常量(应从实时数据获取)
const EXCHANGE_RATES = {
USD_CNY: 7.04,
HKD_CNY: 0.93,
};
// 转换为 CNY
function toCNY(amount: number, currency: Currency): number {
switch (currency) {
case 'USD':
return amount * EXCHANGE_RATES.USD_CNY;
case 'HKD':
return amount * EXCHANGE_RATES.HKD_CNY;
case 'CNY':
default:
return amount;
}
}
// 交易记录去重
function deduplicateTransactions(transactions: Transaction[]): Transaction[] {
const seen = new Set<string>();
return transactions.filter(tx => {
// 生成唯一键:日期 + 股票 + 动作 + 数量 + 价格
const key = `${tx.date}_${tx.ticker}_${tx.action}_${tx.quantity}_${tx.price}`;
if (seen.has(key)) {
console.warn(`发现重复交易: ${key}`);
return false;
}
seen.add(key);
return true;
});
}
| 指标 | 公式 | 说明 |
|---|---|---|
| 最大回撤 | (峰值 - 谷值) / 峰值 | 历史最大亏损幅度 |
| 夏普比率 | (收益率 - 无风险利率) / 波动率 | 风险调整后收益 |
| VaR (95%) | 历史分位数法 | 95% 置信度下的最大损失 |
| 胜率 | 盈利交易数 / 总交易数 | 交易成功率 |
| 盈亏比 | 平均盈利 / 平均亏损 | 风险回报比 |
// 计算最大回撤
function calculateMaxDrawdown(netWorthHistory: number[]): {
maxDrawdown: number;
maxDrawdownPercent: number;
peakDate: string;
troughDate: string;
} {
let peak = netWorthHistory[0];
let maxDrawdown = 0;
let maxDrawdownPercent = 0;
for (const value of netWorthHistory) {
if (value > peak) {
peak = value;
}
const drawdown = peak - value;
const drawdownPercent = drawdown / peak;
if (drawdownPercent > maxDrawdownPercent) {
maxDrawdown = drawdown;
maxDrawdownPercent = drawdownPercent;
}
}
return { maxDrawdown, maxDrawdownPercent, peakDate: '', troughDate: '' };
}
// 计算夏普比率
function calculateSharpeRatio(
returns: number[],
riskFreeRate: number = 0.02
): number {
const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance);
// 年化
const annualizedReturn = avgReturn * 252;
const annualizedStdDev = stdDev * Math.sqrt(252);
return (annualizedReturn - riskFreeRate) / annualizedStdDev;
}
详细的数据库 schema 定义请参考:
references/data-schemas.md - 完整的数据结构定义| 表名 | 用途 | 主键 |
|---|---|---|
transactions | 交易记录 | uuid |
stock_positions | 股票持仓快照 | bigserial |
option_positions | 期权持仓快照 | bigserial |
dashboard_snapshots | 每日驾驶舱快照 | bigserial |
risk_metrics | 风险指标 | bigserial |
watchlist | 观察列表 | uuid |
Input: "需要从 IBKR 导入最新的交易和持仓数据"
Steps:
fetchIBKRFlexQuery() 获取数据syncIBKRToSupabase() 同步到数据库Expected Output:
import { syncIBKRToSupabase } from '@/services/ibkrFlexQuery';
const result = await syncIBKRToSupabase(true, (stage, progress) => {
console.log(`[${progress}%] ${stage}`);
});
if (result.success) {
console.log('同步成功:', result.data);
} else {
console.error('同步失败:', result.message);
}
Input: "检查持仓数据是否与交易记录一致"
Steps:
Expected Output:
// 获取数据
const transactions = await getTransactions();
const positions = await getPositions();
// 计算预期持仓
const expectedPositions = calculatePositionsFromTransactions(transactions);
// 对比
for (const pos of positions) {
const expected = expectedPositions[pos.ticker] || 0;
if (pos.quantity !== expected) {
console.warn(`❌ ${pos.ticker}: 实际=${pos.quantity}, 预期=${expected}`);
} else {
console.log(`✅ ${pos.ticker}: ${pos.quantity}`);
}
}
Input: "计算最近 30 天的风险指标"
Steps:
Expected Output:
// 获取净值数据
const netWorthHistory = await getNetWorthHistory(30);
// 计算日收益率
const returns = calculateDailyReturns(netWorthHistory);
// 计算风险指标
const metrics = {
maxDrawdown: calculateMaxDrawdown(netWorthHistory),
sharpeRatio: calculateSharpeRatio(returns),
var95: calculateVaR(returns, 0.95),
volatility: calculateVolatility(returns),
};
// 存储
await saveRiskMetrics(metrics);
可能原因:
解决方案:
// 1. 检查 Token 有效性
// 登录 IBKR 账户管理 → 报表 → Flex Queries → 检查 Token
// 2. 尝试备用代理
const FALLBACK_PROXIES = [
'https://corsproxy.io/?',
'https://api.allorigins.win/raw?url=',
];
// 3. 增加重试次数和超时时间
解决方案:
// 使用 upsert 而非 insert
const { error } = await supabase
.from('transactions')
.upsert(transactions, {
onConflict: 'date,ticker,action,quantity,price',
ignoreDuplicates: true
});
解决方案:
// 1. 使用实时汇率 API
const rates = await fetchExchangeRates();
// 2. 或使用 IBKR 提供的汇率
const fxRate = trade.fxRateToBase;
references/data-schemas.md: 完整的数据结构定义references/validation-rules.md: 数据验证规则详解references/import-templates.md: 数据导入模板xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience