Initial commit: A-share stock analysis project with screening, backtesting, and multi-factor analysis tools

This commit is contained in:
2026-07-01 06:39:40 +00:00
commit d80c208004
19 changed files with 1542 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
import { tool } from "@opencode-ai/plugin";
import path from "path";
const SCRIPTS_DIR = (ctx: { worktree: string }) =>
path.join(ctx.worktree, "scripts");
async function runPython(
ctx: { worktree: string },
fn: string,
args: Record<string, unknown>,
): Promise<string> {
const scriptPath = path.join(SCRIPTS_DIR(ctx), "backtest_engine.py");
const argsJson = JSON.stringify(args);
const result = await Bun.$`python3 ${scriptPath} ${fn} ${argsJson}`.text();
return result.trim();
}
export const run = tool({
description: "Run a backtest for a trading strategy on specified stock universe and period",
args: {
strategy_name: tool.schema.string().optional().describe("Name of a predefined strategy, or omit to use custom rules"),
entry_rule: tool.schema.string().optional().describe("Custom entry condition (Python expression using df columns like close, ma20, ma60, volume, etc.)"),
exit_rule: tool.schema.string().optional().describe("Custom exit condition (Python expression) or 'stop_loss:0.05,take_profit:0.15,max_hold:20'"),
universe: tool.schema.enum(["hs300", "zz500", "all", "custom"]).default("hs300").describe("Stock universe to test on"),
symbols: tool.schema.string().optional().describe("Comma-separated stock codes for custom universe"),
start_date: tool.schema.string().default("20210101").describe("Start date YYYYMMDD"),
end_date: tool.schema.string().default("20251231").describe("End date YYYYMMDD"),
},
async execute(args, context) {
return runPython(context, "run", args);
},
});
export const predefined = tool({
description: "List all predefined backtest strategies with descriptions",
args: {},
async execute(args, context) {
return runPython(context, "predefined", args);
},
});
export const compare = tool({
description: "Compare multiple predefined strategies on the same universe and period",
args: {
strategies: tool.schema.string().describe("Comma-separated strategy names to compare"),
universe: tool.schema.enum(["hs300", "zz500", "all"]).default("hs300").describe("Stock universe"),
start_date: tool.schema.string().default("20210101").describe("Start date YYYYMMDD"),
end_date: tool.schema.string().default("20251231").describe("End date YYYYMMDD"),
},
async execute(args, context) {
return runPython(context, "compare", args);
},
});
+83
View File
@@ -0,0 +1,83 @@
import { tool } from "@opencode-ai/plugin";
import path from "path";
const SCRIPTS_DIR = (ctx: { worktree: string }) =>
path.join(ctx.worktree, "scripts");
async function runPython(
ctx: { worktree: string },
script: string,
fn: string,
args: Record<string, unknown>,
): Promise<string> {
const scriptPath = path.join(SCRIPTS_DIR(ctx), script);
const argsJson = JSON.stringify(args);
const result = await Bun.$`python3 ${scriptPath} ${fn} ${argsJson}`.text();
return result.trim();
}
export const quote = tool({
description: "Get daily/weekly K-line and real-time quotes for a stock",
args: {
symbol: tool.schema.string().describe("Stock code (e.g. 600519 for Kweichow Moutai)"),
period: tool.schema.enum(["daily", "weekly", "monthly"]).default("daily").describe("K-line period"),
start_date: tool.schema.string().optional().describe("Start date YYYYMMDD, defaults to 90 days ago"),
end_date: tool.schema.string().optional().describe("End date YYYYMMDD, defaults to today"),
},
async execute(args, context) {
return runPython(context, "market_data.py", "quote", args);
},
});
export const financial = tool({
description: "Get financial statement indicators for a stock",
args: {
symbol: tool.schema.string().describe("Stock code (e.g. 600519)"),
},
async execute(args, context) {
return runPython(context, "market_data.py", "financial", args);
},
});
export const moneyflow = tool({
description: "Get capital flow data (main fund, northbound) for a stock",
args: {
symbol: tool.schema.string().describe("Stock code (e.g. 600519)"),
days: tool.schema.number().default(10).describe("Number of recent days"),
},
async execute(args, context) {
return runPython(context, "market_data.py", "moneyflow", args);
},
});
export const news = tool({
description: "Get recent news and announcements for a stock",
args: {
symbol: tool.schema.string().describe("Stock code (e.g. 600519)"),
limit: tool.schema.number().default(20).describe("Max number of news items"),
},
async execute(args, context) {
return runPython(context, "sentiment.py", "news", args);
},
});
export const index = tool({
description: "Get major A-share index data (Shanghai Composite, Shenzhen Component, ChiNext)",
args: {
index_code: tool.schema.enum(["sh", "sz", "cyb", "all"]).default("all").describe("Index to fetch"),
days: tool.schema.number().default(30).describe("Number of recent trading days"),
},
async execute(args, context) {
return runPython(context, "market_data.py", "index", args);
},
});
export const sector = tool({
description: "Get sector/industry performance and ranking",
args: {
date: tool.schema.string().optional().describe("Date YYYYMMDD, defaults to latest"),
},
async execute(args, context) {
return runPython(context, "market_data.py", "sector", args);
},
});
+61
View File
@@ -0,0 +1,61 @@
import { tool } from "@opencode-ai/plugin";
import path from "path";
const SCRIPTS_DIR = (ctx: { worktree: string }) =>
path.join(ctx.worktree, "scripts");
async function runPython(
ctx: { worktree: string },
fn: string,
args: Record<string, unknown>,
): Promise<string> {
const scriptPath = path.join(SCRIPTS_DIR(ctx), "stock_screener.py");
const argsJson = JSON.stringify(args);
const result = await Bun.$`python3 ${scriptPath} ${fn} ${argsJson}`.text();
return result.trim();
}
export const multi_factor = tool({
description: "Screen stocks using multi-factor scoring model (technical + capital flow + fundamental + sentiment)",
args: {
strategy: tool.schema.enum(["comprehensive", "momentum", "value", "breakout"]).default("comprehensive").describe("Screening strategy"),
sector: tool.schema.string().optional().describe("Filter by sector/industry name"),
market_cap: tool.schema.enum(["large", "medium", "small", "all"]).default("all").describe("Market cap filter"),
top_n: tool.schema.number().default(5).describe("Number of top stocks to return (user has 5万 capital, keep it tight)"),
},
async execute(args, context) {
return runPython(context, "multi_factor", args);
},
});
export const strong = tool({
description: "Screen for strong-trend stocks (MA bull alignment + relative strength)",
args: {
sector: tool.schema.string().optional().describe("Filter by sector/industry name"),
top_n: tool.schema.number().default(5).describe("Number of top stocks to return (user has 5万 capital, keep it tight)"),
},
async execute(args, context) {
return runPython(context, "strong", args);
},
});
export const breakout = tool({
description: "Screen for volume breakout stocks (price breaking resistance with expanding volume)",
args: {
lookback_days: tool.schema.number().default(60).describe("Lookback period for resistance identification"),
top_n: tool.schema.number().default(5).describe("Number of top stocks to return (user has 5万 capital, keep it tight)"),
},
async execute(args, context) {
return runPython(context, "breakout", args);
},
});
export const oversold = tool({
description: "Screen for oversold stocks with potential rebound signals",
args: {
top_n: tool.schema.number().default(5).describe("Number of top stocks to return (user has 5万 capital, keep it tight)"),
},
async execute(args, context) {
return runPython(context, "oversold", args);
},
});