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.
- package/README.md +151 -0
- package/day_future.txt +8797 -0
- package/dist/analysis/orchestrators/composite-analysis.orchestrator.d.ts +35 -0
- package/dist/analysis/orchestrators/composite-analysis.orchestrator.js +232 -0
- package/dist/analysis/parsers/json-block.parser.d.ts +1 -0
- package/dist/analysis/parsers/json-block.parser.js +13 -0
- package/dist/analysis/parsers/key-levels.parser.d.ts +5 -0
- package/dist/analysis/parsers/key-levels.parser.js +61 -0
- package/dist/analysis/parsers/post-close-review.parser.d.ts +2 -0
- package/dist/analysis/parsers/post-close-review.parser.js +79 -0
- package/dist/analysis/parsers/watchlist-profile.parser.d.ts +6 -0
- package/dist/analysis/parsers/watchlist-profile.parser.js +93 -0
- package/dist/analysis/providers/financial-analysis.provider.d.ts +12 -0
- package/dist/analysis/providers/financial-analysis.provider.js +85 -0
- package/dist/analysis/providers/market-analysis.provider.d.ts +27 -0
- package/dist/analysis/providers/market-analysis.provider.js +187 -0
- package/dist/analysis/providers/news-analysis.provider.d.ts +9 -0
- package/dist/analysis/providers/news-analysis.provider.js +60 -0
- package/dist/analysis/tasks/analysis-step-task.d.ts +11 -0
- package/dist/analysis/tasks/analysis-step-task.js +1 -0
- package/dist/analysis/tasks/analysis-task.d.ts +13 -0
- package/dist/analysis/tasks/analysis-task.js +1 -0
- package/dist/analysis/tasks/composite-stock-analysis.task.d.ts +17 -0
- package/dist/analysis/tasks/composite-stock-analysis.task.js +47 -0
- package/dist/analysis/tasks/financial-fundamental-lite.task.d.ts +10 -0
- package/dist/analysis/tasks/financial-fundamental-lite.task.js +21 -0
- package/dist/analysis/tasks/financial-fundamental.task.d.ts +12 -0
- package/dist/analysis/tasks/financial-fundamental.task.js +64 -0
- package/dist/analysis/tasks/kline-technical-signal.task.d.ts +10 -0
- package/dist/analysis/tasks/kline-technical-signal.task.js +41 -0
- package/dist/analysis/tasks/kline-technical.task.d.ts +32 -0
- package/dist/analysis/tasks/kline-technical.task.js +61 -0
- package/dist/analysis/tasks/news-catalyst.task.d.ts +11 -0
- package/dist/analysis/tasks/news-catalyst.task.js +62 -0
- package/dist/analysis/tasks/post-close-review.task.d.ts +12 -0
- package/dist/analysis/tasks/post-close-review.task.js +35 -0
- package/dist/analysis/types/composite-analysis.d.ts +123 -0
- package/dist/analysis/types/composite-analysis.js +1 -0
- package/dist/background/daily-update.worker.d.ts +50 -0
- package/dist/background/daily-update.worker.js +546 -0
- package/dist/background/realtime-monitor.worker.d.ts +8 -0
- package/dist/background/realtime-monitor.worker.js +28 -0
- package/dist/bootstrap.d.ts +45 -0
- package/dist/bootstrap.js +214 -0
- package/dist/config/normalize.d.ts +4 -0
- package/dist/config/normalize.js +99 -0
- package/dist/config/schema.d.ts +23 -0
- package/dist/config/schema.js +18 -0
- package/dist/config/tickflow-access.d.ts +4 -0
- package/dist/config/tickflow-access.js +28 -0
- package/dist/constants/market-indexes.d.ts +5 -0
- package/dist/constants/market-indexes.js +4 -0
- package/dist/dev/run-daily-update-loop.d.ts +1 -0
- package/dist/dev/run-daily-update-loop.js +48 -0
- package/dist/dev/run-monitor-loop.d.ts +1 -0
- package/dist/dev/run-monitor-loop.js +60 -0
- package/dist/dev/run-tool.d.ts +1 -0
- package/dist/dev/run-tool.js +49 -0
- package/dist/dev/tickflow-assist-cli.d.ts +2 -0
- package/dist/dev/tickflow-assist-cli.js +525 -0
- package/dist/dev/validate-mx-search.d.ts +1 -0
- package/dist/dev/validate-mx-search.js +212 -0
- package/dist/plugin-commands.d.ts +3 -0
- package/dist/plugin-commands.js +229 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.js +151 -0
- package/dist/prompts/analysis/common-system-prompt.d.ts +1 -0
- package/dist/prompts/analysis/common-system-prompt.js +44 -0
- package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +11 -0
- package/dist/prompts/analysis/composite-analysis-user-prompt.js +102 -0
- package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +7 -0
- package/dist/prompts/analysis/financial-analysis-user-prompt.js +106 -0
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +7 -0
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +59 -0
- package/dist/prompts/analysis/index.d.ts +8 -0
- package/dist/prompts/analysis/index.js +8 -0
- package/dist/prompts/analysis/kline-analysis-user-prompt.d.ts +13 -0
- package/dist/prompts/analysis/kline-analysis-user-prompt.js +215 -0
- package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +8 -0
- package/dist/prompts/analysis/news-analysis-user-prompt.js +57 -0
- package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +3 -0
- package/dist/prompts/analysis/post-close-review-user-prompt.js +129 -0
- package/dist/prompts/analysis/watchlist-profile-extraction-prompt.d.ts +7 -0
- package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +52 -0
- package/dist/prompts/analysis-system-prompt.d.ts +1 -0
- package/dist/prompts/analysis-system-prompt.js +1 -0
- package/dist/prompts/analysis-user-prompt.d.ts +1 -0
- package/dist/prompts/analysis-user-prompt.js +1 -0
- package/dist/runtime/daily-update-process.d.ts +3 -0
- package/dist/runtime/daily-update-process.js +24 -0
- package/dist/runtime/monitor-process.d.ts +3 -0
- package/dist/runtime/monitor-process.js +24 -0
- package/dist/runtime/plugin-api.d.ts +22 -0
- package/dist/runtime/plugin-api.js +2 -0
- package/dist/runtime/process-config.d.ts +8 -0
- package/dist/runtime/process-config.js +35 -0
- package/dist/services/alert-service.d.ts +45 -0
- package/dist/services/alert-service.js +198 -0
- package/dist/services/analysis-service.d.ts +20 -0
- package/dist/services/analysis-service.js +73 -0
- package/dist/services/analysis-view-service.d.ts +23 -0
- package/dist/services/analysis-view-service.js +343 -0
- package/dist/services/financial-lite-service.d.ts +23 -0
- package/dist/services/financial-lite-service.js +201 -0
- package/dist/services/financial-service.d.ts +20 -0
- package/dist/services/financial-service.js +47 -0
- package/dist/services/indicator-service.d.ts +9 -0
- package/dist/services/indicator-service.js +67 -0
- package/dist/services/instrument-service.d.ts +6 -0
- package/dist/services/instrument-service.js +21 -0
- package/dist/services/key-level-service.d.ts +2 -0
- package/dist/services/key-level-service.js +2 -0
- package/dist/services/key-levels-backtest-service.d.ts +71 -0
- package/dist/services/key-levels-backtest-service.js +427 -0
- package/dist/services/kline-service.d.ts +19 -0
- package/dist/services/kline-service.js +91 -0
- package/dist/services/monitor-service.d.ts +44 -0
- package/dist/services/monitor-service.js +598 -0
- package/dist/services/mx-search-service.d.ts +22 -0
- package/dist/services/mx-search-service.js +286 -0
- package/dist/services/post-close-review-service.d.ts +31 -0
- package/dist/services/post-close-review-service.js +402 -0
- package/dist/services/quote-service.d.ts +7 -0
- package/dist/services/quote-service.js +9 -0
- package/dist/services/review-memory-service.d.ts +7 -0
- package/dist/services/review-memory-service.js +76 -0
- package/dist/services/tickflow-client.d.ts +43 -0
- package/dist/services/tickflow-client.js +126 -0
- package/dist/services/trading-calendar-service.d.ts +20 -0
- package/dist/services/trading-calendar-service.js +102 -0
- package/dist/services/update-service.d.ts +21 -0
- package/dist/services/update-service.js +130 -0
- package/dist/services/watchlist-profile-service.d.ts +20 -0
- package/dist/services/watchlist-profile-service.js +76 -0
- package/dist/services/watchlist-service.d.ts +43 -0
- package/dist/services/watchlist-service.js +204 -0
- package/dist/storage/db.d.ts +23 -0
- package/dist/storage/db.js +70 -0
- package/dist/storage/repositories/alert-log-repo.d.ts +15 -0
- package/dist/storage/repositories/alert-log-repo.js +54 -0
- package/dist/storage/repositories/analysis-log-repo.d.ts +8 -0
- package/dist/storage/repositories/analysis-log-repo.js +48 -0
- package/dist/storage/repositories/composite-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/composite-analysis-repo.js +116 -0
- package/dist/storage/repositories/financial-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/financial-analysis-repo.js +107 -0
- package/dist/storage/repositories/indicators-repo.d.ts +8 -0
- package/dist/storage/repositories/indicators-repo.js +98 -0
- package/dist/storage/repositories/intraday-klines-repo.d.ts +9 -0
- package/dist/storage/repositories/intraday-klines-repo.js +102 -0
- package/dist/storage/repositories/key-levels-history-repo.d.ts +9 -0
- package/dist/storage/repositories/key-levels-history-repo.js +91 -0
- package/dist/storage/repositories/key-levels-repo.d.ts +9 -0
- package/dist/storage/repositories/key-levels-repo.js +83 -0
- package/dist/storage/repositories/klines-repo.d.ts +8 -0
- package/dist/storage/repositories/klines-repo.js +60 -0
- package/dist/storage/repositories/news-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/news-analysis-repo.js +107 -0
- package/dist/storage/repositories/technical-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/technical-analysis-repo.js +80 -0
- package/dist/storage/repositories/watchlist-repo.d.ts +10 -0
- package/dist/storage/repositories/watchlist-repo.js +124 -0
- package/dist/storage/schemas.d.ts +13 -0
- package/dist/storage/schemas.js +177 -0
- package/dist/tools/add-stock.tool.d.ts +13 -0
- package/dist/tools/add-stock.tool.js +123 -0
- package/dist/tools/analyze.tool.d.ts +8 -0
- package/dist/tools/analyze.tool.js +24 -0
- package/dist/tools/backtest-key-levels.tool.d.ts +8 -0
- package/dist/tools/backtest-key-levels.tool.js +43 -0
- package/dist/tools/daily-update-status.tool.d.ts +6 -0
- package/dist/tools/daily-update-status.tool.js +9 -0
- package/dist/tools/fetch-financials.tool.d.ts +8 -0
- package/dist/tools/fetch-financials.tool.js +224 -0
- package/dist/tools/fetch-intraday-klines.tool.d.ts +11 -0
- package/dist/tools/fetch-intraday-klines.tool.js +58 -0
- package/dist/tools/fetch-klines.tool.d.ts +11 -0
- package/dist/tools/fetch-klines.tool.js +61 -0
- package/dist/tools/list-watchlist.tool.d.ts +6 -0
- package/dist/tools/list-watchlist.tool.js +22 -0
- package/dist/tools/monitor-status.tool.d.ts +6 -0
- package/dist/tools/monitor-status.tool.js +9 -0
- package/dist/tools/mx-search.tool.d.ts +8 -0
- package/dist/tools/mx-search.tool.js +77 -0
- package/dist/tools/mx-select-stock.tool.d.ts +8 -0
- package/dist/tools/mx-select-stock.tool.js +118 -0
- package/dist/tools/query-database.tool.d.ts +8 -0
- package/dist/tools/query-database.tool.js +283 -0
- package/dist/tools/refresh-watchlist-names.tool.d.ts +7 -0
- package/dist/tools/refresh-watchlist-names.tool.js +17 -0
- package/dist/tools/refresh-watchlist-profiles.tool.d.ts +9 -0
- package/dist/tools/refresh-watchlist-profiles.tool.js +67 -0
- package/dist/tools/remove-stock.tool.d.ts +9 -0
- package/dist/tools/remove-stock.tool.js +27 -0
- package/dist/tools/start-daily-update.tool.d.ts +10 -0
- package/dist/tools/start-daily-update.tool.js +23 -0
- package/dist/tools/start-monitor.tool.d.ts +9 -0
- package/dist/tools/start-monitor.tool.js +52 -0
- package/dist/tools/stop-daily-update.tool.d.ts +9 -0
- package/dist/tools/stop-daily-update.tool.js +20 -0
- package/dist/tools/stop-monitor.tool.d.ts +9 -0
- package/dist/tools/stop-monitor.tool.js +44 -0
- package/dist/tools/test-alert.tool.d.ts +7 -0
- package/dist/tools/test-alert.tool.js +21 -0
- package/dist/tools/update-all.tool.d.ts +9 -0
- package/dist/tools/update-all.tool.js +17 -0
- package/dist/tools/view-analysis.tool.d.ts +8 -0
- package/dist/tools/view-analysis.tool.js +95 -0
- package/dist/types/daily-update.d.ts +26 -0
- package/dist/types/daily-update.js +1 -0
- package/dist/types/domain.d.ts +140 -0
- package/dist/types/domain.js +1 -0
- package/dist/types/indicator.d.ts +43 -0
- package/dist/types/indicator.js +1 -0
- package/dist/types/monitor.d.ts +17 -0
- package/dist/types/monitor.js +1 -0
- package/dist/types/mx-search.d.ts +14 -0
- package/dist/types/mx-search.js +1 -0
- package/dist/types/mx-select-stock.d.ts +28 -0
- package/dist/types/mx-select-stock.js +1 -0
- package/dist/types/tickflow.d.ts +133 -0
- package/dist/types/tickflow.js +1 -0
- package/dist/utils/abortable-sleep.d.ts +1 -0
- package/dist/utils/abortable-sleep.js +20 -0
- package/dist/utils/china-time.d.ts +3 -0
- package/dist/utils/china-time.js +18 -0
- package/dist/utils/cost-price.d.ts +3 -0
- package/dist/utils/cost-price.js +18 -0
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/format.js +3 -0
- package/dist/utils/process.d.ts +5 -0
- package/dist/utils/process.js +1 -0
- package/dist/utils/symbol.d.ts +1 -0
- package/dist/utils/symbol.js +13 -0
- package/docs/installation.md +391 -0
- package/docs/usage.md +244 -0
- package/openclaw.plugin.json +116 -0
- package/package.json +57 -0
- package/python/indicator_runner.py +31 -0
- package/python/indicators.py +148 -0
- package/python/pyproject.toml +9 -0
- package/python/requirements.txt +3 -0
- package/python/uv.lock +366 -0
- package/skills/database-query/SKILL.md +58 -0
- package/skills/stock-analysis/SKILL.md +106 -0
- package/skills/usage-help/SKILL.md +102 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
export class Database {
|
|
3
|
+
baseDir;
|
|
4
|
+
constructor(baseDir) {
|
|
5
|
+
this.baseDir = baseDir;
|
|
6
|
+
}
|
|
7
|
+
async getConnection() {
|
|
8
|
+
return this.connect();
|
|
9
|
+
}
|
|
10
|
+
async hasTable(name) {
|
|
11
|
+
const connection = await this.getConnection();
|
|
12
|
+
const tableNames = await this.listTableNames(connection);
|
|
13
|
+
return tableNames.includes(name);
|
|
14
|
+
}
|
|
15
|
+
async listTables() {
|
|
16
|
+
const connection = await this.getConnection();
|
|
17
|
+
return this.listTableNames(connection);
|
|
18
|
+
}
|
|
19
|
+
async openTable(name) {
|
|
20
|
+
const connection = await this.getConnection();
|
|
21
|
+
return connection.openTable(name);
|
|
22
|
+
}
|
|
23
|
+
async createTable(name, rows, schema) {
|
|
24
|
+
const connection = await this.getConnection();
|
|
25
|
+
return connection.createTable(name, rows, schema ? { schema } : undefined);
|
|
26
|
+
}
|
|
27
|
+
async tableToArray(name) {
|
|
28
|
+
if (!(await this.hasTable(name))) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const table = await this.openTable(name);
|
|
32
|
+
const query = table.query?.();
|
|
33
|
+
if (!query?.toArray) {
|
|
34
|
+
throw new Error(`LanceDB query().toArray() is unavailable for table ${name}`);
|
|
35
|
+
}
|
|
36
|
+
return query.toArray();
|
|
37
|
+
}
|
|
38
|
+
async describeTable(name) {
|
|
39
|
+
if (!(await this.hasTable(name))) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const table = await this.openTable(name);
|
|
43
|
+
const schema = await table.schema();
|
|
44
|
+
const fields = schema.fields ?? [];
|
|
45
|
+
return fields.map((field) => ({
|
|
46
|
+
name: field.name,
|
|
47
|
+
type: String(field.type ?? "unknown"),
|
|
48
|
+
nullable: Boolean(field.nullable),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
async connect() {
|
|
52
|
+
await mkdir(this.baseDir, { recursive: true });
|
|
53
|
+
const lancedb = (await import("@lancedb/lancedb"));
|
|
54
|
+
return lancedb.connect(this.baseDir);
|
|
55
|
+
}
|
|
56
|
+
async listTableNames(connection) {
|
|
57
|
+
const conn = connection;
|
|
58
|
+
if (conn.tableNames) {
|
|
59
|
+
return conn.tableNames();
|
|
60
|
+
}
|
|
61
|
+
if (conn.listTables) {
|
|
62
|
+
const result = await conn.listTables();
|
|
63
|
+
if (Array.isArray(result)) {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
return result.tables ?? [];
|
|
67
|
+
}
|
|
68
|
+
throw new Error("Unable to list LanceDB tables");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Database } from "../db.js";
|
|
2
|
+
export interface AlertLogEntry {
|
|
3
|
+
symbol: string;
|
|
4
|
+
alert_date: string;
|
|
5
|
+
rule_name: string;
|
|
6
|
+
message: string;
|
|
7
|
+
triggered_at: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class AlertLogRepository {
|
|
10
|
+
private readonly db;
|
|
11
|
+
constructor(db: Database);
|
|
12
|
+
append(entry: AlertLogEntry): Promise<void>;
|
|
13
|
+
listByNaturalDate(date: string): Promise<AlertLogEntry[]>;
|
|
14
|
+
isSentThisSession(symbol: string, ruleName: string, sessionKey: string): Promise<boolean>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { alertLogSchema } from "../schemas.js";
|
|
2
|
+
const ALERT_LOG_TABLE = "alert_log";
|
|
3
|
+
export class AlertLogRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async append(entry) {
|
|
9
|
+
const row = {
|
|
10
|
+
symbol: entry.symbol,
|
|
11
|
+
alert_date: entry.alert_date,
|
|
12
|
+
rule_name: entry.rule_name,
|
|
13
|
+
message: entry.message,
|
|
14
|
+
triggered_at: entry.triggered_at,
|
|
15
|
+
};
|
|
16
|
+
if (!(await this.db.hasTable(ALERT_LOG_TABLE))) {
|
|
17
|
+
await this.db.createTable(ALERT_LOG_TABLE, [row], alertLogSchema);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const table = await this.db.openTable(ALERT_LOG_TABLE);
|
|
21
|
+
await table.add([row]);
|
|
22
|
+
}
|
|
23
|
+
async listByNaturalDate(date) {
|
|
24
|
+
if (!(await this.db.hasTable(ALERT_LOG_TABLE))) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const table = await this.db.openTable(ALERT_LOG_TABLE);
|
|
28
|
+
const rows = (await table
|
|
29
|
+
.query()
|
|
30
|
+
.where(`alert_date IN ('${date}_AM', '${date}_PM')`)
|
|
31
|
+
.toArray());
|
|
32
|
+
return rows.map((row) => ({
|
|
33
|
+
symbol: String(row.symbol),
|
|
34
|
+
alert_date: String(row.alert_date),
|
|
35
|
+
rule_name: String(row.rule_name),
|
|
36
|
+
message: String(row.message ?? ""),
|
|
37
|
+
triggered_at: String(row.triggered_at ?? ""),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
async isSentThisSession(symbol, ruleName, sessionKey) {
|
|
41
|
+
if (!(await this.db.hasTable(ALERT_LOG_TABLE))) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const table = await this.db.openTable(ALERT_LOG_TABLE);
|
|
45
|
+
const rows = (await table
|
|
46
|
+
.query()
|
|
47
|
+
.where(`symbol = '${escapeSqlString(symbol)}' AND rule_name = '${escapeSqlString(ruleName)}' AND alert_date = '${escapeSqlString(sessionKey)}'`)
|
|
48
|
+
.toArray());
|
|
49
|
+
return rows.length > 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function escapeSqlString(value) {
|
|
53
|
+
return value.replace(/'/g, "''");
|
|
54
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Database } from "../db.js";
|
|
2
|
+
import type { AnalysisLogEntry } from "../../types/domain.js";
|
|
3
|
+
export declare class AnalysisLogRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
append(entry: AnalysisLogEntry): Promise<void>;
|
|
7
|
+
getLatest(symbol: string): Promise<AnalysisLogEntry | null>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { analysisLogSchema } from "../schemas.js";
|
|
2
|
+
const ANALYSIS_LOG_TABLE = "analysis_log";
|
|
3
|
+
export class AnalysisLogRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async append(entry) {
|
|
9
|
+
const row = toAnalysisLogRow(entry);
|
|
10
|
+
if (!(await this.db.hasTable(ANALYSIS_LOG_TABLE))) {
|
|
11
|
+
await this.db.createTable(ANALYSIS_LOG_TABLE, [row], analysisLogSchema);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const table = await this.db.openTable(ANALYSIS_LOG_TABLE);
|
|
15
|
+
await table.add([row]);
|
|
16
|
+
}
|
|
17
|
+
async getLatest(symbol) {
|
|
18
|
+
if (!(await this.db.hasTable(ANALYSIS_LOG_TABLE))) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const table = await this.db.openTable(ANALYSIS_LOG_TABLE);
|
|
22
|
+
const rows = (await table
|
|
23
|
+
.query()
|
|
24
|
+
.where(`symbol = '${escapeSqlString(symbol)}'`)
|
|
25
|
+
.toArray());
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const row = rows[rows.length - 1];
|
|
30
|
+
return {
|
|
31
|
+
symbol: String(row.symbol),
|
|
32
|
+
analysis_date: String(row.analysis_date),
|
|
33
|
+
analysis_text: String(row.analysis_text),
|
|
34
|
+
structured_ok: Boolean(Number(row.structured_ok ?? 0)),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function toAnalysisLogRow(entry) {
|
|
39
|
+
return {
|
|
40
|
+
symbol: entry.symbol,
|
|
41
|
+
analysis_date: entry.analysis_date,
|
|
42
|
+
analysis_text: entry.analysis_text,
|
|
43
|
+
structured_ok: entry.structured_ok ? 1 : 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function escapeSqlString(value) {
|
|
47
|
+
return value.replace(/'/g, "''");
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CompositeAnalysisEntry } from "../../types/domain.js";
|
|
2
|
+
import { Database } from "../db.js";
|
|
3
|
+
export declare class CompositeAnalysisRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
append(entry: CompositeAnalysisEntry): Promise<void>;
|
|
7
|
+
getLatest(symbol: string): Promise<CompositeAnalysisEntry | null>;
|
|
8
|
+
listLatest(symbol: string, limit: number): Promise<CompositeAnalysisEntry[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { compositeAnalysisSchema } from "../schemas.js";
|
|
2
|
+
const COMPOSITE_ANALYSIS_TABLE = "composite_analysis";
|
|
3
|
+
export class CompositeAnalysisRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async append(entry) {
|
|
9
|
+
const row = toCompositeAnalysisRow(entry);
|
|
10
|
+
if (!(await this.db.hasTable(COMPOSITE_ANALYSIS_TABLE))) {
|
|
11
|
+
await this.db.createTable(COMPOSITE_ANALYSIS_TABLE, [row], compositeAnalysisSchema);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const table = await this.db.openTable(COMPOSITE_ANALYSIS_TABLE);
|
|
15
|
+
await table.add([row]);
|
|
16
|
+
}
|
|
17
|
+
async getLatest(symbol) {
|
|
18
|
+
const rows = await this.listLatest(symbol, 1);
|
|
19
|
+
return rows[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
async listLatest(symbol, limit) {
|
|
22
|
+
if (!(await this.db.hasTable(COMPOSITE_ANALYSIS_TABLE))) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const table = await this.db.openTable(COMPOSITE_ANALYSIS_TABLE);
|
|
26
|
+
const rows = (await table
|
|
27
|
+
.query()
|
|
28
|
+
.where(`symbol = '${escapeSqlString(symbol)}'`)
|
|
29
|
+
.toArray());
|
|
30
|
+
return rows.slice(-Math.max(1, limit)).reverse().map((row) => fromCompositeAnalysisRow(row));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function toCompositeAnalysisRow(entry) {
|
|
34
|
+
return {
|
|
35
|
+
symbol: entry.symbol,
|
|
36
|
+
analysis_date: entry.analysis_date,
|
|
37
|
+
analysis_text: entry.analysis_text,
|
|
38
|
+
structured_ok: entry.structured_ok ? 1 : 0,
|
|
39
|
+
current_price: entry.current_price,
|
|
40
|
+
stop_loss: entry.stop_loss,
|
|
41
|
+
breakthrough: entry.breakthrough,
|
|
42
|
+
support: entry.support,
|
|
43
|
+
cost_level: entry.cost_level,
|
|
44
|
+
resistance: entry.resistance,
|
|
45
|
+
take_profit: entry.take_profit,
|
|
46
|
+
gap: entry.gap,
|
|
47
|
+
target: entry.target,
|
|
48
|
+
round_number: entry.round_number,
|
|
49
|
+
score: entry.score == null ? null : Math.trunc(entry.score),
|
|
50
|
+
technical_score: entry.technical_score == null ? null : Math.trunc(entry.technical_score),
|
|
51
|
+
financial_score: entry.financial_score == null ? null : Math.trunc(entry.financial_score),
|
|
52
|
+
news_score: entry.news_score == null ? null : Math.trunc(entry.news_score),
|
|
53
|
+
financial_bias: entry.financial_bias,
|
|
54
|
+
news_bias: entry.news_bias,
|
|
55
|
+
evidence_json: JSON.stringify(entry.evidence),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function fromCompositeAnalysisRow(row) {
|
|
59
|
+
return {
|
|
60
|
+
symbol: String(row.symbol),
|
|
61
|
+
analysis_date: String(row.analysis_date),
|
|
62
|
+
analysis_text: String(row.analysis_text ?? ""),
|
|
63
|
+
structured_ok: Boolean(Number(row.structured_ok ?? 0)),
|
|
64
|
+
current_price: toNullableNumber(row.current_price),
|
|
65
|
+
stop_loss: toNullableNumber(row.stop_loss),
|
|
66
|
+
breakthrough: toNullableNumber(row.breakthrough),
|
|
67
|
+
support: toNullableNumber(row.support),
|
|
68
|
+
cost_level: toNullableNumber(row.cost_level),
|
|
69
|
+
resistance: toNullableNumber(row.resistance),
|
|
70
|
+
take_profit: toNullableNumber(row.take_profit),
|
|
71
|
+
gap: toNullableNumber(row.gap),
|
|
72
|
+
target: toNullableNumber(row.target),
|
|
73
|
+
round_number: toNullableNumber(row.round_number),
|
|
74
|
+
score: toNullableNumber(row.score),
|
|
75
|
+
technical_score: toNullableNumber(row.technical_score),
|
|
76
|
+
financial_score: toNullableNumber(row.financial_score),
|
|
77
|
+
news_score: toNullableNumber(row.news_score),
|
|
78
|
+
financial_bias: normalizeBias(row.financial_bias),
|
|
79
|
+
news_bias: normalizeBias(row.news_bias),
|
|
80
|
+
evidence: parseJsonObject(row.evidence_json, {
|
|
81
|
+
technical_structured: false,
|
|
82
|
+
financial_available: false,
|
|
83
|
+
financial_latest_period_end: null,
|
|
84
|
+
news_available: false,
|
|
85
|
+
news_query: "",
|
|
86
|
+
news_source_count: 0,
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function toNullableNumber(value) {
|
|
91
|
+
if (value == null) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const num = Number(value);
|
|
95
|
+
return Number.isFinite(num) ? num : null;
|
|
96
|
+
}
|
|
97
|
+
function parseJsonObject(value, fallback) {
|
|
98
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
99
|
+
return fallback;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(value);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return fallback;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function normalizeBias(value) {
|
|
109
|
+
if (value === "positive" || value === "negative") {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
return "neutral";
|
|
113
|
+
}
|
|
114
|
+
function escapeSqlString(value) {
|
|
115
|
+
return value.replace(/'/g, "''");
|
|
116
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FinancialAnalysisEntry } from "../../types/domain.js";
|
|
2
|
+
import { Database } from "../db.js";
|
|
3
|
+
export declare class FinancialAnalysisRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
append(entry: FinancialAnalysisEntry): Promise<void>;
|
|
7
|
+
getLatest(symbol: string): Promise<FinancialAnalysisEntry | null>;
|
|
8
|
+
listLatest(symbol: string, limit: number): Promise<FinancialAnalysisEntry[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { financialAnalysisSchema } from "../schemas.js";
|
|
2
|
+
const FINANCIAL_ANALYSIS_TABLE = "financial_analysis";
|
|
3
|
+
export class FinancialAnalysisRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async append(entry) {
|
|
9
|
+
const row = toFinancialAnalysisRow(entry);
|
|
10
|
+
if (!(await this.db.hasTable(FINANCIAL_ANALYSIS_TABLE))) {
|
|
11
|
+
await this.db.createTable(FINANCIAL_ANALYSIS_TABLE, [row], financialAnalysisSchema);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const table = await this.db.openTable(FINANCIAL_ANALYSIS_TABLE);
|
|
15
|
+
await table.add([row]);
|
|
16
|
+
}
|
|
17
|
+
async getLatest(symbol) {
|
|
18
|
+
const rows = await this.listLatest(symbol, 1);
|
|
19
|
+
return rows[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
async listLatest(symbol, limit) {
|
|
22
|
+
if (!(await this.db.hasTable(FINANCIAL_ANALYSIS_TABLE))) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const table = await this.db.openTable(FINANCIAL_ANALYSIS_TABLE);
|
|
26
|
+
const rows = (await table
|
|
27
|
+
.query()
|
|
28
|
+
.where(`symbol = '${escapeSqlString(symbol)}'`)
|
|
29
|
+
.toArray());
|
|
30
|
+
return rows.slice(-Math.max(1, limit)).reverse().map((row) => fromFinancialAnalysisRow(row));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function toFinancialAnalysisRow(entry) {
|
|
34
|
+
return {
|
|
35
|
+
symbol: entry.symbol,
|
|
36
|
+
analysis_date: entry.analysis_date,
|
|
37
|
+
analysis_text: entry.analysis_text,
|
|
38
|
+
score: entry.score == null ? null : Math.trunc(entry.score),
|
|
39
|
+
bias: entry.bias,
|
|
40
|
+
strengths_json: JSON.stringify(entry.strengths),
|
|
41
|
+
risks_json: JSON.stringify(entry.risks),
|
|
42
|
+
watch_items_json: JSON.stringify(entry.watch_items),
|
|
43
|
+
evidence_json: JSON.stringify(entry.evidence),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function fromFinancialAnalysisRow(row) {
|
|
47
|
+
return {
|
|
48
|
+
symbol: String(row.symbol),
|
|
49
|
+
analysis_date: String(row.analysis_date),
|
|
50
|
+
analysis_text: String(row.analysis_text ?? ""),
|
|
51
|
+
score: toNullableNumber(row.score),
|
|
52
|
+
bias: normalizeBias(row.bias),
|
|
53
|
+
strengths: parseJsonArray(row.strengths_json),
|
|
54
|
+
risks: parseJsonArray(row.risks_json),
|
|
55
|
+
watch_items: parseJsonArray(row.watch_items_json),
|
|
56
|
+
evidence: parseJsonObject(row.evidence_json, {
|
|
57
|
+
available: false,
|
|
58
|
+
latest_period_end: null,
|
|
59
|
+
latest_announce_date: null,
|
|
60
|
+
income_count: 0,
|
|
61
|
+
metrics_count: 0,
|
|
62
|
+
cash_flow_count: 0,
|
|
63
|
+
balance_sheet_count: 0,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function toNullableNumber(value) {
|
|
68
|
+
if (value == null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const num = Number(value);
|
|
72
|
+
return Number.isFinite(num) ? num : null;
|
|
73
|
+
}
|
|
74
|
+
function parseJsonArray(value) {
|
|
75
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(value);
|
|
80
|
+
return Array.isArray(parsed)
|
|
81
|
+
? parsed.map((item) => String(item ?? "").trim()).filter(Boolean)
|
|
82
|
+
: [];
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function parseJsonObject(value, fallback) {
|
|
89
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(value);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function normalizeBias(value) {
|
|
100
|
+
if (value === "positive" || value === "negative") {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return "neutral";
|
|
104
|
+
}
|
|
105
|
+
function escapeSqlString(value) {
|
|
106
|
+
return value.replace(/'/g, "''");
|
|
107
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Database } from "../db.js";
|
|
2
|
+
import type { IndicatorRow } from "../../types/indicator.js";
|
|
3
|
+
export declare class IndicatorsRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
saveAll(symbol: string, rows: IndicatorRow[]): Promise<void>;
|
|
7
|
+
listBySymbol(symbol: string): Promise<IndicatorRow[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { indicatorsSchema } from "../schemas.js";
|
|
2
|
+
const INDICATORS_TABLE = "indicators";
|
|
3
|
+
export class IndicatorsRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async saveAll(symbol, rows) {
|
|
9
|
+
const dbRows = rows.map((row) => toIndicatorRow(symbol, row));
|
|
10
|
+
if (!(await this.db.hasTable(INDICATORS_TABLE))) {
|
|
11
|
+
await this.db.createTable(INDICATORS_TABLE, dbRows, indicatorsSchema);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const table = await this.db.openTable(INDICATORS_TABLE);
|
|
15
|
+
await table.delete(`symbol = '${escapeSqlString(symbol)}'`);
|
|
16
|
+
if (dbRows.length > 0) {
|
|
17
|
+
await table.add(dbRows);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async listBySymbol(symbol) {
|
|
21
|
+
if (!(await this.db.hasTable(INDICATORS_TABLE))) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const table = await this.db.openTable(INDICATORS_TABLE);
|
|
25
|
+
const rows = (await table
|
|
26
|
+
.query()
|
|
27
|
+
.where(`symbol = '${escapeSqlString(symbol)}'`)
|
|
28
|
+
.toArray());
|
|
29
|
+
return rows
|
|
30
|
+
.map((row) => ({
|
|
31
|
+
symbol: String(row.symbol),
|
|
32
|
+
trade_date: String(row.trade_date),
|
|
33
|
+
ma5: toNullableNumber(row.ma5),
|
|
34
|
+
ma10: toNullableNumber(row.ma10),
|
|
35
|
+
ma20: toNullableNumber(row.ma20),
|
|
36
|
+
ma60: toNullableNumber(row.ma60),
|
|
37
|
+
macd: toNullableNumber(row.macd),
|
|
38
|
+
macd_signal: toNullableNumber(row.macd_signal),
|
|
39
|
+
macd_hist: toNullableNumber(row.macd_hist),
|
|
40
|
+
kdj_k: toNullableNumber(row.kdj_k),
|
|
41
|
+
kdj_d: toNullableNumber(row.kdj_d),
|
|
42
|
+
kdj_j: toNullableNumber(row.kdj_j),
|
|
43
|
+
rsi_6: toNullableNumber(row.rsi_6),
|
|
44
|
+
rsi_12: toNullableNumber(row.rsi_12),
|
|
45
|
+
rsi_24: toNullableNumber(row.rsi_24),
|
|
46
|
+
cci: toNullableNumber(row.cci),
|
|
47
|
+
bias_6: toNullableNumber(row.bias_6),
|
|
48
|
+
bias_12: toNullableNumber(row.bias_12),
|
|
49
|
+
bias_24: toNullableNumber(row.bias_24),
|
|
50
|
+
plus_di: toNullableNumber(row.plus_di),
|
|
51
|
+
minus_di: toNullableNumber(row.minus_di),
|
|
52
|
+
adx: toNullableNumber(row.adx),
|
|
53
|
+
boll_upper: toNullableNumber(row.boll_upper),
|
|
54
|
+
boll_mid: toNullableNumber(row.boll_mid),
|
|
55
|
+
boll_lower: toNullableNumber(row.boll_lower),
|
|
56
|
+
}))
|
|
57
|
+
.sort((a, b) => a.trade_date.localeCompare(b.trade_date));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function toIndicatorRow(symbol, row) {
|
|
61
|
+
return {
|
|
62
|
+
symbol,
|
|
63
|
+
trade_date: row.trade_date,
|
|
64
|
+
ma5: row.ma5 ?? null,
|
|
65
|
+
ma10: row.ma10 ?? null,
|
|
66
|
+
ma20: row.ma20 ?? null,
|
|
67
|
+
ma60: row.ma60 ?? null,
|
|
68
|
+
macd: row.macd ?? null,
|
|
69
|
+
macd_signal: row.macd_signal ?? null,
|
|
70
|
+
macd_hist: row.macd_hist ?? null,
|
|
71
|
+
kdj_k: row.kdj_k ?? null,
|
|
72
|
+
kdj_d: row.kdj_d ?? null,
|
|
73
|
+
kdj_j: row.kdj_j ?? null,
|
|
74
|
+
rsi_6: row.rsi_6 ?? null,
|
|
75
|
+
rsi_12: row.rsi_12 ?? null,
|
|
76
|
+
rsi_24: row.rsi_24 ?? null,
|
|
77
|
+
cci: row.cci ?? null,
|
|
78
|
+
bias_6: row.bias_6 ?? null,
|
|
79
|
+
bias_12: row.bias_12 ?? null,
|
|
80
|
+
bias_24: row.bias_24 ?? null,
|
|
81
|
+
plus_di: row.plus_di ?? null,
|
|
82
|
+
minus_di: row.minus_di ?? null,
|
|
83
|
+
adx: row.adx ?? null,
|
|
84
|
+
boll_upper: row.boll_upper ?? null,
|
|
85
|
+
boll_mid: row.boll_mid ?? null,
|
|
86
|
+
boll_lower: row.boll_lower ?? null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function toNullableNumber(value) {
|
|
90
|
+
if (value == null) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const num = Number(value);
|
|
94
|
+
return Number.isFinite(num) ? num : null;
|
|
95
|
+
}
|
|
96
|
+
function escapeSqlString(value) {
|
|
97
|
+
return value.replace(/'/g, "''");
|
|
98
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TickFlowIntradayKlineRow } from "../../types/tickflow.js";
|
|
2
|
+
import { Database } from "../db.js";
|
|
3
|
+
export declare class IntradayKlinesRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
saveAll(symbol: string, period: string, rows: TickFlowIntradayKlineRow[]): Promise<void>;
|
|
7
|
+
pruneToTradeDates(symbol: string, period: string, keepTradeDates: string[]): Promise<void>;
|
|
8
|
+
listBySymbol(symbol: string, period?: string): Promise<TickFlowIntradayKlineRow[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { klinesIntradaySchema } from "../schemas.js";
|
|
2
|
+
const KLINES_INTRADAY_TABLE = "klines_intraday";
|
|
3
|
+
export class IntradayKlinesRepository {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async saveAll(symbol, period, rows) {
|
|
9
|
+
if (rows.length === 0) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!(await this.db.hasTable(KLINES_INTRADAY_TABLE))) {
|
|
13
|
+
await this.db.createTable(KLINES_INTRADAY_TABLE, rows.map(toIntradayKlineRow), klinesIntradaySchema);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const table = await this.db.openTable(KLINES_INTRADAY_TABLE);
|
|
17
|
+
const tradeDates = uniqueTradeDates(rows);
|
|
18
|
+
await table.delete(buildDeleteByTradeDatesWhere(symbol, period, tradeDates));
|
|
19
|
+
await table.add(rows.map(toIntradayKlineRow));
|
|
20
|
+
}
|
|
21
|
+
async pruneToTradeDates(symbol, period, keepTradeDates) {
|
|
22
|
+
if (!(await this.db.hasTable(KLINES_INTRADAY_TABLE))) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const table = await this.db.openTable(KLINES_INTRADAY_TABLE);
|
|
26
|
+
const base = `symbol = '${escapeSqlString(symbol)}' and period = '${escapeSqlString(period)}'`;
|
|
27
|
+
if (keepTradeDates.length === 0) {
|
|
28
|
+
await table.delete(base);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await table.delete(`${base} and trade_date not in (${joinSqlStrings(keepTradeDates)})`);
|
|
32
|
+
}
|
|
33
|
+
async listBySymbol(symbol, period) {
|
|
34
|
+
if (!(await this.db.hasTable(KLINES_INTRADAY_TABLE))) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const filters = [`symbol = '${escapeSqlString(symbol)}'`];
|
|
38
|
+
if (period) {
|
|
39
|
+
filters.push(`period = '${escapeSqlString(period)}'`);
|
|
40
|
+
}
|
|
41
|
+
const table = await this.db.openTable(KLINES_INTRADAY_TABLE);
|
|
42
|
+
const rows = (await table
|
|
43
|
+
.query()
|
|
44
|
+
.where(filters.join(" and "))
|
|
45
|
+
.toArray());
|
|
46
|
+
return rows
|
|
47
|
+
.map((row) => ({
|
|
48
|
+
symbol: String(row.symbol),
|
|
49
|
+
period: String(row.period),
|
|
50
|
+
trade_date: String(row.trade_date),
|
|
51
|
+
trade_time: String(row.trade_time),
|
|
52
|
+
timestamp: Number(row.timestamp),
|
|
53
|
+
open: Number(row.open),
|
|
54
|
+
high: Number(row.high),
|
|
55
|
+
low: Number(row.low),
|
|
56
|
+
close: Number(row.close),
|
|
57
|
+
volume: Number(row.volume),
|
|
58
|
+
amount: Number(row.amount),
|
|
59
|
+
prev_close: Number(row.prev_close ?? 0),
|
|
60
|
+
open_interest: toNullableNumber(row.open_interest),
|
|
61
|
+
settlement_price: toNullableNumber(row.settlement_price),
|
|
62
|
+
}))
|
|
63
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function uniqueTradeDates(rows) {
|
|
67
|
+
return [...new Set(rows.map((row) => row.trade_date))];
|
|
68
|
+
}
|
|
69
|
+
function buildDeleteByTradeDatesWhere(symbol, period, tradeDates) {
|
|
70
|
+
const base = `symbol = '${escapeSqlString(symbol)}' and period = '${escapeSqlString(period)}'`;
|
|
71
|
+
if (tradeDates.length === 0) {
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
74
|
+
return `${base} and trade_date in (${joinSqlStrings(tradeDates)})`;
|
|
75
|
+
}
|
|
76
|
+
function joinSqlStrings(values) {
|
|
77
|
+
return values.map((value) => `'${escapeSqlString(value)}'`).join(", ");
|
|
78
|
+
}
|
|
79
|
+
function toIntradayKlineRow(row) {
|
|
80
|
+
return {
|
|
81
|
+
symbol: row.symbol,
|
|
82
|
+
period: row.period,
|
|
83
|
+
trade_date: row.trade_date,
|
|
84
|
+
trade_time: row.trade_time,
|
|
85
|
+
timestamp: row.timestamp,
|
|
86
|
+
open: row.open,
|
|
87
|
+
high: row.high,
|
|
88
|
+
low: row.low,
|
|
89
|
+
close: row.close,
|
|
90
|
+
volume: row.volume,
|
|
91
|
+
amount: row.amount,
|
|
92
|
+
prev_close: row.prev_close,
|
|
93
|
+
open_interest: row.open_interest,
|
|
94
|
+
settlement_price: row.settlement_price,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function toNullableNumber(value) {
|
|
98
|
+
return value == null ? null : Number(value);
|
|
99
|
+
}
|
|
100
|
+
function escapeSqlString(value) {
|
|
101
|
+
return value.replace(/'/g, "''");
|
|
102
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KeyLevelsHistoryEntry } from "../../types/domain.js";
|
|
2
|
+
import { Database } from "../db.js";
|
|
3
|
+
export declare class KeyLevelsHistoryRepository {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: Database);
|
|
6
|
+
saveDailySnapshot(entry: KeyLevelsHistoryEntry): Promise<void>;
|
|
7
|
+
listBySymbol(symbol: string, limit?: number): Promise<KeyLevelsHistoryEntry[]>;
|
|
8
|
+
listLatest(limit?: number): Promise<KeyLevelsHistoryEntry[]>;
|
|
9
|
+
}
|