tickflow-assist 0.2.0

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 (246) hide show
  1. package/README.md +151 -0
  2. package/day_future.txt +8797 -0
  3. package/dist/analysis/orchestrators/composite-analysis.orchestrator.d.ts +35 -0
  4. package/dist/analysis/orchestrators/composite-analysis.orchestrator.js +232 -0
  5. package/dist/analysis/parsers/json-block.parser.d.ts +1 -0
  6. package/dist/analysis/parsers/json-block.parser.js +13 -0
  7. package/dist/analysis/parsers/key-levels.parser.d.ts +5 -0
  8. package/dist/analysis/parsers/key-levels.parser.js +61 -0
  9. package/dist/analysis/parsers/post-close-review.parser.d.ts +2 -0
  10. package/dist/analysis/parsers/post-close-review.parser.js +79 -0
  11. package/dist/analysis/parsers/watchlist-profile.parser.d.ts +6 -0
  12. package/dist/analysis/parsers/watchlist-profile.parser.js +93 -0
  13. package/dist/analysis/providers/financial-analysis.provider.d.ts +12 -0
  14. package/dist/analysis/providers/financial-analysis.provider.js +85 -0
  15. package/dist/analysis/providers/market-analysis.provider.d.ts +27 -0
  16. package/dist/analysis/providers/market-analysis.provider.js +187 -0
  17. package/dist/analysis/providers/news-analysis.provider.d.ts +9 -0
  18. package/dist/analysis/providers/news-analysis.provider.js +60 -0
  19. package/dist/analysis/tasks/analysis-step-task.d.ts +11 -0
  20. package/dist/analysis/tasks/analysis-step-task.js +1 -0
  21. package/dist/analysis/tasks/analysis-task.d.ts +13 -0
  22. package/dist/analysis/tasks/analysis-task.js +1 -0
  23. package/dist/analysis/tasks/composite-stock-analysis.task.d.ts +17 -0
  24. package/dist/analysis/tasks/composite-stock-analysis.task.js +47 -0
  25. package/dist/analysis/tasks/financial-fundamental-lite.task.d.ts +10 -0
  26. package/dist/analysis/tasks/financial-fundamental-lite.task.js +21 -0
  27. package/dist/analysis/tasks/financial-fundamental.task.d.ts +12 -0
  28. package/dist/analysis/tasks/financial-fundamental.task.js +64 -0
  29. package/dist/analysis/tasks/kline-technical-signal.task.d.ts +10 -0
  30. package/dist/analysis/tasks/kline-technical-signal.task.js +41 -0
  31. package/dist/analysis/tasks/kline-technical.task.d.ts +32 -0
  32. package/dist/analysis/tasks/kline-technical.task.js +61 -0
  33. package/dist/analysis/tasks/news-catalyst.task.d.ts +11 -0
  34. package/dist/analysis/tasks/news-catalyst.task.js +62 -0
  35. package/dist/analysis/tasks/post-close-review.task.d.ts +12 -0
  36. package/dist/analysis/tasks/post-close-review.task.js +35 -0
  37. package/dist/analysis/types/composite-analysis.d.ts +123 -0
  38. package/dist/analysis/types/composite-analysis.js +1 -0
  39. package/dist/background/daily-update.worker.d.ts +50 -0
  40. package/dist/background/daily-update.worker.js +546 -0
  41. package/dist/background/realtime-monitor.worker.d.ts +8 -0
  42. package/dist/background/realtime-monitor.worker.js +28 -0
  43. package/dist/bootstrap.d.ts +45 -0
  44. package/dist/bootstrap.js +214 -0
  45. package/dist/config/normalize.d.ts +4 -0
  46. package/dist/config/normalize.js +99 -0
  47. package/dist/config/schema.d.ts +23 -0
  48. package/dist/config/schema.js +18 -0
  49. package/dist/config/tickflow-access.d.ts +4 -0
  50. package/dist/config/tickflow-access.js +28 -0
  51. package/dist/constants/market-indexes.d.ts +5 -0
  52. package/dist/constants/market-indexes.js +4 -0
  53. package/dist/dev/run-daily-update-loop.d.ts +1 -0
  54. package/dist/dev/run-daily-update-loop.js +48 -0
  55. package/dist/dev/run-monitor-loop.d.ts +1 -0
  56. package/dist/dev/run-monitor-loop.js +60 -0
  57. package/dist/dev/run-tool.d.ts +1 -0
  58. package/dist/dev/run-tool.js +49 -0
  59. package/dist/dev/tickflow-assist-cli.d.ts +2 -0
  60. package/dist/dev/tickflow-assist-cli.js +525 -0
  61. package/dist/dev/validate-mx-search.d.ts +1 -0
  62. package/dist/dev/validate-mx-search.js +212 -0
  63. package/dist/plugin-commands.d.ts +3 -0
  64. package/dist/plugin-commands.js +229 -0
  65. package/dist/plugin.d.ts +8 -0
  66. package/dist/plugin.js +151 -0
  67. package/dist/prompts/analysis/common-system-prompt.d.ts +1 -0
  68. package/dist/prompts/analysis/common-system-prompt.js +44 -0
  69. package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +11 -0
  70. package/dist/prompts/analysis/composite-analysis-user-prompt.js +102 -0
  71. package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +7 -0
  72. package/dist/prompts/analysis/financial-analysis-user-prompt.js +106 -0
  73. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +7 -0
  74. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +59 -0
  75. package/dist/prompts/analysis/index.d.ts +8 -0
  76. package/dist/prompts/analysis/index.js +8 -0
  77. package/dist/prompts/analysis/kline-analysis-user-prompt.d.ts +13 -0
  78. package/dist/prompts/analysis/kline-analysis-user-prompt.js +215 -0
  79. package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +8 -0
  80. package/dist/prompts/analysis/news-analysis-user-prompt.js +57 -0
  81. package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +3 -0
  82. package/dist/prompts/analysis/post-close-review-user-prompt.js +129 -0
  83. package/dist/prompts/analysis/watchlist-profile-extraction-prompt.d.ts +7 -0
  84. package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +52 -0
  85. package/dist/prompts/analysis-system-prompt.d.ts +1 -0
  86. package/dist/prompts/analysis-system-prompt.js +1 -0
  87. package/dist/prompts/analysis-user-prompt.d.ts +1 -0
  88. package/dist/prompts/analysis-user-prompt.js +1 -0
  89. package/dist/runtime/daily-update-process.d.ts +3 -0
  90. package/dist/runtime/daily-update-process.js +24 -0
  91. package/dist/runtime/monitor-process.d.ts +3 -0
  92. package/dist/runtime/monitor-process.js +24 -0
  93. package/dist/runtime/plugin-api.d.ts +22 -0
  94. package/dist/runtime/plugin-api.js +2 -0
  95. package/dist/runtime/process-config.d.ts +8 -0
  96. package/dist/runtime/process-config.js +35 -0
  97. package/dist/services/alert-service.d.ts +45 -0
  98. package/dist/services/alert-service.js +198 -0
  99. package/dist/services/analysis-service.d.ts +20 -0
  100. package/dist/services/analysis-service.js +73 -0
  101. package/dist/services/analysis-view-service.d.ts +23 -0
  102. package/dist/services/analysis-view-service.js +343 -0
  103. package/dist/services/financial-lite-service.d.ts +23 -0
  104. package/dist/services/financial-lite-service.js +201 -0
  105. package/dist/services/financial-service.d.ts +20 -0
  106. package/dist/services/financial-service.js +47 -0
  107. package/dist/services/indicator-service.d.ts +9 -0
  108. package/dist/services/indicator-service.js +67 -0
  109. package/dist/services/instrument-service.d.ts +6 -0
  110. package/dist/services/instrument-service.js +21 -0
  111. package/dist/services/key-level-service.d.ts +2 -0
  112. package/dist/services/key-level-service.js +2 -0
  113. package/dist/services/key-levels-backtest-service.d.ts +71 -0
  114. package/dist/services/key-levels-backtest-service.js +427 -0
  115. package/dist/services/kline-service.d.ts +19 -0
  116. package/dist/services/kline-service.js +91 -0
  117. package/dist/services/monitor-service.d.ts +44 -0
  118. package/dist/services/monitor-service.js +598 -0
  119. package/dist/services/mx-search-service.d.ts +22 -0
  120. package/dist/services/mx-search-service.js +286 -0
  121. package/dist/services/post-close-review-service.d.ts +31 -0
  122. package/dist/services/post-close-review-service.js +402 -0
  123. package/dist/services/quote-service.d.ts +7 -0
  124. package/dist/services/quote-service.js +9 -0
  125. package/dist/services/review-memory-service.d.ts +7 -0
  126. package/dist/services/review-memory-service.js +76 -0
  127. package/dist/services/tickflow-client.d.ts +43 -0
  128. package/dist/services/tickflow-client.js +126 -0
  129. package/dist/services/trading-calendar-service.d.ts +20 -0
  130. package/dist/services/trading-calendar-service.js +102 -0
  131. package/dist/services/update-service.d.ts +21 -0
  132. package/dist/services/update-service.js +130 -0
  133. package/dist/services/watchlist-profile-service.d.ts +20 -0
  134. package/dist/services/watchlist-profile-service.js +76 -0
  135. package/dist/services/watchlist-service.d.ts +43 -0
  136. package/dist/services/watchlist-service.js +204 -0
  137. package/dist/storage/db.d.ts +23 -0
  138. package/dist/storage/db.js +70 -0
  139. package/dist/storage/repositories/alert-log-repo.d.ts +15 -0
  140. package/dist/storage/repositories/alert-log-repo.js +54 -0
  141. package/dist/storage/repositories/analysis-log-repo.d.ts +8 -0
  142. package/dist/storage/repositories/analysis-log-repo.js +48 -0
  143. package/dist/storage/repositories/composite-analysis-repo.d.ts +9 -0
  144. package/dist/storage/repositories/composite-analysis-repo.js +116 -0
  145. package/dist/storage/repositories/financial-analysis-repo.d.ts +9 -0
  146. package/dist/storage/repositories/financial-analysis-repo.js +107 -0
  147. package/dist/storage/repositories/indicators-repo.d.ts +8 -0
  148. package/dist/storage/repositories/indicators-repo.js +98 -0
  149. package/dist/storage/repositories/intraday-klines-repo.d.ts +9 -0
  150. package/dist/storage/repositories/intraday-klines-repo.js +102 -0
  151. package/dist/storage/repositories/key-levels-history-repo.d.ts +9 -0
  152. package/dist/storage/repositories/key-levels-history-repo.js +91 -0
  153. package/dist/storage/repositories/key-levels-repo.d.ts +9 -0
  154. package/dist/storage/repositories/key-levels-repo.js +83 -0
  155. package/dist/storage/repositories/klines-repo.d.ts +8 -0
  156. package/dist/storage/repositories/klines-repo.js +60 -0
  157. package/dist/storage/repositories/news-analysis-repo.d.ts +9 -0
  158. package/dist/storage/repositories/news-analysis-repo.js +107 -0
  159. package/dist/storage/repositories/technical-analysis-repo.d.ts +9 -0
  160. package/dist/storage/repositories/technical-analysis-repo.js +80 -0
  161. package/dist/storage/repositories/watchlist-repo.d.ts +10 -0
  162. package/dist/storage/repositories/watchlist-repo.js +124 -0
  163. package/dist/storage/schemas.d.ts +13 -0
  164. package/dist/storage/schemas.js +177 -0
  165. package/dist/tools/add-stock.tool.d.ts +13 -0
  166. package/dist/tools/add-stock.tool.js +123 -0
  167. package/dist/tools/analyze.tool.d.ts +8 -0
  168. package/dist/tools/analyze.tool.js +24 -0
  169. package/dist/tools/backtest-key-levels.tool.d.ts +8 -0
  170. package/dist/tools/backtest-key-levels.tool.js +43 -0
  171. package/dist/tools/daily-update-status.tool.d.ts +6 -0
  172. package/dist/tools/daily-update-status.tool.js +9 -0
  173. package/dist/tools/fetch-financials.tool.d.ts +8 -0
  174. package/dist/tools/fetch-financials.tool.js +224 -0
  175. package/dist/tools/fetch-intraday-klines.tool.d.ts +11 -0
  176. package/dist/tools/fetch-intraday-klines.tool.js +58 -0
  177. package/dist/tools/fetch-klines.tool.d.ts +11 -0
  178. package/dist/tools/fetch-klines.tool.js +61 -0
  179. package/dist/tools/list-watchlist.tool.d.ts +6 -0
  180. package/dist/tools/list-watchlist.tool.js +22 -0
  181. package/dist/tools/monitor-status.tool.d.ts +6 -0
  182. package/dist/tools/monitor-status.tool.js +9 -0
  183. package/dist/tools/mx-search.tool.d.ts +8 -0
  184. package/dist/tools/mx-search.tool.js +77 -0
  185. package/dist/tools/mx-select-stock.tool.d.ts +8 -0
  186. package/dist/tools/mx-select-stock.tool.js +118 -0
  187. package/dist/tools/query-database.tool.d.ts +8 -0
  188. package/dist/tools/query-database.tool.js +283 -0
  189. package/dist/tools/refresh-watchlist-names.tool.d.ts +7 -0
  190. package/dist/tools/refresh-watchlist-names.tool.js +17 -0
  191. package/dist/tools/refresh-watchlist-profiles.tool.d.ts +9 -0
  192. package/dist/tools/refresh-watchlist-profiles.tool.js +67 -0
  193. package/dist/tools/remove-stock.tool.d.ts +9 -0
  194. package/dist/tools/remove-stock.tool.js +27 -0
  195. package/dist/tools/start-daily-update.tool.d.ts +10 -0
  196. package/dist/tools/start-daily-update.tool.js +23 -0
  197. package/dist/tools/start-monitor.tool.d.ts +9 -0
  198. package/dist/tools/start-monitor.tool.js +52 -0
  199. package/dist/tools/stop-daily-update.tool.d.ts +9 -0
  200. package/dist/tools/stop-daily-update.tool.js +20 -0
  201. package/dist/tools/stop-monitor.tool.d.ts +9 -0
  202. package/dist/tools/stop-monitor.tool.js +44 -0
  203. package/dist/tools/test-alert.tool.d.ts +7 -0
  204. package/dist/tools/test-alert.tool.js +21 -0
  205. package/dist/tools/update-all.tool.d.ts +9 -0
  206. package/dist/tools/update-all.tool.js +17 -0
  207. package/dist/tools/view-analysis.tool.d.ts +8 -0
  208. package/dist/tools/view-analysis.tool.js +95 -0
  209. package/dist/types/daily-update.d.ts +26 -0
  210. package/dist/types/daily-update.js +1 -0
  211. package/dist/types/domain.d.ts +140 -0
  212. package/dist/types/domain.js +1 -0
  213. package/dist/types/indicator.d.ts +43 -0
  214. package/dist/types/indicator.js +1 -0
  215. package/dist/types/monitor.d.ts +17 -0
  216. package/dist/types/monitor.js +1 -0
  217. package/dist/types/mx-search.d.ts +14 -0
  218. package/dist/types/mx-search.js +1 -0
  219. package/dist/types/mx-select-stock.d.ts +28 -0
  220. package/dist/types/mx-select-stock.js +1 -0
  221. package/dist/types/tickflow.d.ts +133 -0
  222. package/dist/types/tickflow.js +1 -0
  223. package/dist/utils/abortable-sleep.d.ts +1 -0
  224. package/dist/utils/abortable-sleep.js +20 -0
  225. package/dist/utils/china-time.d.ts +3 -0
  226. package/dist/utils/china-time.js +18 -0
  227. package/dist/utils/cost-price.d.ts +3 -0
  228. package/dist/utils/cost-price.js +18 -0
  229. package/dist/utils/format.d.ts +1 -0
  230. package/dist/utils/format.js +3 -0
  231. package/dist/utils/process.d.ts +5 -0
  232. package/dist/utils/process.js +1 -0
  233. package/dist/utils/symbol.d.ts +1 -0
  234. package/dist/utils/symbol.js +13 -0
  235. package/docs/installation.md +391 -0
  236. package/docs/usage.md +244 -0
  237. package/openclaw.plugin.json +116 -0
  238. package/package.json +57 -0
  239. package/python/indicator_runner.py +31 -0
  240. package/python/indicators.py +148 -0
  241. package/python/pyproject.toml +9 -0
  242. package/python/requirements.txt +3 -0
  243. package/python/uv.lock +366 -0
  244. package/skills/database-query/SKILL.md +58 -0
  245. package/skills/stock-analysis/SKILL.md +106 -0
  246. package/skills/usage-help/SKILL.md +102 -0
