tickflow-assist 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +33 -101
  2. package/dist/analysis/parsers/json-block.parser.d.ts +4 -1
  3. package/dist/analysis/parsers/json-block.parser.js +109 -6
  4. package/dist/analysis/parsers/key-levels.parser.js +2 -11
  5. package/dist/analysis/parsers/watchlist-profile.parser.js +1 -1
  6. package/dist/config/normalize.js +0 -3
  7. package/dist/dev/tickflow-assist-cli.js +5 -48
  8. package/dist/plugin-registration.test.d.ts +1 -0
  9. package/dist/plugin-registration.test.js +93 -0
  10. package/dist/prompts/analysis/common-system-prompt.d.ts +1 -1
  11. package/dist/prompts/analysis/common-system-prompt.js +7 -15
  12. package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +1 -1
  13. package/dist/prompts/analysis/composite-analysis-user-prompt.js +18 -26
  14. package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +1 -1
  15. package/dist/prompts/analysis/financial-analysis-user-prompt.js +38 -6
  16. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +1 -1
  17. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +10 -6
  18. package/dist/prompts/analysis/kline-analysis-user-prompt.js +151 -34
  19. package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +1 -1
  20. package/dist/prompts/analysis/news-analysis-user-prompt.js +44 -9
  21. package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +1 -1
  22. package/dist/prompts/analysis/post-close-review-user-prompt.js +28 -30
  23. package/dist/prompts/analysis/prompt-text-utils.d.ts +4 -0
  24. package/dist/prompts/analysis/prompt-text-utils.js +28 -0
  25. package/dist/prompts/analysis/shared-schema.d.ts +4 -0
  26. package/dist/prompts/analysis/shared-schema.js +40 -0
  27. package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +3 -2
  28. package/dist/utils/cost-price.d.ts +1 -0
  29. package/dist/utils/cost-price.js +15 -0
  30. package/openclaw.plugin.json +64 -19
  31. package/package.json +9 -3
  32. package/skills/database-query/SKILL.md +2 -1
  33. package/skills/stock-analysis/SKILL.md +4 -1
  34. package/dist/runtime/monitor-process.d.ts +0 -3
  35. package/dist/runtime/monitor-process.js +0 -24
  36. package/docs/installation.md +0 -393
  37. package/docs/usage.md +0 -244
