Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ Where each capability stands today. Live module inventory and the end-to-end dec
| ✅ Shipped | Scheduler / cron agent mode | D-9 | `scheduler_jobs` + advisory lock + `/api/scheduler/*` management plane |
| ✅ Shipped | RiskGuard per-account isolation | D-9.1a | `RiskGuardFactory` removes cross-account state bleed |
| ✅ Shipped | Multi-market data sources — web search + financial fundamentals | D-10 | zero-key DDGS web search · `baostock` is the logical A-share venue: Tencent HTTPS bars/ticker + Baostock fundamentals/calendar/constituents; yfinance covers global markets including HK · analyst integration + fallback · per-market lookbackDays |
| ✅ Shipped | Unified multi-market financial news | D-12 | `GET /news` + `data.get_news` · Eastmoney / SEC / HKEX / Crypto feeds · PIT `as_of` filtering · observable provider failures · explicit index/ETF proxy labels for market-level stock news |
| ✅ Shipped | Risk engine — all 5 rules live in HTTP path | D-9 closed | `closed_trades` writes from HTTP order flow; `RoutingCalendar` for US equity + crypto; all trade-based rules trigger on real data |
| ✅ Shipped | `askUserChoice` — `ask` permission path | D-11 (issue #2) | pending-permission flow resolves the `ask` state (no longer a workaround) |
| ✅ Shipped | `permissions.yaml` configuration | D-11 (issue #4) | `config/permissions.default.yaml` + `yaml_loader.ts` replace the hard-coded `defaults.ts` |
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ Inalpha 把*调度*和*算力*分开:agent runtime 负责扇出网格、聚合
| ✅ 已上线 | Scheduler / cron agent 模式 | D-9 | `scheduler_jobs` + advisory lock + `/api/scheduler/*` 管理面 |
| ✅ 已上线 | RiskGuard 账户级隔离 | D-9.1a | `RiskGuardFactory` 去除跨账户状态串联 |
| ✅ 已上线 | 多市场数据源 — web 搜索 + 财报基本面 | D-10 | DDGS 零 key web search · `baostock` 是 A 股逻辑 venue:腾讯 HTTPS 行情/最新价 + Baostock 基本面/日历/成分;yfinance 覆盖全球(含港股)· analyst 接入 + 兜底 · lookbackDays 按市场分化 |
| ✅ 已上线 | 统一多市场财经新闻 | D-12 | `GET /news` + `data.get_news` · 东财 / SEC / HKEX / Crypto feed · PIT `as_of` 过滤 · provider 故障可观察 · 股票市场级新闻明确标注指数/ETF 代理口径 |
| ✅ 已上线 | 风控引擎 — 5 条规则全在 HTTP 路径激活 | D-9 收口 | `closed_trades` 由 HTTP 订单流写入;`RoutingCalendar` 覆盖美股 + crypto;trade-based 规则全在真实数据上触发 |
| ✅ 已上线 | `askUserChoice` — `ask` 权限路径 | D-11(issue #2) | pending-permission 流程把 `ask` 状态从 workaround 救回(已收口) |
| ✅ 已上线 | `permissions.yaml` 配置化 | D-11(issue #4) | `config/permissions.default.yaml` + `yaml_loader.ts` 替代 `defaults.ts` 硬编码 |
Expand Down
6 changes: 6 additions & 0 deletions docs/04-current-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ D-9 把决策护栏(Plan/Exec + 风控 + 沙盒)做扎实后,D-10 在**数
`lookbackDays`(crypto 1h/4h ≈30d;A股/港股/日股 akshare 1d ≈180d;美股/全球指数
yfinance 1d ≈90d),避免"30 天 K 线<20 根交易日"无统计意义;research client
超时放宽到 300s 给 A股慢路径留余量。
- **统一多市场新闻**:`GET /news` 与 `data.get_news` 统一 `market_news` / `media` /
`disclosure` 契约,首批接入东财、SEC、HKEX 与 Crypto 专业 feed;股票市场级快讯
用代表指数/ETF 的 Yahoo 新闻代理并在条目中保留代理 ticker,不冒充完整新闻线。
请求支持 `as_of` / `since` 的 PIT 过滤,响应保留逐 provider 状态、`is_partial` 与
来源等级;旧 `venue + symbol` 请求继续兼容。`SentimentAnalyst` 与 `MacroAnalyst`
已消费结构化新闻,来源不可用时再显式降级 web 搜索。
- **金融时效**:web/news 与基本面均无 API key 依赖;analyst 数据源不可用时降级到
LLM-only 并标低 confidence,不静默用过时数据。

Expand Down
55 changes: 50 additions & 5 deletions packages/orchestration/src/clients/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export type Ticker = {
stale_seconds: number;
};

export type NewsMarket =
| "cn" | "us" | "hk" | "jp" | "kr" | "au" | "in" | "uk"
| "de" | "fr" | "ca" | "br" | "global" | "crypto";

export class DataClient {
private readonly http: HttpClient;

Expand Down Expand Up @@ -111,20 +115,61 @@ export class DataClient {
}
}

async getNews(params: {
market?: NewsMarket;
venue?: string;
symbol?: string;
asOf?: string;
since?: string;
kinds?: Array<"market_news" | "media" | "disclosure">;
language?: string;
limit?: number;
}): Promise<Record<string, unknown>> {
try {
return await this.http.get<Record<string, unknown>>("/news", {
...(params.market ? { market: params.market } : {}),
...(params.venue ? { venue: params.venue } : {}),
...(params.symbol ? { symbol: params.symbol } : {}),
...(params.asOf ? { as_of: params.asOf } : {}),
...(params.since ? { since: params.since } : {}),
...(params.kinds?.length ? { kinds: params.kinds.join(",") } : {}),
...(params.language ? { language: params.language } : {}),
limit: String(params.limit ?? 10),
});
} catch (err) {
return {
market: params.market,
symbol: params.symbol,
items: [],
providers: [],
is_partial: true,
error: err instanceof HttpClientError ? `upstream ${err.status}: ${err.message}` : String(err),
};
}
}

async getMarketNews(params: {
market?: string;
limit?: number;
}): Promise<Record<string, unknown>> {
if ((params.market ?? "cn") !== "cn") {
return await this.getNews({
market: params.market as NewsMarket,
limit: params.limit ?? 20,
kinds: ["market_news", "media"],
});
}
try {
return await this.http.get<Record<string, unknown>>("/market/news", {
market: params.market ?? "cn",
market: "cn",
limit: String(params.limit ?? 20),
});
} catch (err) {
if (err instanceof HttpClientError) {
return { market: params.market ?? "cn", items: [], error: `upstream ${err.status}: ${err.message}` };
}
return { market: params.market ?? "cn", items: [], error: String(err) };
return {
market: "cn",
items: [],
error: err instanceof HttpClientError ? `upstream ${err.status}: ${err.message}` : String(err),
};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ Inalpha 是金融 agent —— "数据 stale 几天" 等于"建议过时"。任
市场级工具传对应 market;该市场没有市场级工具时,用
"web.search_news + 该市场代表性指数的 get_bars"组合替代维度 1-4。

**市场快讯 market code**(供 data.get_market_news 使用):
- A股 \`cn\`、美股 \`us\`、港股 \`hk\`、日股 \`jp\`、韩股 \`kr\`、澳股 \`au\`
- 印股 \`in\`、英股 \`uk\`、德股 \`de\`、法股 \`fr\`、加拿大 \`ca\`、巴西 \`br\`
- 全球市场 \`global\`、加密市场 \`crypto\`
- 除 A股专业快讯和 Crypto 专业 feed 外,股票市场返回代表性指数/ETF 新闻代理;
必须根据响应 \`symbols\` 标注代理口径,不得称为完整市场新闻线

**结论纪律**:
- 每个维度的结论必须指得回工具返回的数据;某维度拿不到数据 → **显式声明该维度缺失
并跳过**,继续完成其余维度——不要因为一个维度空就放弃整个归因、只讲技术面
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const TOOL_CATALOG = `
其他市场返 yahoo 格式(venue 字段标明配哪个数据源)。
**你已判断出市场分类时显式传 venue**(美股/港股/全球 → yfinance,A股 → baostock);
query 的语言 ≠ 市场——中文名问美股公司极常见,别把市场判断丢给 auto 兜底。
- **data.get_news —— 多市场单标的新闻与官方披露**。A股走东财、美股 SEC+Yahoo、
港股 HKEX+Yahoo、Crypto 走已注册财经 feed;需要历史截止点时传 asOf。响应的
providers/status 与 is_partial 区分“无结果”和“源站故障”,后者不能解释为没有消息。

**Web 搜索**(D-10 新 · 零 key,ddgs 聚合多引擎):
- web.search —— 搜索互联网。query 用自然语言;backend 默认 auto,中文自动走 bing。
Expand All @@ -44,9 +47,10 @@ export const TOOL_CATALOG = `
对 A股用 venue=baostock(支持 1d/1wk/1mo + 分钟级 5m/15m/30m/1h),美股/港股用 venue=yfinance

**市场级行情(D-12+ 新 · 行情归因专用,无需 symbol)**:
- data.get_market_news —— 市场级财经快讯流。用户问"某市场 / 大盘今天有什么消息 /
为什么涨跌"时**优先于 web.search_news**(专业财经快讯源,免搜索引擎噪声)。
不用于单标的新闻深挖(标的级仍走 web.search_news + web.fetch)
- data.get_market_news —— 多市场财经快讯流。用户问“某市场 / 大盘今天有什么消息 /
为什么涨跌”时优先于 web.search_news;支持全球市场分类。A股用东财、Crypto 用
专业 feed,其余股票市场使用代表性指数/ETF 新闻代理,引用时必须带代理口径。
单标的消息或官方披露改用 data.get_news。
- data.get_market_sectors —— 行业板块涨跌幅榜(涨跌两端 + 领涨股)。
判断"普涨还是结构性、哪些板块领涨领跌";归因个股时先看它所属板块在榜单的位置
- data.get_market_moneyflow —— 跨境资金流(A股=沪深港通)。资金面维度。
Expand All @@ -55,8 +59,8 @@ export const TOOL_CATALOG = `
- data.get_market_movers —— 当日强势股 + 人工题材标签。归因"什么主线在涨"的
最直接证据(对 tags 聚类看热点)。坑:标签是媒体归纳**非因果实锤**,
措辞用"市场归因于 / 题材标签显示"
- 四个工具按 market 参数路由(同"全球市场覆盖"分类);当前仅实装 cn(A股)
**未实装的市场不要硬调**(会返 400),降级走 web.search_news + 该市场代表性指数 get_bars
- sectors / moneyflow / movers 当前仅实装 cn(A股);其它市场只可调用 get_market_news,
其余盘面维度降级走 web.search_news + 代表性指数 get_bars

**有效因子择时(接现成因子库 pandas-ta / Alpha101 / qlib)**:
- factor.timing —— 给一个标的/周期,返回**当前最有效的因子**(按时序 Rank IC 排序)+ 读数 + 方向 + 强度。
Expand Down
43 changes: 43 additions & 0 deletions packages/orchestration/src/tools/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,53 @@ export const dataSearchSymbolTool = createTool({
},
});

// ────────────────────────────────────────────────────────────────────
// data.get_news
// ────────────────────────────────────────────────────────────────────

export const dataGetNewsTool = createTool({
id: "data.get_news",
description: `
统一财经新闻与官方披露。按 market 路由:A股东财、美股 SEC+Yahoo、港股
HKEX+Yahoo、Crypto 专业媒体/官方 feed;返回逐来源 status、内容时点和部分失败标志。

何时用:单标的新闻、公司公告、市场消息面,以及需要 asOf 截止时间的研究证据。
何时不用:价格/K线用 data.get_bars;要读全文时对返回 URL 再用 web.fetch。
坑:disclosure 不等于媒体新闻;is_partial=true 表示至少一个专业源故障,不能把
空结果解释成“没有消息”。历史查询会排除无可靠发布时间和晚于 asOf 的条目。
`.trim(),
inputSchema: z.object({
market: z.enum([
"cn", "us", "hk", "jp", "kr", "au", "in", "uk",
"de", "fr", "ca", "br", "global", "crypto",
]),
symbol: SymbolSchema.optional(),
asOf: z.string().datetime({ offset: true }).optional(),
since: z.string().datetime({ offset: true }).optional(),
kinds: z.array(z.enum(["market_news", "media", "disclosure"])).optional(),
language: z.string().min(1).max(35).optional(),
limit: z.number().int().min(1).max(50).default(10),
}),
execute: async (inputData, ctx) => {
const tc = ctx?.requestContext as ToolRequestContext | undefined;
const client = await getClient(tc);
return await client.getNews({
market: inputData.market,
symbol: inputData.symbol,
asOf: inputData.asOf,
since: inputData.since,
kinds: inputData.kinds,
language: inputData.language,
limit: inputData.limit ?? 10,
});
},
});

export const dataTools = [
dataGetBarsTool,
dataBackfillBarsTool,
dataGetTickerTool,
dataGetFundamentalsTool,
dataSearchSymbolTool,
dataGetNewsTool,
] as const;
3 changes: 3 additions & 0 deletions packages/orchestration/src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
dataBackfillBarsTool,
dataGetBarsTool,
dataGetFundamentalsTool,
dataGetNewsTool,
dataGetTickerTool,
dataSearchSymbolTool,
dataTools,
Expand Down Expand Up @@ -115,6 +116,7 @@ export {
dataGetMarketMoversTool,
dataGetMarketNewsTool,
dataGetMarketSectorsTool,
dataGetNewsTool,
dataGetTickerTool,
dataSearchSymbolTool,
divinationCastHexagramTool,
Expand Down Expand Up @@ -244,6 +246,7 @@ export const orchestratorToolList = [
dataBackfillBarsTool,
dataGetTickerTool,
dataGetFundamentalsTool,
dataGetNewsTool,
// 公司名 → ticker 解析(候选池构建,禁训练记忆猜代码)
dataSearchSymbolTool,
// D-10:web 搜索;web.fetch 读原文补证据链最后一公里
Expand Down
32 changes: 20 additions & 12 deletions packages/orchestration/src/tools/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* 市场级行情归因 tool(D-12+)—— services/data 的 /market/* 端点包装。
*
* 行情归因("今天为什么涨/跌")的四个数据维度,全部无需 symbol。
* venue 按 market 参数路由:当前实装 cn(A股,直连东财/同花顺,配方源自
* a-stock-data);未实装的市场后端返 400,不要硬调
* venue 按 market 参数路由:新闻支持全部已声明市场;板块、资金与强势股当前仅支持
* cn(A股,直连东财/同花顺,配方源自 a-stock-data)。
*
* Tool 设计遵循 docs/05-tool-skill-discipline.md:description 四要素。
*/
Expand All @@ -22,8 +22,14 @@ async function getClient(ctx?: ToolRequestContext): Promise<DataClient> {
return new DataClient({ baseUrl: settings.dataServiceUrl, token });
}

const MarketSchema = z.enum(["cn"]).default("cn")
.describe("市场;当前仅实装 cn(A股),其它市场归因用 web.search_news + 指数 get_bars 替代");
const NewsMarketSchema = z.enum([
"cn", "us", "hk", "jp", "kr", "au", "in", "uk",
"de", "fr", "ca", "br", "global", "crypto",
]).default("cn")
.describe("市场;A股走东财,Crypto 走财经 feed,其余股票市场走明确标注的代表指数/ETF 新闻代理");

const CnMarketSchema = z.enum(["cn"]).default("cn")
.describe("市场;当前仅支持 cn(A股)");

// ────────────────────────────────────────────────────────────────────
// data.get_market_news
Expand All @@ -32,8 +38,9 @@ const MarketSchema = z.enum(["cn"]).default("cn")
export const dataGetMarketNewsTool = createTool({
id: "data.get_market_news",
description: `
全市场财经快讯流(A股=东财 7×24 全球资讯),**无需 symbol**。返回标题 / 摘要 /
UTC 时间戳 / 关联代码。
多市场财经快讯流,**无需 symbol**。A股走东财,Crypto 走专业财经 feed;其他
股票市场走代表性指数/ETF 的 Yahoo 新闻代理,条目会保留代理 ticker 与口径声明。
响应含逐 provider status、UTC 时间戳与部分失败标志。

何时用:
- 行情归因:"某市场 / 大盘今天为什么涨跌、有什么消息"——**优先于 web.search_news**
Expand All @@ -47,11 +54,12 @@ export const dataGetMarketNewsTool = createTool({
坑:
- published_at 已转 UTC;引用时按 §3.1 标注数据时点
- 60s 进程内缓存;快讯≠结论,结论级引用先 web.fetch 读原文
- 源站故障返回 error 字段(502 MARKET_DATA_UNAVAILABLE)——此时降级
web.search 并显式说明快讯源不可用
- A股源站故障返回 error 字段(502 MARKET_DATA_UNAVAILABLE);其它市场通过
providers[].status / is_partial 表示部分或全部来源故障。此时降级 web.search_news
并显式说明结构化快讯源不可用
`.trim(),
inputSchema: z.object({
market: MarketSchema,
market: NewsMarketSchema,
limit: z.number().int().min(1).max(50).default(20),
}),
execute: async (inputData, ctx) => {
Expand Down Expand Up @@ -88,7 +96,7 @@ export const dataGetMarketSectorsTool = createTool({
- 60s 缓存;不要循环逐板块调用
`.trim(),
inputSchema: z.object({
market: MarketSchema,
market: CnMarketSchema,
topN: z.number().int().min(1).max(50).default(10),
}),
execute: async (inputData, ctx) => {
Expand Down Expand Up @@ -125,7 +133,7 @@ export const dataGetMarketMoneyflowTool = createTool({
- as_of_time 是北京时间 HH:MM;非交易时段拿的是上一交易日尾盘值
`.trim(),
inputSchema: z.object({
market: MarketSchema,
market: CnMarketSchema,
}),
execute: async (inputData, ctx) => {
const tc = ctx?.requestContext as ToolRequestContext | undefined;
Expand Down Expand Up @@ -158,7 +166,7 @@ export const dataGetMarketMoversTool = createTool({
- 含 ST / 涨停板个股,标签可能滞后;60s 缓存
`.trim(),
inputSchema: z.object({
market: MarketSchema,
market: CnMarketSchema,
limit: z.number().int().min(1).max(50).default(30),
}),
execute: async (inputData, ctx) => {
Expand Down
Loading
Loading