@vizzor/cli 0.4.0 → 0.5.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/dist/index.js +672 -44
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -46,6 +46,10 @@ var init_schema = __esm({
|
|
|
46
46
|
walletData: z.number().default(600),
|
|
47
47
|
contractCode: z.number().default(86400)
|
|
48
48
|
}).default(() => ({ tokenInfo: 3600, marketData: 300, walletData: 600, contractCode: 86400 })),
|
|
49
|
+
database: z.object({
|
|
50
|
+
type: z.enum(["sqlite", "postgres"]).default("sqlite"),
|
|
51
|
+
url: z.string().optional()
|
|
52
|
+
}).default(() => ({ type: "sqlite" })),
|
|
49
53
|
discordToken: z.string().optional(),
|
|
50
54
|
discordGuildId: z.string().optional(),
|
|
51
55
|
telegramToken: z.string().optional()
|
|
@@ -643,12 +647,12 @@ var init_adapter = __esm({
|
|
|
643
647
|
toBlock: options?.toBlock,
|
|
644
648
|
args: options?.args
|
|
645
649
|
});
|
|
646
|
-
return logs.map((
|
|
647
|
-
eventName:
|
|
648
|
-
blockNumber:
|
|
649
|
-
transactionHash:
|
|
650
|
-
args:
|
|
651
|
-
logIndex:
|
|
650
|
+
return logs.map((log2) => ({
|
|
651
|
+
eventName: log2.eventName ?? eventName,
|
|
652
|
+
blockNumber: log2.blockNumber ?? 0n,
|
|
653
|
+
transactionHash: log2.transactionHash ?? "0x",
|
|
654
|
+
args: log2.args ?? {},
|
|
655
|
+
logIndex: log2.logIndex ?? 0
|
|
652
656
|
}));
|
|
653
657
|
}
|
|
654
658
|
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
@@ -2561,7 +2565,17 @@ async function handleConfigSet(key, value) {
|
|
|
2561
2565
|
}
|
|
2562
2566
|
await loadConfig();
|
|
2563
2567
|
const config2 = getConfig();
|
|
2564
|
-
const updatedConfig =
|
|
2568
|
+
const updatedConfig = JSON.parse(JSON.stringify(config2));
|
|
2569
|
+
if (key.includes(".")) {
|
|
2570
|
+
const [section, field] = key.split(".");
|
|
2571
|
+
if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
|
|
2572
|
+
updatedConfig[section] = {};
|
|
2573
|
+
}
|
|
2574
|
+
const parsed = field === "maxTokens" ? Number(value) : value;
|
|
2575
|
+
updatedConfig[section][field] = parsed;
|
|
2576
|
+
} else {
|
|
2577
|
+
updatedConfig[key] = value;
|
|
2578
|
+
}
|
|
2565
2579
|
writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
|
|
2566
2580
|
const displayValue = isSensitive ? maskKey(value) : value;
|
|
2567
2581
|
console.log(chalk6.green(`Set ${key} = ${displayValue}`));
|
|
@@ -5514,6 +5528,26 @@ function getDb() {
|
|
|
5514
5528
|
`);
|
|
5515
5529
|
return db;
|
|
5516
5530
|
}
|
|
5531
|
+
function getCached(key) {
|
|
5532
|
+
const row = getDb().prepare("SELECT value, expires_at FROM cache WHERE key = ?").get(key);
|
|
5533
|
+
if (!row) {
|
|
5534
|
+
return null;
|
|
5535
|
+
}
|
|
5536
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5537
|
+
if (row.expires_at <= now) {
|
|
5538
|
+
getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
|
|
5539
|
+
return null;
|
|
5540
|
+
}
|
|
5541
|
+
return JSON.parse(row.value);
|
|
5542
|
+
}
|
|
5543
|
+
function setCache(key, value, ttlSeconds) {
|
|
5544
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5545
|
+
const expiresAt = now + ttlSeconds;
|
|
5546
|
+
getDb().prepare(
|
|
5547
|
+
`INSERT OR REPLACE INTO cache (key, value, expires_at, created_at)
|
|
5548
|
+
VALUES (?, ?, ?, ?)`
|
|
5549
|
+
).run(key, JSON.stringify(value), expiresAt, now);
|
|
5550
|
+
}
|
|
5517
5551
|
var db;
|
|
5518
5552
|
var init_cache = __esm({
|
|
5519
5553
|
"src/data/cache.ts"() {
|
|
@@ -8048,40 +8082,18 @@ function handleBotValidate() {
|
|
|
8048
8082
|
const config2 = getConfig();
|
|
8049
8083
|
console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
|
|
8050
8084
|
const checks = [
|
|
8051
|
-
{
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
}
|
|
8057
|
-
{
|
|
8058
|
-
label: "Etherscan API Key",
|
|
8059
|
-
value: config2.etherscanApiKey,
|
|
8060
|
-
required: true,
|
|
8061
|
-
key: "etherscanApiKey"
|
|
8062
|
-
},
|
|
8063
|
-
{ label: "Discord Token", value: config2.discordToken, required: false, key: "discordToken" },
|
|
8064
|
-
{
|
|
8065
|
-
label: "Discord Guild ID",
|
|
8066
|
-
value: config2.discordGuildId,
|
|
8067
|
-
required: false,
|
|
8068
|
-
key: "discordGuildId"
|
|
8069
|
-
},
|
|
8070
|
-
{ label: "Telegram Token", value: config2.telegramToken, required: false, key: "telegramToken" },
|
|
8071
|
-
{
|
|
8072
|
-
label: "CryptoPanic Key",
|
|
8073
|
-
value: config2.cryptopanicApiKey,
|
|
8074
|
-
required: false,
|
|
8075
|
-
key: "cryptopanicApiKey"
|
|
8076
|
-
}
|
|
8085
|
+
{ label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
|
|
8086
|
+
{ label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
|
|
8087
|
+
{ label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
|
|
8088
|
+
{ label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
|
|
8089
|
+
{ label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
|
|
8090
|
+
{ label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
|
|
8077
8091
|
];
|
|
8078
8092
|
let allRequired = true;
|
|
8079
8093
|
for (const check of checks) {
|
|
8080
|
-
const
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
console.log(` ${status.padEnd(18)} ${check.label.padEnd(20)} ${masked}`);
|
|
8084
|
-
if (check.required && !set) allRequired = false;
|
|
8094
|
+
const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
|
|
8095
|
+
console.log(` ${status.padEnd(18)} ${check.label}`);
|
|
8096
|
+
if (check.required && !check.isSet) allRequired = false;
|
|
8085
8097
|
}
|
|
8086
8098
|
console.log();
|
|
8087
8099
|
if (hasKey(config2.discordToken)) {
|
|
@@ -8114,6 +8126,613 @@ var init_bot3 = __esm({
|
|
|
8114
8126
|
}
|
|
8115
8127
|
});
|
|
8116
8128
|
|
|
8129
|
+
// src/data/sqlite-store.ts
|
|
8130
|
+
function ensureAgentTables2() {
|
|
8131
|
+
const db2 = getDb();
|
|
8132
|
+
db2.exec(`
|
|
8133
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
8134
|
+
id TEXT PRIMARY KEY,
|
|
8135
|
+
name TEXT NOT NULL UNIQUE,
|
|
8136
|
+
strategy TEXT NOT NULL,
|
|
8137
|
+
pairs TEXT NOT NULL,
|
|
8138
|
+
interval_seconds INTEGER NOT NULL DEFAULT 60,
|
|
8139
|
+
created_at INTEGER NOT NULL,
|
|
8140
|
+
updated_at INTEGER NOT NULL
|
|
8141
|
+
)
|
|
8142
|
+
`);
|
|
8143
|
+
db2.exec(`
|
|
8144
|
+
CREATE TABLE IF NOT EXISTS agent_decisions (
|
|
8145
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8146
|
+
agent_id TEXT NOT NULL,
|
|
8147
|
+
symbol TEXT NOT NULL,
|
|
8148
|
+
action TEXT NOT NULL,
|
|
8149
|
+
confidence INTEGER NOT NULL,
|
|
8150
|
+
reasoning TEXT NOT NULL,
|
|
8151
|
+
signals TEXT NOT NULL,
|
|
8152
|
+
created_at INTEGER NOT NULL,
|
|
8153
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
|
8154
|
+
)
|
|
8155
|
+
`);
|
|
8156
|
+
}
|
|
8157
|
+
function rowToConfig(row) {
|
|
8158
|
+
return {
|
|
8159
|
+
id: row.id,
|
|
8160
|
+
name: row.name,
|
|
8161
|
+
strategy: row.strategy,
|
|
8162
|
+
pairs: JSON.parse(row.pairs),
|
|
8163
|
+
interval: row.interval_seconds,
|
|
8164
|
+
createdAt: row.created_at,
|
|
8165
|
+
updatedAt: row.updated_at
|
|
8166
|
+
};
|
|
8167
|
+
}
|
|
8168
|
+
var SqliteStore;
|
|
8169
|
+
var init_sqlite_store = __esm({
|
|
8170
|
+
"src/data/sqlite-store.ts"() {
|
|
8171
|
+
"use strict";
|
|
8172
|
+
init_cache();
|
|
8173
|
+
SqliteStore = class {
|
|
8174
|
+
// ---- Cache ---------------------------------------------------------------
|
|
8175
|
+
async getCached(key) {
|
|
8176
|
+
return getCached(key);
|
|
8177
|
+
}
|
|
8178
|
+
async setCache(key, value, ttlSeconds) {
|
|
8179
|
+
setCache(key, value, ttlSeconds);
|
|
8180
|
+
}
|
|
8181
|
+
// ---- Agents --------------------------------------------------------------
|
|
8182
|
+
async createAgent(config2) {
|
|
8183
|
+
ensureAgentTables2();
|
|
8184
|
+
getDb().prepare(
|
|
8185
|
+
`INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
|
|
8186
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
8187
|
+
).run(
|
|
8188
|
+
config2.id,
|
|
8189
|
+
config2.name,
|
|
8190
|
+
config2.strategy,
|
|
8191
|
+
JSON.stringify(config2.pairs),
|
|
8192
|
+
config2.interval,
|
|
8193
|
+
config2.createdAt,
|
|
8194
|
+
config2.updatedAt
|
|
8195
|
+
);
|
|
8196
|
+
return config2;
|
|
8197
|
+
}
|
|
8198
|
+
async listAgents() {
|
|
8199
|
+
ensureAgentTables2();
|
|
8200
|
+
const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
|
|
8201
|
+
return rows.map(rowToConfig);
|
|
8202
|
+
}
|
|
8203
|
+
async getAgentById(id) {
|
|
8204
|
+
ensureAgentTables2();
|
|
8205
|
+
const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
|
|
8206
|
+
return row ? rowToConfig(row) : null;
|
|
8207
|
+
}
|
|
8208
|
+
async getAgentByName(name) {
|
|
8209
|
+
ensureAgentTables2();
|
|
8210
|
+
const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
|
|
8211
|
+
return row ? rowToConfig(row) : null;
|
|
8212
|
+
}
|
|
8213
|
+
async deleteAgent(id) {
|
|
8214
|
+
ensureAgentTables2();
|
|
8215
|
+
const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
8216
|
+
getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
|
|
8217
|
+
return result.changes > 0;
|
|
8218
|
+
}
|
|
8219
|
+
async logDecision(result) {
|
|
8220
|
+
ensureAgentTables2();
|
|
8221
|
+
getDb().prepare(
|
|
8222
|
+
`INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
|
|
8223
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
8224
|
+
).run(
|
|
8225
|
+
result.agentId,
|
|
8226
|
+
result.symbol,
|
|
8227
|
+
result.decision.action,
|
|
8228
|
+
result.decision.confidence,
|
|
8229
|
+
JSON.stringify(result.decision.reasoning),
|
|
8230
|
+
JSON.stringify(result.signals),
|
|
8231
|
+
result.timestamp
|
|
8232
|
+
);
|
|
8233
|
+
}
|
|
8234
|
+
async getDecisions(agentId, limit) {
|
|
8235
|
+
ensureAgentTables2();
|
|
8236
|
+
const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
|
|
8237
|
+
return rows.map((r) => ({
|
|
8238
|
+
agentId: r.agent_id,
|
|
8239
|
+
symbol: r.symbol,
|
|
8240
|
+
timestamp: r.created_at,
|
|
8241
|
+
signals: JSON.parse(r.signals),
|
|
8242
|
+
decision: {
|
|
8243
|
+
action: r.action,
|
|
8244
|
+
confidence: r.confidence,
|
|
8245
|
+
reasoning: JSON.parse(r.reasoning)
|
|
8246
|
+
}
|
|
8247
|
+
}));
|
|
8248
|
+
}
|
|
8249
|
+
// ---- Time-series (no-op for SQLite) --------------------------------------
|
|
8250
|
+
async insertOHLCV(_records) {
|
|
8251
|
+
}
|
|
8252
|
+
async queryOHLCV(_symbol, _timeframe, _from, _to) {
|
|
8253
|
+
return [];
|
|
8254
|
+
}
|
|
8255
|
+
// ---- Predictions (no-op for SQLite) --------------------------------------
|
|
8256
|
+
async logPrediction(_prediction) {
|
|
8257
|
+
}
|
|
8258
|
+
async getPredictionAccuracy(_model, _days) {
|
|
8259
|
+
return {
|
|
8260
|
+
model: _model,
|
|
8261
|
+
totalPredictions: 0,
|
|
8262
|
+
correctPredictions: 0,
|
|
8263
|
+
accuracy: 0,
|
|
8264
|
+
byDirection: {
|
|
8265
|
+
up: { total: 0, correct: 0, accuracy: 0 },
|
|
8266
|
+
down: { total: 0, correct: 0, accuracy: 0 },
|
|
8267
|
+
sideways: { total: 0, correct: 0, accuracy: 0 }
|
|
8268
|
+
},
|
|
8269
|
+
period: `${_days}d`
|
|
8270
|
+
};
|
|
8271
|
+
}
|
|
8272
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
8273
|
+
async close() {
|
|
8274
|
+
}
|
|
8275
|
+
};
|
|
8276
|
+
}
|
|
8277
|
+
});
|
|
8278
|
+
|
|
8279
|
+
// src/data/postgres-store.ts
|
|
8280
|
+
var postgres_store_exports = {};
|
|
8281
|
+
__export(postgres_store_exports, {
|
|
8282
|
+
PostgresStore: () => PostgresStore
|
|
8283
|
+
});
|
|
8284
|
+
import pg from "pg";
|
|
8285
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8286
|
+
import { resolve as resolve2, dirname } from "path";
|
|
8287
|
+
import { fileURLToPath } from "url";
|
|
8288
|
+
var __filename, __dirname, PostgresStore;
|
|
8289
|
+
var init_postgres_store = __esm({
|
|
8290
|
+
"src/data/postgres-store.ts"() {
|
|
8291
|
+
"use strict";
|
|
8292
|
+
__filename = fileURLToPath(import.meta.url);
|
|
8293
|
+
__dirname = dirname(__filename);
|
|
8294
|
+
PostgresStore = class {
|
|
8295
|
+
pool;
|
|
8296
|
+
initialized = false;
|
|
8297
|
+
constructor(connectionUrl) {
|
|
8298
|
+
this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
|
|
8299
|
+
}
|
|
8300
|
+
async init() {
|
|
8301
|
+
if (this.initialized) return;
|
|
8302
|
+
const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
|
|
8303
|
+
const sql = readFileSync2(migrationPath, "utf-8");
|
|
8304
|
+
await this.pool.query(sql);
|
|
8305
|
+
this.initialized = true;
|
|
8306
|
+
}
|
|
8307
|
+
async query(text, params) {
|
|
8308
|
+
await this.init();
|
|
8309
|
+
return this.pool.query(text, params);
|
|
8310
|
+
}
|
|
8311
|
+
// ---- Cache ---------------------------------------------------------------
|
|
8312
|
+
async getCached(key) {
|
|
8313
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8314
|
+
const { rows } = await this.query(
|
|
8315
|
+
"SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
|
|
8316
|
+
[key, now]
|
|
8317
|
+
);
|
|
8318
|
+
if (rows.length === 0) return null;
|
|
8319
|
+
return rows[0].value;
|
|
8320
|
+
}
|
|
8321
|
+
async setCache(key, value, ttlSeconds) {
|
|
8322
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8323
|
+
const expiresAt = now + ttlSeconds;
|
|
8324
|
+
await this.query(
|
|
8325
|
+
`INSERT INTO cache (key, value, expires_at, created_at)
|
|
8326
|
+
VALUES ($1, $2, $3, $4)
|
|
8327
|
+
ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
|
|
8328
|
+
[key, JSON.stringify(value), expiresAt, now]
|
|
8329
|
+
);
|
|
8330
|
+
}
|
|
8331
|
+
// ---- Agents --------------------------------------------------------------
|
|
8332
|
+
async createAgent(config2) {
|
|
8333
|
+
await this.query(
|
|
8334
|
+
`INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
|
|
8335
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
8336
|
+
[
|
|
8337
|
+
config2.id,
|
|
8338
|
+
config2.name,
|
|
8339
|
+
config2.strategy,
|
|
8340
|
+
JSON.stringify(config2.pairs),
|
|
8341
|
+
config2.interval,
|
|
8342
|
+
config2.createdAt,
|
|
8343
|
+
config2.updatedAt
|
|
8344
|
+
]
|
|
8345
|
+
);
|
|
8346
|
+
return config2;
|
|
8347
|
+
}
|
|
8348
|
+
async listAgents() {
|
|
8349
|
+
const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
|
|
8350
|
+
return rows.map((r) => ({
|
|
8351
|
+
id: r.id,
|
|
8352
|
+
name: r.name,
|
|
8353
|
+
strategy: r.strategy,
|
|
8354
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
8355
|
+
interval: r.interval_seconds,
|
|
8356
|
+
createdAt: Number(r.created_at),
|
|
8357
|
+
updatedAt: Number(r.updated_at)
|
|
8358
|
+
}));
|
|
8359
|
+
}
|
|
8360
|
+
async getAgentById(id) {
|
|
8361
|
+
const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
|
|
8362
|
+
if (rows.length === 0) return null;
|
|
8363
|
+
const r = rows[0];
|
|
8364
|
+
return {
|
|
8365
|
+
id: r.id,
|
|
8366
|
+
name: r.name,
|
|
8367
|
+
strategy: r.strategy,
|
|
8368
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
8369
|
+
interval: r.interval_seconds,
|
|
8370
|
+
createdAt: Number(r.created_at),
|
|
8371
|
+
updatedAt: Number(r.updated_at)
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
async getAgentByName(name) {
|
|
8375
|
+
const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
|
|
8376
|
+
if (rows.length === 0) return null;
|
|
8377
|
+
const r = rows[0];
|
|
8378
|
+
return {
|
|
8379
|
+
id: r.id,
|
|
8380
|
+
name: r.name,
|
|
8381
|
+
strategy: r.strategy,
|
|
8382
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
8383
|
+
interval: r.interval_seconds,
|
|
8384
|
+
createdAt: Number(r.created_at),
|
|
8385
|
+
updatedAt: Number(r.updated_at)
|
|
8386
|
+
};
|
|
8387
|
+
}
|
|
8388
|
+
async deleteAgent(id) {
|
|
8389
|
+
const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
|
|
8390
|
+
return (result.rowCount ?? 0) > 0;
|
|
8391
|
+
}
|
|
8392
|
+
async logDecision(result) {
|
|
8393
|
+
await this.query(
|
|
8394
|
+
`INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
|
|
8395
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
8396
|
+
[
|
|
8397
|
+
result.agentId,
|
|
8398
|
+
result.symbol,
|
|
8399
|
+
result.decision.action,
|
|
8400
|
+
result.decision.confidence,
|
|
8401
|
+
JSON.stringify(result.decision.reasoning),
|
|
8402
|
+
JSON.stringify(result.signals),
|
|
8403
|
+
result.timestamp
|
|
8404
|
+
]
|
|
8405
|
+
);
|
|
8406
|
+
}
|
|
8407
|
+
async getDecisions(agentId, limit) {
|
|
8408
|
+
const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
|
|
8409
|
+
agentId,
|
|
8410
|
+
limit
|
|
8411
|
+
]);
|
|
8412
|
+
return rows.map((r) => ({
|
|
8413
|
+
agentId: r.agent_id,
|
|
8414
|
+
symbol: r.symbol,
|
|
8415
|
+
timestamp: Number(r.created_at),
|
|
8416
|
+
signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
|
|
8417
|
+
decision: {
|
|
8418
|
+
action: r.action,
|
|
8419
|
+
confidence: r.confidence,
|
|
8420
|
+
reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
|
|
8421
|
+
}
|
|
8422
|
+
}));
|
|
8423
|
+
}
|
|
8424
|
+
// ---- Time-series ---------------------------------------------------------
|
|
8425
|
+
async insertOHLCV(records) {
|
|
8426
|
+
if (records.length === 0) return;
|
|
8427
|
+
const values = [];
|
|
8428
|
+
const placeholders = [];
|
|
8429
|
+
for (let i = 0; i < records.length; i++) {
|
|
8430
|
+
const r = records[i];
|
|
8431
|
+
const offset = i * 9;
|
|
8432
|
+
placeholders.push(
|
|
8433
|
+
`($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
|
|
8434
|
+
);
|
|
8435
|
+
values.push(
|
|
8436
|
+
new Date(r.time).toISOString(),
|
|
8437
|
+
r.symbol,
|
|
8438
|
+
r.timeframe,
|
|
8439
|
+
r.open,
|
|
8440
|
+
r.high,
|
|
8441
|
+
r.low,
|
|
8442
|
+
r.close,
|
|
8443
|
+
r.volume,
|
|
8444
|
+
r.trades
|
|
8445
|
+
);
|
|
8446
|
+
}
|
|
8447
|
+
await this.query(
|
|
8448
|
+
`INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
|
|
8449
|
+
VALUES ${placeholders.join(", ")}
|
|
8450
|
+
ON CONFLICT (symbol, timeframe, time) DO UPDATE
|
|
8451
|
+
SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
|
|
8452
|
+
close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
|
|
8453
|
+
values
|
|
8454
|
+
);
|
|
8455
|
+
}
|
|
8456
|
+
async queryOHLCV(symbol, timeframe, from, to) {
|
|
8457
|
+
const { rows } = await this.query(
|
|
8458
|
+
`SELECT * FROM ohlcv
|
|
8459
|
+
WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
|
|
8460
|
+
ORDER BY time ASC`,
|
|
8461
|
+
[symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
|
|
8462
|
+
);
|
|
8463
|
+
return rows.map((r) => ({
|
|
8464
|
+
time: new Date(r.time).getTime(),
|
|
8465
|
+
symbol: r.symbol,
|
|
8466
|
+
timeframe: r.timeframe,
|
|
8467
|
+
open: r.open,
|
|
8468
|
+
high: r.high,
|
|
8469
|
+
low: r.low,
|
|
8470
|
+
close: r.close,
|
|
8471
|
+
volume: r.volume,
|
|
8472
|
+
trades: r.trades
|
|
8473
|
+
}));
|
|
8474
|
+
}
|
|
8475
|
+
// ---- Predictions ---------------------------------------------------------
|
|
8476
|
+
async logPrediction(prediction) {
|
|
8477
|
+
await this.query(
|
|
8478
|
+
`INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
|
|
8479
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
8480
|
+
[
|
|
8481
|
+
prediction.symbol,
|
|
8482
|
+
prediction.model,
|
|
8483
|
+
prediction.direction,
|
|
8484
|
+
prediction.probability,
|
|
8485
|
+
prediction.horizon,
|
|
8486
|
+
JSON.stringify(prediction.features),
|
|
8487
|
+
new Date(prediction.predictedAt).toISOString()
|
|
8488
|
+
]
|
|
8489
|
+
);
|
|
8490
|
+
}
|
|
8491
|
+
async getPredictionAccuracy(model, days) {
|
|
8492
|
+
const since = new Date(Date.now() - days * 864e5).toISOString();
|
|
8493
|
+
const { rows: totals } = await this.query(
|
|
8494
|
+
`SELECT direction,
|
|
8495
|
+
COUNT(*)::text AS total,
|
|
8496
|
+
COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
|
|
8497
|
+
FROM predictions
|
|
8498
|
+
WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
|
|
8499
|
+
GROUP BY direction`,
|
|
8500
|
+
[model, since]
|
|
8501
|
+
);
|
|
8502
|
+
const byDir = {
|
|
8503
|
+
up: { total: 0, correct: 0, accuracy: 0 },
|
|
8504
|
+
down: { total: 0, correct: 0, accuracy: 0 },
|
|
8505
|
+
sideways: { total: 0, correct: 0, accuracy: 0 }
|
|
8506
|
+
};
|
|
8507
|
+
let totalAll = 0;
|
|
8508
|
+
let correctAll = 0;
|
|
8509
|
+
for (const row of totals) {
|
|
8510
|
+
const t = parseInt(row.total, 10);
|
|
8511
|
+
const c = parseInt(row.correct, 10);
|
|
8512
|
+
totalAll += t;
|
|
8513
|
+
correctAll += c;
|
|
8514
|
+
const dir = row.direction;
|
|
8515
|
+
if (byDir[dir]) {
|
|
8516
|
+
byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
|
|
8517
|
+
}
|
|
8518
|
+
}
|
|
8519
|
+
return {
|
|
8520
|
+
model,
|
|
8521
|
+
totalPredictions: totalAll,
|
|
8522
|
+
correctPredictions: correctAll,
|
|
8523
|
+
accuracy: totalAll > 0 ? correctAll / totalAll : 0,
|
|
8524
|
+
byDirection: byDir,
|
|
8525
|
+
period: `${days}d`
|
|
8526
|
+
};
|
|
8527
|
+
}
|
|
8528
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
8529
|
+
async close() {
|
|
8530
|
+
await this.pool.end();
|
|
8531
|
+
}
|
|
8532
|
+
};
|
|
8533
|
+
}
|
|
8534
|
+
});
|
|
8535
|
+
|
|
8536
|
+
// src/data/store-factory.ts
|
|
8537
|
+
async function getStore(config2) {
|
|
8538
|
+
if (instance) return instance;
|
|
8539
|
+
if (config2.database?.type === "postgres" && config2.database.url) {
|
|
8540
|
+
const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
|
|
8541
|
+
instance = new PostgresStore2(config2.database.url);
|
|
8542
|
+
} else {
|
|
8543
|
+
instance = new SqliteStore();
|
|
8544
|
+
}
|
|
8545
|
+
return instance;
|
|
8546
|
+
}
|
|
8547
|
+
var instance;
|
|
8548
|
+
var init_store_factory = __esm({
|
|
8549
|
+
"src/data/store-factory.ts"() {
|
|
8550
|
+
"use strict";
|
|
8551
|
+
init_sqlite_store();
|
|
8552
|
+
instance = null;
|
|
8553
|
+
}
|
|
8554
|
+
});
|
|
8555
|
+
|
|
8556
|
+
// src/data/collector.ts
|
|
8557
|
+
var log, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
|
|
8558
|
+
var init_collector = __esm({
|
|
8559
|
+
"src/data/collector.ts"() {
|
|
8560
|
+
"use strict";
|
|
8561
|
+
init_binance();
|
|
8562
|
+
init_logger();
|
|
8563
|
+
log = createLogger("collector");
|
|
8564
|
+
MAJOR_SYMBOLS = [
|
|
8565
|
+
"BTC",
|
|
8566
|
+
"ETH",
|
|
8567
|
+
"SOL",
|
|
8568
|
+
"BNB",
|
|
8569
|
+
"XRP",
|
|
8570
|
+
"ADA",
|
|
8571
|
+
"DOGE",
|
|
8572
|
+
"DOT",
|
|
8573
|
+
"AVAX",
|
|
8574
|
+
"MATIC",
|
|
8575
|
+
"LINK",
|
|
8576
|
+
"UNI",
|
|
8577
|
+
"ATOM",
|
|
8578
|
+
"NEAR",
|
|
8579
|
+
"ARB",
|
|
8580
|
+
"OP",
|
|
8581
|
+
"SUI",
|
|
8582
|
+
"APT",
|
|
8583
|
+
"PEPE",
|
|
8584
|
+
"SHIB",
|
|
8585
|
+
"FLOKI",
|
|
8586
|
+
"BONK",
|
|
8587
|
+
"WIF"
|
|
8588
|
+
];
|
|
8589
|
+
TIMEFRAMES = ["1h", "4h"];
|
|
8590
|
+
COLLECTION_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8591
|
+
DataCollector = class {
|
|
8592
|
+
constructor(store, symbols = MAJOR_SYMBOLS, intervalMs = COLLECTION_INTERVAL_MS) {
|
|
8593
|
+
this.store = store;
|
|
8594
|
+
this.symbols = symbols;
|
|
8595
|
+
this.intervalMs = intervalMs;
|
|
8596
|
+
this.status.symbols = symbols;
|
|
8597
|
+
this.status.intervalMs = intervalMs;
|
|
8598
|
+
}
|
|
8599
|
+
timer = null;
|
|
8600
|
+
status = {
|
|
8601
|
+
running: false,
|
|
8602
|
+
symbols: MAJOR_SYMBOLS,
|
|
8603
|
+
timeframes: [...TIMEFRAMES],
|
|
8604
|
+
intervalMs: COLLECTION_INTERVAL_MS,
|
|
8605
|
+
lastRun: null,
|
|
8606
|
+
totalRecords: 0,
|
|
8607
|
+
errors: 0
|
|
8608
|
+
};
|
|
8609
|
+
getStatus() {
|
|
8610
|
+
return { ...this.status };
|
|
8611
|
+
}
|
|
8612
|
+
start() {
|
|
8613
|
+
if (this.status.running) {
|
|
8614
|
+
log.warn("Collector already running");
|
|
8615
|
+
return;
|
|
8616
|
+
}
|
|
8617
|
+
log.info(
|
|
8618
|
+
`Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
|
|
8619
|
+
);
|
|
8620
|
+
this.status.running = true;
|
|
8621
|
+
void this.collectAll();
|
|
8622
|
+
this.timer = setInterval(() => void this.collectAll(), this.intervalMs);
|
|
8623
|
+
}
|
|
8624
|
+
stop() {
|
|
8625
|
+
if (this.timer) {
|
|
8626
|
+
clearInterval(this.timer);
|
|
8627
|
+
this.timer = null;
|
|
8628
|
+
}
|
|
8629
|
+
this.status.running = false;
|
|
8630
|
+
log.info("Data collector stopped");
|
|
8631
|
+
}
|
|
8632
|
+
async collectAll() {
|
|
8633
|
+
log.info("Collection cycle starting");
|
|
8634
|
+
const start = Date.now();
|
|
8635
|
+
for (const timeframe of TIMEFRAMES) {
|
|
8636
|
+
for (const symbol of this.symbols) {
|
|
8637
|
+
try {
|
|
8638
|
+
await this.collectSymbol(symbol, timeframe);
|
|
8639
|
+
} catch (err) {
|
|
8640
|
+
this.status.errors++;
|
|
8641
|
+
log.error(
|
|
8642
|
+
`Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
|
|
8643
|
+
);
|
|
8644
|
+
}
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
this.status.lastRun = Date.now();
|
|
8648
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
8649
|
+
log.info(
|
|
8650
|
+
`Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
|
|
8651
|
+
);
|
|
8652
|
+
}
|
|
8653
|
+
async collectSymbol(symbol, timeframe) {
|
|
8654
|
+
const klines = await fetchKlines(symbol, timeframe, 100);
|
|
8655
|
+
const records = klines.map((k) => ({
|
|
8656
|
+
time: k.openTime,
|
|
8657
|
+
symbol: symbol.toUpperCase(),
|
|
8658
|
+
timeframe,
|
|
8659
|
+
open: k.open,
|
|
8660
|
+
high: k.high,
|
|
8661
|
+
low: k.low,
|
|
8662
|
+
close: k.close,
|
|
8663
|
+
volume: k.volume,
|
|
8664
|
+
trades: k.trades
|
|
8665
|
+
}));
|
|
8666
|
+
await this.store.insertOHLCV(records);
|
|
8667
|
+
this.status.totalRecords += records.length;
|
|
8668
|
+
}
|
|
8669
|
+
};
|
|
8670
|
+
}
|
|
8671
|
+
});
|
|
8672
|
+
|
|
8673
|
+
// src/cli/commands/collect.ts
|
|
8674
|
+
var collect_exports = {};
|
|
8675
|
+
__export(collect_exports, {
|
|
8676
|
+
handleCollectStart: () => handleCollectStart,
|
|
8677
|
+
handleCollectStatus: () => handleCollectStatus
|
|
8678
|
+
});
|
|
8679
|
+
import chalk8 from "chalk";
|
|
8680
|
+
async function handleCollectStart(options) {
|
|
8681
|
+
const config2 = getConfig();
|
|
8682
|
+
if (config2.database?.type !== "postgres" || !config2.database.url) {
|
|
8683
|
+
console.log(
|
|
8684
|
+
chalk8.yellow(
|
|
8685
|
+
'Data collection requires PostgreSQL + TimescaleDB.\nSet database.type = "postgres" and database.url in your config.\n\nExample:\n vizzor config set database.type postgres\n vizzor config set database.url postgres://user:pass@localhost:5432/vizzor'
|
|
8686
|
+
)
|
|
8687
|
+
);
|
|
8688
|
+
return;
|
|
8689
|
+
}
|
|
8690
|
+
const store = await getStore(config2);
|
|
8691
|
+
const symbols = options.symbols ? options.symbols.split(",").map((s) => s.trim().toUpperCase()) : void 0;
|
|
8692
|
+
const intervalMs = options.interval ? options.interval * 1e3 : void 0;
|
|
8693
|
+
collector = new DataCollector(store, symbols, intervalMs);
|
|
8694
|
+
collector.start();
|
|
8695
|
+
console.log(chalk8.green("Data collector started"));
|
|
8696
|
+
const status = collector.getStatus();
|
|
8697
|
+
console.log(` Symbols: ${status.symbols.length} pairs`);
|
|
8698
|
+
console.log(` Timeframes: ${status.timeframes.join(", ")}`);
|
|
8699
|
+
console.log(` Interval: ${status.intervalMs / 1e3}s`);
|
|
8700
|
+
console.log(chalk8.dim("\nPress Ctrl+C to stop"));
|
|
8701
|
+
process.on("SIGINT", () => {
|
|
8702
|
+
collector?.stop();
|
|
8703
|
+
void store.close();
|
|
8704
|
+
process.exit(0);
|
|
8705
|
+
});
|
|
8706
|
+
await new Promise(() => {
|
|
8707
|
+
});
|
|
8708
|
+
}
|
|
8709
|
+
function handleCollectStatus() {
|
|
8710
|
+
if (!collector) {
|
|
8711
|
+
console.log(chalk8.yellow("No collector running in this process."));
|
|
8712
|
+
console.log(chalk8.dim("Start with: vizzor collect start"));
|
|
8713
|
+
return;
|
|
8714
|
+
}
|
|
8715
|
+
const status = collector.getStatus();
|
|
8716
|
+
console.log(chalk8.bold("Data Collector Status"));
|
|
8717
|
+
console.log(` Running: ${status.running ? chalk8.green("yes") : chalk8.red("no")}`);
|
|
8718
|
+
console.log(` Symbols: ${status.symbols.length} pairs`);
|
|
8719
|
+
console.log(` Timeframes: ${status.timeframes.join(", ")}`);
|
|
8720
|
+
console.log(` Interval: ${status.intervalMs / 1e3}s`);
|
|
8721
|
+
console.log(` Last run: ${status.lastRun ? new Date(status.lastRun).toISOString() : "never"}`);
|
|
8722
|
+
console.log(` Total records: ${status.totalRecords.toLocaleString()}`);
|
|
8723
|
+
console.log(` Errors: ${status.errors}`);
|
|
8724
|
+
}
|
|
8725
|
+
var collector;
|
|
8726
|
+
var init_collect = __esm({
|
|
8727
|
+
"src/cli/commands/collect.ts"() {
|
|
8728
|
+
"use strict";
|
|
8729
|
+
init_loader();
|
|
8730
|
+
init_store_factory();
|
|
8731
|
+
init_collector();
|
|
8732
|
+
collector = null;
|
|
8733
|
+
}
|
|
8734
|
+
});
|
|
8735
|
+
|
|
8117
8736
|
// src/tui/components/status-bar.tsx
|
|
8118
8737
|
import React, { useState, useEffect } from "react";
|
|
8119
8738
|
import { Box, Text, Spacer } from "ink";
|
|
@@ -9858,12 +10477,12 @@ var init_app = __esm({
|
|
|
9858
10477
|
// src/index.ts
|
|
9859
10478
|
init_loader();
|
|
9860
10479
|
import { Command } from "commander";
|
|
9861
|
-
import { readFileSync as
|
|
9862
|
-
import { fileURLToPath } from "url";
|
|
9863
|
-
import { dirname, resolve as
|
|
9864
|
-
var
|
|
9865
|
-
var
|
|
9866
|
-
var pkg = JSON.parse(
|
|
10480
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10481
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10482
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
10483
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
10484
|
+
var __dirname2 = dirname2(__filename2);
|
|
10485
|
+
var pkg = JSON.parse(readFileSync3(resolve3(__dirname2, "..", "package.json"), "utf-8"));
|
|
9867
10486
|
var program = new Command().name("vizzor").description("Crypto chronovisor \u2014 AI-powered on-chain intelligence").version(pkg.version);
|
|
9868
10487
|
program.hook("preAction", async () => {
|
|
9869
10488
|
await loadConfig();
|
|
@@ -9911,6 +10530,15 @@ botCmd.command("validate").description("Check bot token configuration").action(a
|
|
|
9911
10530
|
const { handleBotValidate: handleBotValidate2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
|
|
9912
10531
|
handleBotValidate2();
|
|
9913
10532
|
});
|
|
10533
|
+
var collectCmd = program.command("collect").description("Data collection pipeline");
|
|
10534
|
+
collectCmd.command("start").description("Start background OHLCV data collection (requires PostgreSQL)").option("--symbols <symbols>", "Comma-separated symbols to collect (default: 23 major pairs)").option("--interval <seconds>", "Collection interval in seconds (default: 300)", parseInt).action(async (options) => {
|
|
10535
|
+
const { handleCollectStart: handleCollectStart2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
|
|
10536
|
+
await handleCollectStart2(options);
|
|
10537
|
+
});
|
|
10538
|
+
collectCmd.command("status").description("Show data collection status").action(async () => {
|
|
10539
|
+
const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
|
|
10540
|
+
handleCollectStatus2();
|
|
10541
|
+
});
|
|
9914
10542
|
var args = process.argv.slice(2);
|
|
9915
10543
|
if (args.length === 0) {
|
|
9916
10544
|
await loadConfig();
|