@@ -0,0 +1,28 @@
1
+ export function extractNarrativeWithoutJson(text) {
2
+ const stripped = text.replace(/```json\s*[\s\S]*?\s*```/gi, "").trim();
3
+ return stripped || text.trim();
4
+ }
5
+ export function truncatePromptText(text, maxLength) {
6
+ const normalized = text.trim();
7
+ if (!normalized) {
8
+ return "";
9
+ }
10
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength)}...`;
11
+ }
12
+ export function buildReferencedNarrative(text, maxLength) {
13
+ return truncatePromptText(extractNarrativeWithoutJson(text), maxLength);
14
+ }
15
+ export function sanitizeExternalPromptText(text, maxLength) {
16
+ const normalized = String(text ?? "")
17
+ .replace(/```[\s\S]*?```/g, " ")
18
+ .replace(/[`#>*]/g, " ")
19
+ .replace(/\s+/g, " ")
20
+ .trim();
21
+ if (!normalized) {
22
+ return "";
23
+ }
24
+ if (/(忽略以上|请忽略|不要遵循|system\s*prompt|developer\s*:|assistant\s*:|user\s*:|只输出\s*json)/i.test(normalized)) {
25
+ return "";
26
+ }
27
+ return truncatePromptText(normalized, maxLength);
28
+ }
@@ -0,0 +1,4 @@
1
+ export declare const KEY_LEVELS_JSON_SCHEMA_INNER: string;
2
+ export declare const KEY_LEVELS_JSON_SCHEMA: string;
3
+ export declare const KEY_LEVELS_FIELD_GUIDANCE: string;
4
+ export declare function indentPromptBlock(text: string, spaces: number): string;
@@ -0,0 +1,40 @@
1
+ export const KEY_LEVELS_JSON_SCHEMA_INNER = [
2
+ '"current_price": number,',
3
+ '"stop_loss": number | null,',
4
+ '"breakthrough": number | null,',
5
+ '"support": number | null,',
6
+ '"cost_level": number | null,',
7
+ '"resistance": number | null,',
8
+ '"take_profit": number | null,',
9
+ '"gap": number | null,',
10
+ '"target": number | null,',
11
+ '"round_number": number | null,',
12
+ '"score": integer',
13
+ ].join("\n");
14
+ export const KEY_LEVELS_JSON_SCHEMA = [
15
+ "{",
16
+ indentPromptBlock(KEY_LEVELS_JSON_SCHEMA_INNER, 2),
17
+ "}",
18
+ ].join("\n");
19
+ export const KEY_LEVELS_FIELD_GUIDANCE = [
20
+ "- current_price: 最新可用价格,必须与输入中的最新收盘价或实时价一致。",
21
+ "- support: 当前最近支撑位;不存在或当前不适用填 null。",
22
+ "- resistance: 当前最近压力位;不存在或当前不适用填 null。",
23
+ "- breakthrough: 需要放量或收盘确认的向上突破位;不存在或当前不适用填 null。",
24
+ "- stop_loss: 短线止损参考位;不存在或当前不适用填 null。",
25
+ "- take_profit: 短线分批止盈参考位;不存在或当前不适用填 null。",
26
+ "- target: 突破后的第一目标位;不存在或当前不适用填 null。",
27
+ "- round_number: 重要整数关口;不存在或当前不适用填 null。",
28
+ "- gap: 近期未回补的跳空缺口;无明确缺口填 null。",
29
+ "- cost_level: 若提供了用户成本价,必须填写该成本价;未提供则填 null。",
30
+ "- 除 current_price 外,其余价格字段已知则填真实数值,不存在或当前不适用填 null。",
31
+ "- score: 1-10 的整数。",
32
+ "- 最终输出必须是合法 JSON,并用 ```json 代码块包裹,不要输出裸 JSON。",
33
+ ].join("\n");
34
+ export function indentPromptBlock(text, spaces) {
35
+ const prefix = " ".repeat(spaces);
36
+ return text
37
+ .split("\n")
38
+ .map((line) => `${prefix}${line}`)
39
+ .join("\n");
40
+ }
@@ -1,5 +1,6 @@
1
1
  const MAX_PROMPT_DOCUMENTS = 8;
2
2
  const MAX_TRUNK_LENGTH = 600;