@@ -0,0 +1,402 @@
1
+ import { formatChinaDateTime } from "../utils/china-time.js";
2
+ const LEVEL_BUFFER = 0.005;
3
+ const INTRADAY_PERIOD = "1m";
4
+ export class PostCloseReviewService {
5
+ watchlistService;
6
+ compositeAnalysisOrchestrator;
7
+ analysisService;
8
+ postCloseReviewTask;
9
+ keyLevelsRepository;
10
+ keyLevelsHistoryRepository;
11
+ klinesRepository;
12
+ intradayKlinesRepository;
13
+ constructor(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository) {
14
+ this.watchlistService = watchlistService;
15
+ this.compositeAnalysisOrchestrator = compositeAnalysisOrchestrator;
16
+ this.analysisService = analysisService;
17
+ this.postCloseReviewTask = postCloseReviewTask;
18
+ this.keyLevelsRepository = keyLevelsRepository;
19
+ this.keyLevelsHistoryRepository = keyLevelsHistoryRepository;
20
+ this.klinesRepository = klinesRepository;
21
+ this.intradayKlinesRepository = intradayKlinesRepository;
22
+ }
23
+ async run() {
24
+ const watchlist = await this.watchlistService.list();
25
+ if (watchlist.length === 0) {
26
+ const overviewMessage = "🧭 收盘复盘总览\n\n关注列表为空,已跳过收盘复盘。";
27
+ return {
28
+ overviewMessage,
29
+ detailMessages: [],
30
+ combinedText: overviewMessage,
31
+ };
32
+ }
33
+ const entries = [];
34
+ const detailMessages = [];
35
+ let marketOverview = null;
36
+ for (const item of watchlist) {
37
+ let compositeResult = null;
38
+ try {
39
+ const input = await this.compositeAnalysisOrchestrator.buildInput(item.symbol);
40
+ marketOverview ??= input.market.marketOverview;
41
+ const tradeDate = input.market.klines[input.market.klines.length - 1]?.trade_date ?? formatChinaDateTime().slice(0, 10);
42
+ const validation = await this.buildValidationContext(item.symbol, tradeDate);
43
+ compositeResult = await this.compositeAnalysisOrchestrator.analyzeInput(input);
44
+ const review = await this.analysisService.runTask(this.postCloseReviewTask, {
45
+ ...input,
46
+ compositeResult,
47
+ validation,
48
+ });
49
+ const message = this.formatDetailMessage(item, validation, review);
50
+ await this.persistReview(item.symbol, message, review);
51
+ entries.push({
52
+ ok: true,
53
+ item,
54
+ validation,
55
+ review,
56
+ });
57
+ detailMessages.push(message);
58
+ }
59
+ catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ if (compositeResult?.levels) {
62
+ await this.persistFallbackCompositeReview(item.symbol, compositeResult);
63
+ }
64
+ entries.push({ ok: false, item, errorMessage: message });
65
+ detailMessages.push(this.formatFailureMessage(item, message, compositeResult));
66
+ }
67
+ }
68
+ const overviewMessage = this.formatOverviewMessage(marketOverview, entries);
69
+ return {
70
+ overviewMessage,
71
+ detailMessages,
72
+ combinedText: [overviewMessage, ...detailMessages].join("\n\n"),
73
+ };
74
+ }
75
+ async persistReview(symbol, message, review) {
76
+ if (review.decision === "invalidate" || !review.levels) {
77
+ await this.keyLevelsRepository.remove(symbol);
78
+ return;
79
+ }
80
+ const levels = {
81
+ ...review.levels,
82
+ analysis_text: message,
83
+ };
84
+ await this.keyLevelsRepository.save(symbol, levels);
85
+ await this.keyLevelsHistoryRepository.saveDailySnapshot(toHistoryEntry(symbol, message, levels));
86
+ }
87
+ async persistFallbackCompositeReview(symbol, result) {
88
+ if (!result.levels) {
89
+ return;
90
+ }
91
+ await this.keyLevelsHistoryRepository.saveDailySnapshot(toHistoryEntry(symbol, result.analysisText, result.levels));
92
+ }
93
+ async buildValidationContext(symbol, tradeDate) {
94
+ const snapshots = await this.keyLevelsHistoryRepository.listBySymbol(symbol);
95
+ const snapshot = snapshots.find((item) => item.analysis_date < tradeDate) ?? null;
96
+ if (!snapshot) {
97
+ return {
98
+ available: false,
99
+ snapshotDate: null,
100
+ evaluatedTradeDate: tradeDate,
101
+ verdict: "unavailable",
102
+ snapshot: null,
103
+ summary: "昨日无可验证的活动关键位快照,本轮只能基于今日数据直接重算。",
104
+ lines: ["暂无昨日活动关键位快照。"],
105
+ };
106
+ }
107
+ const dailyRows = await this.klinesRepository.listBySymbol(symbol);
108
+ const row = dailyRows.find((item) => item.trade_date === tradeDate)
109
+ ?? dailyRows.find((item) => item.trade_date > snapshot.analysis_date)
110
+ ?? null;
111
+ if (!row) {
112
+ return {
113
+ available: false,
114
+ snapshotDate: snapshot.analysis_date,
115
+ evaluatedTradeDate: null,
116
+ verdict: "unavailable",
117
+ snapshot,
118
+ summary: `已找到 ${snapshot.analysis_date} 的关键位快照,但尚无后续交易日数据可供验证。`,
119
+ lines: ["缺少后续交易日数据。"],
120
+ };
121
+ }
122
+ const intradayRows = (await this.intradayKlinesRepository.listBySymbol(symbol, INTRADAY_PERIOD))
123
+ .filter((item) => item.trade_date === row.trade_date);
124
+ const support = evaluateSupport(snapshot, row);
125
+ const resistance = evaluateResistance(snapshot, row);
126
+ const stopLoss = evaluateStopLoss(snapshot, row);
127
+ const takeProfit = evaluateTakeProfit(snapshot, row);
128
+ const breakthrough = evaluateBreakthrough(snapshot, row);
129
+ const path = evaluatePath(snapshot, row, intradayRows);
130
+ const verdict = deriveValidationVerdict({
131
+ support,
132
+ stopLoss,
133
+ takeProfit,
134
+ breakthrough,
135
+ path,
136
+ });
137
+ const lines = [
138
+ `快照日期 ${snapshot.analysis_date},验证交易日 ${row.trade_date}。`,
139
+ `当日K线: 高 ${row.high.toFixed(2)} | 低 ${row.low.toFixed(2)} | 收 ${row.close.toFixed(2)}`,
140
+ support,
141
+ resistance,
142
+ stopLoss,
143
+ takeProfit,
144
+ breakthrough,
145
+ path,
146
+ ];
147
+ return {
148
+ available: true,
149
+ snapshotDate: snapshot.analysis_date,
150
+ evaluatedTradeDate: row.trade_date,
151
+ verdict,
152
+ snapshot,
153
+ summary: `昨日关键位${formatValidationVerdictLabel(verdict)}。`,
154
+ lines,
155
+ };
156
+ }
157
+ formatOverviewMessage(marketOverview, entries) {
158
+ const successEntries = entries.filter((entry) => entry.ok);
159
+ const failureCount = entries.length - successEntries.length;
160
+ const validationCounts = countBy(successEntries.map((entry) => entry.validation.verdict));
161
+ const decisionCounts = countBy(successEntries.map((entry) => entry.review.decision));
162
+ const marketBiasCounts = countBy(successEntries.map((entry) => entry.review.marketBias));
163
+ const sectorBiasCounts = countBy(successEntries.map((entry) => entry.review.sectorBias));
164
+ const newsImpactCounts = countBy(successEntries.map((entry) => entry.review.newsImpact));
165
+ const lines = [
166
+ marketOverview?.summary ?? "未获取到大盘总览,本轮仅输出个股复盘。",
167
+ "",
168
+ `复盘数量: ${entries.length} 只 | 成功 ${successEntries.length} | 失败 ${failureCount}`,
169
+ `关键位验证: 有效 ${validationCounts.validated ?? 0} | 混合 ${validationCounts.mixed ?? 0} | 失效 ${validationCounts.invalidated ?? 0} | 缺样本 ${validationCounts.unavailable ?? 0}`,
170
+ `明日处理: 沿用 ${decisionCounts.keep ?? 0} | 微调 ${decisionCounts.adjust ?? 0} | 重算 ${decisionCounts.recompute ?? 0} | 暂停 ${decisionCounts.invalidate ?? 0}`,
171
+ `大盘风向: 顺风 ${marketBiasCounts.tailwind ?? 0} | 中性 ${marketBiasCounts.neutral ?? 0} | 逆风 ${marketBiasCounts.headwind ?? 0}`,
172
+ `板块风向: 顺风 ${sectorBiasCounts.tailwind ?? 0} | 中性 ${sectorBiasCounts.neutral ?? 0} | 逆风 ${sectorBiasCounts.headwind ?? 0}`,
173
+ `新闻影响: 支持 ${newsImpactCounts.supportive ?? 0} | 中性 ${newsImpactCounts.neutral ?? 0} | 扰动 ${newsImpactCounts.disruptive ?? 0}`,
174
+ ];
175
+ return `🧭 收盘复盘总览\n\n${lines.join("\n")}`.trim();
176
+ }
177
+ formatDetailMessage(item, validation, review) {
178
+ const lines = [
179
+ `📘 收盘复盘 | ${item.name}(${item.symbol})`,
180
+ "",
181
+ "昨日关键位验证",
182
+ `• ${validation.summary}`,
183
+ ...validation.lines.map((line) => `• ${line}`),
184
+ "",
185
+ "今日盘面",
186
+ review.sessionSummary || "未生成盘面一句话总结。",
187
+ "",
188
+ "大盘与板块",
189
+ review.marketSectorSummary || "未生成大盘/板块总结。",
190
+ `风向判断: 大盘${formatMarketBiasLabel(review.marketBias)} | 板块${formatMarketBiasLabel(review.sectorBias)}`,
191
+ "",
192
+ "新闻与公告",
193
+ review.newsSummary || "未生成新闻影响总结。",
194
+ `新闻影响: ${formatNewsImpactLabel(review.newsImpact)}`,
195
+ "",
196
+ "明日关键位处理",
197
+ `结论: ${formatDecisionLabel(review.decision)}`,
198
+ review.decisionReason || "未生成处理理由。",
199
+ "",
200
+ "更新后关键位",
201
+ ];
202
+ if (review.decision === "invalidate" || !review.levels) {
203
+ lines.push("• 已暂停沿用昨日关键位,等待下一轮重算。", "", "操作建议", review.actionAdvice || "明日先观察,等待新的关键位再执行。");
204
+ return lines.join("\n");
205
+ }
206
+ lines.push(`• 支撑 ${formatMaybePrice(review.levels.support)} | 压力 ${formatMaybePrice(review.levels.resistance)} | 突破 ${formatMaybePrice(review.levels.breakthrough)}`, `• 止损 ${formatMaybePrice(review.levels.stop_loss)} | 止盈 ${formatMaybePrice(review.levels.take_profit)} | 评分 ${review.levels.score}/10`, "", "操作建议", review.actionAdvice || "按关键位和次日量价配合再决定是否执行。");
207
+ return lines.join("\n");
208
+ }
209
+ formatFailureMessage(item, errorMessage, compositeResult) {
210
+ const fallback = compositeResult?.levels
211
+ ? "已保留综合分析生成的关键位,可稍后用 view_analysis 或 analyze 复核。"
212
+ : "本轮未生成可用关键位。";
213
+ return [
214
+ `⚠️ 收盘复盘 | ${item.name}(${item.symbol})`,
215
+ "",
216
+ `失败原因: ${errorMessage}`,
217
+ fallback,
218
+ ].join("\n");
219
+ }
220
+ }
221
+ function toHistoryEntry(symbol, analysisText, levels) {
222
+ return {
223
+ symbol,
224
+ analysis_date: levels.analysis_date ?? formatChinaDateTime().slice(0, 10),
225
+ activated_at: formatChinaDateTime(),
226
+ profile: "composite",
227
+ current_price: levels.current_price,
228
+ stop_loss: levels.stop_loss ?? null,
229
+ breakthrough: levels.breakthrough ?? null,
230
+ support: levels.support ?? null,
231
+ cost_level: levels.cost_level ?? null,
232
+ resistance: levels.resistance ?? null,
233
+ take_profit: levels.take_profit ?? null,
234
+ gap: levels.gap ?? null,
235
+ target: levels.target ?? null,
236
+ round_number: levels.round_number ?? null,
237
+ analysis_text: analysisText,
238
+ score: levels.score,
239
+ };
240
+ }
241
+ function evaluateSupport(snapshot, row) {
242
+ if (!(snapshot.support != null && snapshot.support > 0)) {
243
+ return "支撑: 昨日未设置支撑位。";
244
+ }
245
+ const touchUpper = snapshot.support * (1 + LEVEL_BUFFER);
246
+ const holdLower = snapshot.support * (1 - LEVEL_BUFFER);
247
+ if (row.low > touchUpper) {
248
+ return `支撑 ${snapshot.support.toFixed(2)}: 当日未触达。`;
249
+ }
250
+ if (row.close < holdLower) {
251
+ return `支撑 ${snapshot.support.toFixed(2)}: 盘中触达后收盘失守,验证失败。`;
252
+ }
253
+ return `支撑 ${snapshot.support.toFixed(2)}: 盘中触达后收盘仍守住,验证有效。`;
254
+ }
255
+ function evaluateResistance(snapshot, row) {
256
+ if (!(snapshot.resistance != null && snapshot.resistance > 0)) {
257
+ return "压力: 昨日未设置压力位。";
258
+ }
259
+ const touchLower = snapshot.resistance * (1 - LEVEL_BUFFER);
260
+ const holdUpper = snapshot.resistance * (1 + LEVEL_BUFFER);
261
+ if (row.high < touchLower) {
262
+ return `压力 ${snapshot.resistance.toFixed(2)}: 当日未触达。`;
263
+ }
264
+ if (row.close > holdUpper) {
265
+ return `压力 ${snapshot.resistance.toFixed(2)}: 当日已被有效站上,原压力失效。`;
266
+ }
267
+ return `压力 ${snapshot.resistance.toFixed(2)}: 盘中触达但未有效站上,压制仍在。`;
268
+ }
269
+ function evaluateStopLoss(snapshot, row) {
270
+ if (!(snapshot.stop_loss != null && snapshot.stop_loss > 0)) {
271
+ return "止损: 昨日未设置止损位。";
272
+ }
273
+ if (row.low <= snapshot.stop_loss) {
274
+ return `止损 ${snapshot.stop_loss.toFixed(2)}: 已触发。`;
275
+ }
276
+ return `止损 ${snapshot.stop_loss.toFixed(2)}: 未触发。`;
277
+ }
278
+ function evaluateTakeProfit(snapshot, row) {
279
+ if (!(snapshot.take_profit != null && snapshot.take_profit > 0)) {
280
+ return "止盈: 昨日未设置止盈位。";
281
+ }
282
+ if (row.high >= snapshot.take_profit) {
283
+ return `止盈 ${snapshot.take_profit.toFixed(2)}: 已触发。`;
284
+ }
285
+ return `止盈 ${snapshot.take_profit.toFixed(2)}: 未触发。`;
286
+ }
287
+ function evaluateBreakthrough(snapshot, row) {
288
+ if (!(snapshot.breakthrough != null && snapshot.breakthrough > 0)) {
289
+ return "突破: 昨日未设置突破位。";
290
+ }
291
+ if (row.high < snapshot.breakthrough) {
292
+ return `突破 ${snapshot.breakthrough.toFixed(2)}: 未触发。`;
293
+ }
294
+ if (row.close >= snapshot.breakthrough * (1 + LEVEL_BUFFER)) {
295
+ return `突破 ${snapshot.breakthrough.toFixed(2)}: 已触发且收盘确认。`;
296
+ }
297
+ return `突破 ${snapshot.breakthrough.toFixed(2)}: 盘中试探但收盘未确认。`;
298
+ }
299
+ function evaluatePath(snapshot, row, intradayRows) {
300
+ if (!(snapshot.stop_loss != null && snapshot.stop_loss > 0 && snapshot.take_profit != null && snapshot.take_profit > 0)) {
301
+ return "路径: 缺少双目标,无法判断先止损还是先止盈。";
302
+ }
303
+ const hitsStop = row.low <= snapshot.stop_loss;
304
+ const hitsTakeProfit = row.high >= snapshot.take_profit;
305
+ if (!hitsStop && !hitsTakeProfit) {
306
+ return "路径: 当日未触发双目标。";
307
+ }
308
+ if (hitsStop && !hitsTakeProfit) {
309
+ return `路径: 当日先到止损 ${snapshot.stop_loss.toFixed(2)}。`;
310
+ }
311
+ if (!hitsStop && hitsTakeProfit) {
312
+ return `路径: 当日先到止盈 ${snapshot.take_profit.toFixed(2)}。`;
313
+ }
314
+ for (const intradayRow of intradayRows) {
315
+ const intradayHitsStop = intradayRow.low <= snapshot.stop_loss;
316
+ const intradayHitsTakeProfit = intradayRow.high >= snapshot.take_profit;
317
+ if (!intradayHitsStop && !intradayHitsTakeProfit) {
318
+ continue;
319
+ }
320
+ if (intradayHitsStop && !intradayHitsTakeProfit) {
321
+ return `路径: 同日双触发中,分钟线判定先到止损 ${snapshot.stop_loss.toFixed(2)}。`;
322
+ }
323
+ if (!intradayHitsStop && intradayHitsTakeProfit) {
324
+ return `路径: 同日双触发中,分钟线判定先到止盈 ${snapshot.take_profit.toFixed(2)}。`;
325
+ }
326
+ if (intradayRow.open <= snapshot.stop_loss) {
327
+ return `路径: 同日双触发中,分钟线按开盘位置判定先到止损 ${snapshot.stop_loss.toFixed(2)}。`;
328
+ }
329
+ if (intradayRow.open >= snapshot.take_profit) {
330
+ return `路径: 同日双触发中,分钟线按开盘位置判定先到止盈 ${snapshot.take_profit.toFixed(2)}。`;
331
+ }
332
+ return "路径: 同日双触发,但分钟线仍无法明确先后。";
333
+ }
334
+ return "路径: 同日双触发,但缺少有效分钟线判定先后。";
335
+ }
336
+ function deriveValidationVerdict(input) {
337
+ if (input.support.includes("验证失败")
338
+ || input.stopLoss.includes("已触发")
339
+ || input.path.includes("先到止损")) {
340
+ return "invalidated";
341
+ }
342
+ if (input.takeProfit.includes("已触发")
343
+ || input.breakthrough.includes("收盘确认")
344
+ || input.support.includes("验证有效")
345
+ || input.path.includes("先到止盈")) {
346
+ return "validated";
347
+ }
348
+ return "mixed";
349
+ }
350
+ function countBy(values) {
351
+ return values.reduce((acc, value) => {
352
+ acc[value] = (acc[value] ?? 0) + 1;
353
+ return acc;
354
+ }, {});
355
+ }
356
+ function formatDecisionLabel(value) {
357
+ switch (value) {
358
+ case "keep":
359
+ return "沿用";
360
+ case "adjust":
361
+ return "微调";
362
+ case "invalidate":
363
+ return "暂停沿用";
364
+ default:
365
+ return "重算";
366
+ }
367
+ }
368
+ function formatMarketBiasLabel(value) {
369
+ switch (value) {
370
+ case "tailwind":
371
+ return "顺风";
372
+ case "headwind":
373
+ return "逆风";
374
+ default:
375
+ return "中性";
376
+ }
377
+ }
378
+ function formatNewsImpactLabel(value) {
379
+ switch (value) {
380
+ case "supportive":
381
+ return "支持";
382
+ case "disruptive":
383
+ return "扰动";
384
+ default:
385
+ return "中性";
386
+ }
387
+ }
388
+ function formatValidationVerdictLabel(value) {
389
+ switch (value) {
390
+ case "validated":
391
+ return "验证有效";
392
+ case "invalidated":
393
+ return "明显失效";
394
+ case "mixed":
395
+ return "效果偏混合";
396
+ default:
397
+ return "暂无可验证样本";
398
+ }
399
+ }
400
+ function formatMaybePrice(value) {
401
+ return value == null ? "-" : value.toFixed(2);
402
+ }
@@ -0,0 +1,7 @@
1
+ import type { TickFlowQuote } from "../types/tickflow.js";
2
+ import { TickFlowClient } from "./tickflow-client.js";
3
+ export declare class QuoteService {
4
+ private readonly client;
5
+ constructor(client: TickFlowClient);
6
+ fetchQuotes(symbols: string[]): Promise<TickFlowQuote[]>;
7
+ }
@@ -0,0 +1,9 @@
1
+ export class QuoteService {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async fetchQuotes(symbols) {
7
+ return this.client.fetchQuotes(symbols);
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReviewMemoryContext } from "../analysis/types/composite-analysis.js";
2
+ import { KeyLevelsBacktestService } from "./key-levels-backtest-service.js";
3
+ export declare class ReviewMemoryService {
4
+ private readonly keyLevelsBacktestService;
5
+ constructor(keyLevelsBacktestService: KeyLevelsBacktestService);
6
+ getSymbolContext(symbol: string): Promise<ReviewMemoryContext>;
7
+ }
@@ -0,0 +1,76 @@
1
+ const RECENT_SNAPSHOTS_LIMIT = 2;
2
+ export class ReviewMemoryService {
3
+ keyLevelsBacktestService;
4
+ constructor(keyLevelsBacktestService) {
5
+ this.keyLevelsBacktestService = keyLevelsBacktestService;
6
+ }
7
+ async getSymbolContext(symbol) {
8
+ const report = await this.keyLevelsBacktestService.buildReport({
9
+ symbol,
10
+ recentLimit: RECENT_SNAPSHOTS_LIMIT,
11
+ });
12
+ const asOf = report.recentSnapshots[0]?.analysis_date ?? null;
13
+ if (report.snapshots.length === 0) {
14
+ return {
15
+ available: false,
16
+ summary: "",
17
+ asOf,
18
+ };
19
+ }
20
+ const primary = [...report.horizons].reverse().find((stats) => countSamples(stats) > 0);
21
+ const lines = [
22
+ "历史关键位复盘经验仅用于校准当前判断;若与最新K线、实时价或资讯催化冲突,以当前证据为主。",
23
+ ];
24
+ if (primary) {
25
+ lines.push(`近${primary.horizon}日窗口:支撑守住 ${formatRate(primary.support.validCount, primary.support.touchCount)},压力压制 ${formatRate(primary.resistance.validCount, primary.resistance.touchCount)},止损触发 ${formatRate(primary.stopLoss.hitCount, primary.stopLoss.sampleCount)},止盈触发 ${formatRate(primary.takeProfit.hitCount, primary.takeProfit.sampleCount)},突破确认 ${formatRate(primary.breakthrough.confirmCount, primary.breakthrough.hitCount)}。`);
26
+ if (primary.tradePath.sampleCount > 0) {
27
+ lines.push(`双目标路径:先止盈 ${formatRate(primary.tradePath.takeProfitFirstCount, primary.tradePath.sampleCount)},先止损 ${formatRate(primary.tradePath.stopFirstCount, primary.tradePath.sampleCount)},未决 ${formatRate(primary.tradePath.unresolvedCount, primary.tradePath.sampleCount)}。`);
28
+ }
29
+ }
30
+ else {
31
+ lines.push("历史复盘样本仍少,暂时只能作为弱参考。");
32
+ }
33
+ if (report.recentSnapshots.length > 0) {
34
+ lines.push(`最近活动价位:${report.recentSnapshots.map(formatSnapshot).join(";")}`);
35
+ }
36
+ const conclusion = report.conclusion.replace(/^💡\s*结论:\s*/, "").trim();
37
+ if (conclusion) {
38
+ lines.push(`复盘结论:${conclusion}`);
39
+ }
40
+ return {
41
+ available: true,
42
+ summary: lines.join("\n"),
43
+ asOf,
44
+ };
45
+ }
46
+ }
47
+ function countSamples(stats) {
48
+ return (stats.support.sampleCount +
49
+ stats.resistance.sampleCount +
50
+ stats.stopLoss.sampleCount +
51
+ stats.takeProfit.sampleCount +
52
+ stats.breakthrough.sampleCount +
53
+ stats.tradePath.sampleCount);
54
+ }
55
+ function formatRate(numerator, denominator) {
56
+ if (!(denominator > 0)) {
57
+ return "样本不足";
58
+ }
59
+ return `${((numerator / denominator) * 100).toFixed(1)}%`;
60
+ }
61
+ function formatSnapshot(snapshot) {
62
+ return [
63
+ snapshot.analysis_date,
64
+ `评分${formatMaybeInt(snapshot.score)}`,
65
+ `支撑${formatMaybePrice(snapshot.support)}`,
66
+ `压力${formatMaybePrice(snapshot.resistance)}`,
67
+ `止损${formatMaybePrice(snapshot.stop_loss)}`,
68
+ `止盈${formatMaybePrice(snapshot.take_profit)}`,
69
+ ].join(" ");
70
+ }
71
+ function formatMaybePrice(value) {
72
+ return value == null ? "-" : value.toFixed(2);
73
+ }
74
+ function formatMaybeInt(value) {
75
+ return value == null ? "-" : String(Math.trunc(value));
76
+ }
@@ -0,0 +1,43 @@
1
+ import type { TickFlowBalanceSheetRecord, TickFlowCashFlowRecord, TickFlowFinancialMetricsRecord, TickFlowFinancialQueryOptions, TickFlowIncomeRecord, TickFlowInstrument, TickFlowQuote } from "../types/tickflow.js";
2
+ export declare class TickFlowClientError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare class TickFlowClient {
6
+ private readonly baseUrl;
7
+ private readonly apiKey;
8
+ constructor(baseUrl: string, apiKey: string);
9
+ getBaseUrl(): string;
10
+ getApiKey(): string;
11
+ fetchInstruments(symbols: string[]): Promise<TickFlowInstrument[]>;
12
+ fetchQuotes(symbols: string[]): Promise<TickFlowQuote[]>;
13
+ fetchKlinesBatch<T = unknown>(symbols: string[], params?: {
14
+ period?: string;
15
+ count?: number;
16
+ adjust?: string;
17
+ startTime?: number;
18
+ endTime?: number;
19
+ }): Promise<{
20
+ data?: Record<string, T>;
21
+ }>;
22
+ fetchIntradayKlinesBatch<T = unknown>(symbols: string[], params?: {
23
+ period?: string;
24
+ count?: number;
25
+ }): Promise<{
26
+ data?: Record<string, T>;
27
+ }>;
28
+ fetchBalanceSheet(symbols: string[], params?: TickFlowFinancialQueryOptions): Promise<{
29
+ data?: Record<string, TickFlowBalanceSheetRecord[]>;
30
+ }>;
31
+ fetchCashFlow(symbols: string[], params?: TickFlowFinancialQueryOptions): Promise<{
32
+ data?: Record<string, TickFlowCashFlowRecord[]>;
33
+ }>;
34
+ fetchIncome(symbols: string[], params?: TickFlowFinancialQueryOptions): Promise<{
35
+ data?: Record<string, TickFlowIncomeRecord[]>;
36
+ }>;
37
+ fetchFinancialMetrics(symbols: string[], params?: TickFlowFinancialQueryOptions): Promise<{
38
+ data?: Record<string, TickFlowFinancialMetricsRecord[]>;
39
+ }>;
40
+ private fetchFinancialRecords;
41
+ private requestJson;
42
+ private handleResponse;
43
+ }
@@ -0,0 +1,126 @@
1
+ export class TickFlowClientError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "TickFlowClientError";
5
+ }
6
+ }
7
+ export class TickFlowClient {
8
+ baseUrl;
9
+ apiKey;
10
+ constructor(baseUrl, apiKey) {
11
+ this.baseUrl = baseUrl;
12
+ this.apiKey = apiKey;
13
+ }
14
+ getBaseUrl() {
15
+ return this.baseUrl;
16
+ }
17
+ getApiKey() {
18
+ return this.apiKey;
19
+ }
20
+ async fetchInstruments(symbols) {
21
+ if (symbols.length === 0) {
22
+ return [];
23
+ }
24
+ const url = new URL("/v1/instruments", this.baseUrl);
25
+ url.searchParams.set("symbols", symbols.join(","));
26
+ const response = await this.requestJson(url.toString(), {
27
+ method: "GET",
28
+ });
29
+ return response.data ?? [];
30
+ }
31
+ async fetchQuotes(symbols) {
32
+ if (symbols.length === 0) {
33
+ return [];
34
+ }
35
+ const url = new URL("/v1/quotes", this.baseUrl);
36
+ const response = await this.requestJson(url.toString(), {
37
+ method: "POST",
38
+ body: JSON.stringify({ symbols }),
39
+ });
40
+ return response.data ?? [];
41
+ }
42
+ async fetchKlinesBatch(symbols, params = {}) {
43
+ if (symbols.length === 0) {
44
+ return { data: {} };
45
+ }
46
+ const url = new URL("/v1/klines/batch", this.baseUrl);
47
+ url.searchParams.set("symbols", symbols.join(","));
48
+ url.searchParams.set("period", params.period ?? "1d");
49
+ url.searchParams.set("count", String(params.count ?? 90));
50
+ url.searchParams.set("adjust", params.adjust ?? "forward");
51
+ if (params.startTime != null) {
52
+ url.searchParams.set("start_time", String(params.startTime));
53
+ }
54
+ if (params.endTime != null) {
55
+ url.searchParams.set("end_time", String(params.endTime));
56
+ }
57
+ return this.requestJson(url.toString(), {
58
+ method: "GET",
59
+ });
60
+ }
61
+ async fetchIntradayKlinesBatch(symbols, params = {}) {
62
+ if (symbols.length === 0) {
63
+ return { data: {} };
64
+ }
65
+ const url = new URL("/v1/klines/intraday/batch", this.baseUrl);
66
+ url.searchParams.set("symbols", symbols.join(","));
67
+ url.searchParams.set("period", params.period ?? "1m");
68
+ if (params.count != null) {
69
+ url.searchParams.set("count", String(params.count));
70
+ }
71
+ return this.requestJson(url.toString(), {
72
+ method: "GET",
73
+ });
74
+ }
75
+ async fetchBalanceSheet(symbols, params = {}) {
76
+ return this.fetchFinancialRecords("/v1/financials/balance-sheet", symbols, params);
77
+ }
78
+ async fetchCashFlow(symbols, params = {}) {
79
+ return this.fetchFinancialRecords("/v1/financials/cash-flow", symbols, params);
80
+ }
81
+ async fetchIncome(symbols, params = {}) {
82
+ return this.fetchFinancialRecords("/v1/financials/income", symbols, params);
83
+ }
84
+ async fetchFinancialMetrics(symbols, params = {}) {
85
+ return this.fetchFinancialRecords("/v1/financials/metrics", symbols, params);
86
+ }
87
+ async fetchFinancialRecords(endpoint, symbols, params = {}) {
88
+ if (symbols.length === 0) {
89
+ return { data: {} };
90
+ }
91
+ const url = new URL(endpoint, this.baseUrl);
92
+ url.searchParams.set("symbols", symbols.join(","));
93
+ if (params.start_date) {
94
+ url.searchParams.set("start_date", params.start_date);
95
+ }
96
+ if (params.end_date) {
97
+ url.searchParams.set("end_date", params.end_date);
98
+ }
99
+ if (params.latest != null) {
100
+ url.searchParams.set("latest", String(params.latest));
101
+ }
102
+ return this.requestJson(url.toString(), {
103
+ method: "GET",
104
+ });
105
+ }
106
+ async requestJson(url, init) {
107
+ const headers = new Headers(init.headers ?? {});
108
+ headers.set("x-api-key", this.apiKey);
109
+ headers.set("Content-Type", "application/json");
110
+ const first = await fetch(url, { ...init, headers });
111
+ if (first.status === 429) {
112
+ const retryAfter = Number(first.headers.get("Retry-After") ?? "5");
113
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
114
+ const retry = await fetch(url, { ...init, headers });
115
+ return this.handleResponse(retry, url);
116
+ }
117
+ return this.handleResponse(first, url);
118
+ }
119
+ async handleResponse(response, url) {
120
+ if (!response.ok) {
121
+ const text = await response.text();
122
+ throw new TickFlowClientError(`TickFlow request failed: ${response.status} ${response.statusText} (${url}) ${text}`.trim());
123
+ }
124
+ return (await response.json());
125
+ }
126
+ }