financial-data
npx skills add https://github.com/dseirz-rgb/worker --skill financial-data
Agent 安装分布
Skill 文档
Financial Data (éèæ°æ®å¤ç)
ð° æ ¸å¿ç念: éèæ°æ®æ¯æèµå³ççåºç¡ï¼å¿ é¡»ç¡®ä¿æ°æ®åç¡®æ§ã宿´æ§åä¸è´æ§ãåå¾è¿ï¼åå¾åºã
ð´ 第ä¸ååï¼æ°æ®éªè¯ä¼å
â éè¯¯åæ³: ç´æ¥å¯¼å
¥æ°æ®ï¼åè®¾æ°æ®æ£ç¡®
â
æ£ç¡®åæ³: 导å
¥ â éªè¯ â æ¸
æ´ â åéªè¯ â åå¨
â éè¯¯åæ³: "è¿æ¯å¸åæ°æ®ï¼åºè¯¥æ²¡é®é¢"
â
æ£ç¡®åæ³: ä»»ä½å¤é¨æ°æ®é½è¦ç»è¿å®æ´éªè¯æµç¨
When to Use This Skill
ä½¿ç¨æ¤æè½å½ä½ éè¦ï¼
- ä» IBKRãGmailãGoogle Drive å¯¼å ¥äº¤ææ°æ®
- å¤çæä»å¿«ç §æ°æ®
- 计ç®é£é©ææ ï¼VaRã忤ã夿®æ¯ççï¼
- æ°æ®æ¸ æ´åæ ¼å¼è½¬æ¢
- éªè¯æ°æ®å®æ´æ§åä¸è´æ§
- å¤çå¤å¸ç§æ°æ®è½¬æ¢
Not For / Boundaries
æ¤æè½ä¸éç¨äºï¼
- 宿¶è¡æ æ°æ®è·åï¼åè api-integration skillï¼
- AI åæå建议çæï¼åè agent ç¸å ³ä»£ç ï¼
- æ°æ®åº schema åæ´ï¼åè database-migration skillï¼
Quick Reference
ð¯ æ°æ®å¤ç工使µ
æ°æ®æº â è·ååå§æ°æ® â æ ¼å¼éªè¯ â æ°æ®æ¸
æ´ â ä¸å¡éªè¯ â åå¨ â 确认
â â
IBKR/Gmail/Drive 失败 â è®°å½é误 â 人工å¤ç
ð æ°æ®å¯¼å ¥åå¿ é®æ¸ å
| é®é¢ | ç®ç |
|---|---|
| 1. æ°æ®æºæ¯ä»ä¹ï¼ | ç¡®å®è§£ææ ¼å¼ï¼XML/CSV/JSONï¼ |
| 2. æ°æ®æ¶é´èå´ï¼ | é¿å éå¤å¯¼å ¥æéæ¼ |
| 3. å¸ç§æ¯ä»ä¹ï¼ | ç¡®å®æ±ç转æ¢éæ± |
| 4. ææ²¡æå·²åå¨çæ°æ®ï¼ | å³å®æ¯è¦çè¿æ¯å¢éæ´æ° |
| 5. æ°æ®éæå¤å¤§ï¼ | è¯ä¼°æ¯å¦éè¦åæ¹å¤ç |
â æ°æ®è´¨éæ£æ¥æ¸ å
| æ£æ¥é¡¹ | 说æ | 严éç¨åº¦ |
|---|---|---|
| å¿ å¡«åæ®µå®æ´ | ticker, date, quantity ç | ð´ 黿 |
| æ°å¼èå´åç | ä»·æ ¼ > 0, æ°é â 0 | ð´ 黿 |
| æ¥ææ ¼å¼æ£ç¡® | YYYY-MM-DD | ð´ 黿 |
| å¸ç§ææ | USD/HKD/CNY | ð¡ è¦å |
| æ éå¤è®°å½ | åä¸äº¤æä¸éå¤ | ð¡ è¦å |
| æ°æ®è¿ç»æ§ | æ ç¼ºå¤±æ¥æ | ð¢ æç¤º |
æ°æ®æºéææå
1. IBKR Flex Query å¯¼å ¥
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 æ°æ®å¤ç
2. Gmail å¯¼å ¥ï¼äº¤æç¡®è®¤é®ä»¶ï¼
ä»å¸å确认é®ä»¶ä¸æåäº¤ææ°æ®ã
æ¯æçé®ä»¶æ ¼å¼ï¼
- IBKR 交æç¡®è®¤
- å¯éçç交æç¡®è®¤
- èèè¯å¸äº¤æç¡®è®¤
è§£ææµç¨ï¼
1. éè¿ Gmail API è·åé®ä»¶
2. è§£æé®ä»¶æ£æï¼HTML/çº¯ææ¬ï¼
3. ä½¿ç¨æ£åè¡¨è¾¾å¼æå交æä¿¡æ¯
4. éªè¯å¹¶æ ¼å¼åæ°æ®
3. Google Drive å¯¼å ¥ï¼CSV/Excelï¼
ä» Google Drive å¯¼å ¥å岿°æ®æä»¶ã
æ¯æçæä»¶æ ¼å¼ï¼
- CSVï¼æ¨èï¼
- Excel (.xlsx)
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)}%`);
}
}
}
æ°æ®æ¸ æ´æä½³å®è·µ
1. è¡ç¥¨ä»£ç æ åå
// ç»ä¸è¡ç¥¨ä»£ç æ ¼å¼
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, '');
}
}
2. æ¥ææ ¼å¼æ åå
// ç»ä¸æ¥ææ ¼å¼ä¸º 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;
}
3. éé¢åæ±çå¤ç
// æ±ç常éï¼åºä»å®æ¶æ°æ®è·åï¼
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;
}
}
4. é夿°æ®å¤ç
// 交æè®°å½å»é
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 |
Examples
Example 1: ä» IBKR å¯¼å ¥æ°æ®
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);
}
Example 2: éªè¯æä»æ°æ®ä¸è´æ§
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}`);
}
}
Example 3: 计ç®é£é©ææ
Input: “è®¡ç®æè¿ 30 天çé£é©ææ ”
Steps:
- è·ååå¼å岿°æ®
- è®¡ç®æ¥æ¶çç
- 计ç®å项é£é©ææ
- åå¨å° risk_metrics 表
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);
常è§é®é¢å¤ç
Q1: IBKR æ°æ®è·å失败
å¯è½åå ï¼
- Token è¿æ
- CORS 代çä¸å¯ç¨
- ç½ç»é®é¢
è§£å³æ¹æ¡ï¼
// 1. æ£æ¥ Token æææ§
// ç»å½ IBKR è´¦æ·ç®¡ç â æ¥è¡¨ â Flex Queries â æ£æ¥ Token
// 2. å°è¯å¤ç¨ä»£ç
const FALLBACK_PROXIES = [
'https://corsproxy.io/?',
'https://api.allorigins.win/raw?url=',
];
// 3. å¢å éè¯æ¬¡æ°åè¶
æ¶æ¶é´
Q2: æ°æ®éå¤å¯¼å ¥
è§£å³æ¹æ¡ï¼
// ä½¿ç¨ upsert èé insert
const { error } = await supabase
.from('transactions')
.upsert(transactions, {
onConflict: 'date,ticker,action,quantity,price',
ignoreDuplicates: true
});
Q3: æ±çæ°æ®ä¸åç¡®
è§£å³æ¹æ¡ï¼
// 1. 使ç¨å®æ¶æ±ç API
const rates = await fetchExchangeRates();
// 2. æä½¿ç¨ IBKR æä¾çæ±ç
const fxRate = trade.fxRateToBase;
References
references/data-schemas.md: 宿´çæ°æ®ç»æå®ä¹references/validation-rules.md: æ°æ®éªè¯è§å详解references/import-templates.md: æ°æ®å¯¼å ¥æ¨¡æ¿
Maintenance
- Sources: 项ç®å®é 代ç , IBKR API ææ¡£, éèæ°æ®å¤çæä½³å®è·µ
- Last Updated: 2025-01-01
- Known Limits:
- IBKR Flex Query æè¯·æ±é¢çéå¶
- å岿°æ®å¯¼å ¥éè¦æå¨è§¦å
- æ±çæ°æ®å¯è½æå»¶è¿