3
+ const MAX_THEME_COUNT = 10;
3
4
  export const WATCHLIST_PROFILE_EXTRACTION_SYSTEM_PROMPT = [
4
5
  "你是A股证券资料结构化抽取助手。",
5
6
  "",
@@ -15,11 +16,11 @@ export const WATCHLIST_PROFILE_EXTRACTION_SYSTEM_PROMPT = [
15
16
  ' "confidence": "low" | "medium" | "high"',
16
17
  "}",
17
18
  "4. sector 优先提取申万行业/行业分类,保留完整层级;没有可靠信息时填 null。",
18
- "5. themes 尽量完整列出概念板块,去重后输出数组;优先保留明确的概念/题材/板块名称。",
19
+ `5. themes 尽量完整列出概念板块,去重后输出数组;优先保留明确的概念/题材/板块名称,最多保留 ${MAX_THEME_COUNT} 个。`,
19
20
  "6. themes 中不要输出泛词,例如公司新闻、最新公告、市场快讯;也不要输出等。",
20
21
  "7. 若资料中是组合表达,拆成独立概念更优,例如华为昇腾 / 华为昇思应拆成两个数组项。",
21
22
  "8. 若资料仅出现业务描述而没有足够证据支持概念标签,不要强行扩写。",
22
- "9. confidence 仅反映你对提取结果的把握,不要附加解释。",
23
+ "9. confidence 仅反映你对提取结果的把握,不要附加解释;high 表示多条资料交叉印证,medium 表示有较明确来源但证据有限,low 表示仅能做保守提取。",
23
24
  ].join("\n");
24
25
  export function buildWatchlistProfileExtractionUserPrompt(input) {
25
26
  const documents = input.documents.slice(0, MAX_PROMPT_DOCUMENTS);
@@ -1,3 +1,4 @@
1
1
  export declare function normalizeCostPrice(value: unknown): number | null;
2
2
  export declare function formatCostPrice(value: number | null | undefined, suffix?: string): string;
3
3
  export declare function calculateProfitPct(currentPrice: number, costPrice: number | null | undefined): number | null;
4
+ export declare function formatCostRelationship(currentPrice: number, costPrice: number | null | undefined): string;
@@ -16,3 +16,18 @@ export function calculateProfitPct(currentPrice, costPrice) {
16
16
  }
17
17
  return ((currentPrice - numeric) / numeric) * 100;
18
18
  }
19
+ export function formatCostRelationship(currentPrice, costPrice) {
20
+ const numeric = normalizeCostPrice(costPrice);
21
+ if (numeric == null || !Number.isFinite(currentPrice)) {
22
+ return "未设置";
23
+ }
24
+ const diff = currentPrice - numeric;
25
+ const pct = calculateProfitPct(currentPrice, numeric);
26
+ if (pct == null) {
27
+ return "未设置";
28
+ }
29
+ const direction = diff > 0 ? "高于" : diff < 0 ? "低于" : "持平";
30
+ const diffPrefix = diff > 0 ? "+" : diff < 0 ? "-" : "";
31
+ const pctPrefix = pct > 0 ? "+" : pct < 0 ? "-" : "";
32
+ return `${direction}成本价 ${Math.abs(diff).toFixed(2)} 元(${diffPrefix}${Math.abs(diff).toFixed(2)} 元,${pctPrefix}${Math.abs(pct).toFixed(2)}%)`;
33
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "tickflow-assist",
3
3
  "name": "TickFlow Assist",
4
- "version": "0.2.4",
4
+ "version": "0.2.6",
5
5
  "description": "A-share watchlist analysis, monitoring, and alert delivery powered by TickFlow and OpenClaw.",
6
6
  "skills": [
7
7
  "skills"
@@ -9,54 +9,69 @@
9
9
  "configSchema": {
10
10
  "type": "object",
11
11
  "additionalProperties": false,
12
+ "required": ["tickflowApiKey", "llmApiKey"],
12
13
  "properties": {
13
14
  "tickflowApiUrl": {
14
15
  "type": "string",
15
- "default": "https://api.tickflow.org"
16
+ "default": "https://api.tickflow.org",
17
+ "description": "TickFlow API base URL."
16
18
  },
17
19
  "tickflowApiKey": {
18
- "type": "string"
20
+ "type": "string",
21
+ "minLength": 1,
22
+ "description": "Required. TickFlow API key used for market and financial data."
19
23
  },
20
24
  "tickflowApiKeyLevel": {
21
25
  "type": "string",
22
26
  "enum": ["Free", "Start", "Pro", "Expert"],
23
- "default": "Free"
27
+ "default": "Free",
28
+ "description": "TickFlow subscription level. Pro and Expert enable intraday K-line fetching."
24
29
  },
25
30
  "mxSearchApiUrl": {
26
31
  "type": "string",
27
- "default": "https://mkapi2.dfcfs.com/finskillshub/api/claw"
32
+ "default": "https://mkapi2.dfcfs.com/finskillshub/api/claw",
33
+ "description": "MX Search API base URL."
28
34
  },
29
35
  "mxSearchApiKey": {
30
36
  "type": "string",
31
- "default": ""
37
+ "default": "",
38
+ "description": "Optional. Enables mx_search, mx_select_stock, and non-Expert financial fallback."
32
39
  },
33
40
  "llmBaseUrl": {
34
41
  "type": "string",
35
- "default": "https://api.openai.com/v1"
42
+ "default": "https://api.openai.com/v1",
43
+ "description": "OpenAI-compatible LLM API base URL."
36
44
  },
37
45
  "llmApiKey": {
38
- "type": "string"
46
+ "type": "string",
47
+ "minLength": 1,
48
+ "description": "Required. API key for the analysis model endpoint configured in llmBaseUrl."
39
49
  },
40
50
  "llmModel": {
41
51
  "type": "string",
42
- "default": "gpt-4o"
52
+ "default": "gpt-4o",
53
+ "description": "Model name used for analysis."
43
54
  },
44
55
  "databasePath": {
45
56
  "type": "string",
46
- "default": "./data/lancedb"
57
+ "default": "./data/lancedb",
58
+ "description": "Local LanceDB data directory."
47
59
  },
48
60
  "calendarFile": {
49
61
  "type": "string",
50
- "default": "./day_future.txt"
62
+ "default": "./day_future.txt",
63
+ "description": "Trading calendar file path."
51
64
  },
52
65
  "requestInterval": {
53
66
  "type": "integer",
54
67
  "minimum": 5,
55
- "default": 30
68
+ "default": 30,
69
+ "description": "Realtime monitoring polling interval in seconds."
56
70
  },
57
71
  "dailyUpdateNotify": {
58
72
  "type": "boolean",
59
- "default": true
73
+ "default": true,
74
+ "description": "Whether scheduled daily update and post-close review send notifications."
60
75
  },
61
76
  "alertChannel": {
62
77
  "type": "string",
@@ -73,44 +88,74 @@
73
88
  },
74
89
  "alertTarget": {
75
90
  "type": "string",
76
- "description": "Channel target, e.g. Telegram chat ID, qqbot:c2c:OPENID, or WeCom userId/chatId",
91
+ "description": "Optional. Channel target for test_alert, monitoring alerts, and scheduled notifications.",
77
92
  "default": ""
78
93
  },
79
94
  "pythonBin": {
80
95
  "type": "string",
81
- "default": "uv"
96
+ "default": "uv",
97
+ "description": "Python launcher used for indicator calculation."
82
98
  },
83
99
  "pythonArgs": {
84
100
  "type": "array",
85
101
  "items": {
86
102
  "type": "string"
87
103
  },
88
- "default": ["run", "python"]
104
+ "default": ["run", "python"],
105
+ "description": "Arguments passed to pythonBin before indicator_runner.py."
89
106
  },
90
107
  "pythonWorkdir": {
91
108
  "type": "string",
92
- "default": "./python"
109
+ "default": "./python",
110
+ "description": "Working directory for the Python indicator bridge."
93
111
  }
94
112
  }
95
113
  },
96
114
  "uiHints": {
97
115
  "tickflowApiKey": {
98
116
  "label": "TickFlow API Key",
117
+ "help": "Required for TickFlow market and financial data.",
99
118
  "sensitive": true
100
119
  },
101
120
  "tickflowApiKeyLevel": {
102
- "label": "TickFlow API Key Level"
121
+ "label": "TickFlow API Key Level",
122
+ "help": "Set this to your actual TickFlow subscription level."
103
123
  },
104
124
  "mxSearchApiKey": {
105
125
  "label": "MX Search API Key",
126
+ "help": "Optional. Enables mx_search, mx_select_stock, and lite financial fallback.",
106
127
  "sensitive": true
107
128
  },
129
+ "llmBaseUrl": {
130
+ "label": "LLM Base URL",
131
+ "help": "OpenAI-compatible analysis endpoint."
132
+ },
108
133
  "llmApiKey": {
109
134
  "label": "LLM API Key",
135
+ "help": "Required for analysis model requests.",
110
136
  "sensitive": true
111
137
  },
138
+ "llmModel": {
139
+ "label": "LLM Model",
140
+ "help": "Model name used for stock analysis."
141
+ },
142
+ "alertChannel": {
143
+ "label": "Alert Channel",
144
+ "help": "Default delivery channel for alert messages."
145
+ },
112
146
  "alertTarget": {
113
- "label": "Alert Target"
147
+ "label": "Alert Target",
148
+ "help": "Optional. Configure this if you want test_alert, monitoring alerts, or scheduled notifications."
149
+ },
150
+ "databasePath": {
151
+ "label": "Database Path",
152
+ "help": "Local LanceDB storage directory.",
153
+ "advanced": true
154
+ },
155
+ "calendarFile": {
156
+ "label": "Calendar File",
157
+ "help": "Trading calendar file path.",
158
+ "advanced": true
114
159
  }
115
160
  }
116
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickflow-assist",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "OpenClaw plugin for TickFlow-based A-share analysis, monitoring, and alerting.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "docs",
14
13
  "skills",
15
14
  "python",
16
15
  "day_future.txt",
@@ -25,9 +24,11 @@
25
24
  "lancedb"
26
25
  ],
27
26
  "scripts": {
28
- "build": "tsc -p tsconfig.json",
27
+ "build": "node ./scripts/clean-dist.mjs && tsc -p tsconfig.json",
29
28
  "check": "tsc -p tsconfig.json --noEmit",
30
29
  "dev": "tsc -p tsconfig.json --watch",
30
+ "prepack": "npm run build && node ./scripts/prepare-package-assets.mjs",
31
+ "postpack": "node ./scripts/restore-package-assets.mjs",
31
32
  "test": "npm run build && node --test dist/plugin-registration.test.js",
32
33
  "community-setup": "node dist/dev/tickflow-assist-cli.js configure-openclaw",
33
34
  "tool": "node dist/dev/run-tool.js",
@@ -36,6 +37,11 @@
36
37
  "validate:mx-search": "npm run build && node dist/dev/validate-mx-search.js"
37
38
  },
38
39
  "openclaw": {
40
+ "compat": {
41
+ "pluginApi": "1.2.0",
42
+ "minGatewayVersion": "2026.3.22",
43
+ "builtWithOpenClawVersion": "2026.3.22"
44
+ },
39
45
  "extensions": [
40
46
  "dist/plugin.js"
41
47
  ]
@@ -5,7 +5,8 @@ metadata:
5
5
  openclaw:
6
6
  skillKey: database_query
7
7
  requires:
8
- config: true
8
+ config:
9
+ - plugins.entries.tickflow-assist.enabled
9
10
  ---
10
11
  # LanceDB 数据查询
11
12
 
@@ -5,7 +5,10 @@ metadata:
5
5
  openclaw:
6
6
  skillKey: stock_analysis
7
7
  requires:
8
- config: true
8
+ config:
9
+ - plugins.entries.tickflow-assist.enabled
10
+ - plugins.entries.tickflow-assist.config.tickflowApiKey
11
+ - plugins.entries.tickflow-assist.config.llmApiKey
9
12
  ---
10
13
  # 股票分析与监控
11
14
 
@@ -1,3 +0,0 @@
1
- import type { PluginConfig } from "../config/schema.js";
2
- export declare function spawnMonitorLoop(config: PluginConfig, configSource: "openclaw_plugin" | "local_config"): number | null;
3
- export declare function isPidAlive(pid: number): boolean;
@@ -1,24 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { fileURLToPath } from "node:url";
3
- import { buildProcessConfigEnv } from "./process-config.js";
4
- const PROJECT_ROOT = fileURLToPath(new URL("../../", import.meta.url));
5
- const MONITOR_LOOP_SCRIPT = fileURLToPath(new URL("../dev/run-monitor-loop.js", import.meta.url));
6
- export function spawnMonitorLoop(config, configSource) {
7
- const child = spawn(process.execPath, [MONITOR_LOOP_SCRIPT], {
8
- cwd: PROJECT_ROOT,
9
- detached: true,
10
- stdio: "ignore",
11
- env: buildProcessConfigEnv(config, configSource),
12
- });
13
- child.unref();
14
- return child.pid ?? null;
15
- }
16
- export function isPidAlive(pid) {
17
- try {
18
- process.kill(pid, 0);
19
- return true;
20
- }
21
- catch {
22
- return false;
23
- }
24
- }