@vizzor/cli 0.4.0 → 0.6.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 +924 -77
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -46,6 +46,19 @@ 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" })),
|
|
53
|
+
ml: z.object({
|
|
54
|
+
enabled: z.boolean().default(false),
|
|
55
|
+
sidecarUrl: z.string().default("http://localhost:8000"),
|
|
56
|
+
fallbackToRules: z.boolean().default(true)
|
|
57
|
+
}).default(() => ({
|
|
58
|
+
enabled: false,
|
|
59
|
+
sidecarUrl: "http://localhost:8000",
|
|
60
|
+
fallbackToRules: true
|
|
61
|
+
})),
|
|
49
62
|
discordToken: z.string().optional(),
|
|
50
63
|
discordGuildId: z.string().optional(),
|
|
51
64
|
telegramToken: z.string().optional()
|
|
@@ -643,12 +656,12 @@ var init_adapter = __esm({
|
|
|
643
656
|
toBlock: options?.toBlock,
|
|
644
657
|
args: options?.args
|
|
645
658
|
});
|
|
646
|
-
return logs.map((
|
|
647
|
-
eventName:
|
|
648
|
-
blockNumber:
|
|
649
|
-
transactionHash:
|
|
650
|
-
args:
|
|
651
|
-
logIndex:
|
|
659
|
+
return logs.map((log3) => ({
|
|
660
|
+
eventName: log3.eventName ?? eventName,
|
|
661
|
+
blockNumber: log3.blockNumber ?? 0n,
|
|
662
|
+
transactionHash: log3.transactionHash ?? "0x",
|
|
663
|
+
args: log3.args ?? {},
|
|
664
|
+
logIndex: log3.logIndex ?? 0
|
|
652
665
|
}));
|
|
653
666
|
}
|
|
654
667
|
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
@@ -1710,6 +1723,111 @@ var init_fear_greed = __esm({
|
|
|
1710
1723
|
}
|
|
1711
1724
|
});
|
|
1712
1725
|
|
|
1726
|
+
// src/utils/logger.ts
|
|
1727
|
+
import pino from "pino";
|
|
1728
|
+
function createLogger(name) {
|
|
1729
|
+
const isDev = process.env["NODE_ENV"] !== "production";
|
|
1730
|
+
if (isDev) {
|
|
1731
|
+
return pino({
|
|
1732
|
+
name,
|
|
1733
|
+
level,
|
|
1734
|
+
transport: {
|
|
1735
|
+
target: "pino-pretty",
|
|
1736
|
+
options: {
|
|
1737
|
+
colorize: true
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
return pino({ name, level });
|
|
1743
|
+
}
|
|
1744
|
+
var level;
|
|
1745
|
+
var init_logger = __esm({
|
|
1746
|
+
"src/utils/logger.ts"() {
|
|
1747
|
+
"use strict";
|
|
1748
|
+
level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
// src/ml/client.ts
|
|
1753
|
+
function getMLClient() {
|
|
1754
|
+
return mlClient;
|
|
1755
|
+
}
|
|
1756
|
+
var log, mlClient;
|
|
1757
|
+
var init_client = __esm({
|
|
1758
|
+
"src/ml/client.ts"() {
|
|
1759
|
+
"use strict";
|
|
1760
|
+
init_logger();
|
|
1761
|
+
log = createLogger("ml-client");
|
|
1762
|
+
mlClient = null;
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
// src/ml/feature-engineer.ts
|
|
1767
|
+
async function buildFeatureVector(symbol) {
|
|
1768
|
+
const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
|
|
1769
|
+
analyzeTechnicals(symbol, "4h"),
|
|
1770
|
+
fetchFundingRate(symbol),
|
|
1771
|
+
fetchTickerPrice(symbol),
|
|
1772
|
+
fetchFearGreedIndex(1),
|
|
1773
|
+
fetchKlines(symbol, "4h", 100)
|
|
1774
|
+
]);
|
|
1775
|
+
const indicators = ta.status === "fulfilled" ? ta.value.indicators : null;
|
|
1776
|
+
const funding = fundingResult.status === "fulfilled" ? fundingResult.value : null;
|
|
1777
|
+
const ticker = tickerResult.status === "fulfilled" ? tickerResult.value : null;
|
|
1778
|
+
const fg = fgResult.status === "fulfilled" ? fgResult.value : null;
|
|
1779
|
+
const candles = klines.status === "fulfilled" ? klines.value : [];
|
|
1780
|
+
let rsiSlope = 0;
|
|
1781
|
+
if (candles.length >= 17) {
|
|
1782
|
+
const closes = candles.map((k) => k.close);
|
|
1783
|
+
const recentRsi = calculateRSI(closes, 14);
|
|
1784
|
+
const olderCloses = closes.slice(0, -3);
|
|
1785
|
+
const olderRsi = calculateRSI(olderCloses, 14);
|
|
1786
|
+
if (recentRsi !== null && olderRsi !== null) {
|
|
1787
|
+
rsiSlope = recentRsi - olderRsi;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
let volumeRatio = 1;
|
|
1791
|
+
if (candles.length >= 21) {
|
|
1792
|
+
const currentVolume = candles[candles.length - 1].volume;
|
|
1793
|
+
const avgVolume = candles.slice(-21, -1).reduce((sum, k) => sum + k.volume, 0) / 20;
|
|
1794
|
+
volumeRatio = avgVolume > 0 ? currentVolume / avgVolume : 1;
|
|
1795
|
+
}
|
|
1796
|
+
const price = ticker?.price ?? candles[candles.length - 1]?.close ?? 0;
|
|
1797
|
+
const ema12 = indicators?.ema12 ?? 0;
|
|
1798
|
+
const ema26 = indicators?.ema26 ?? 0;
|
|
1799
|
+
const emaCrossoverPct = price > 0 ? (ema12 - ema26) / price * 100 : 0;
|
|
1800
|
+
const atr = indicators?.atr ?? 0;
|
|
1801
|
+
const atrPct = price > 0 ? atr / price * 100 : 0;
|
|
1802
|
+
return {
|
|
1803
|
+
rsi: indicators?.rsi ?? 50,
|
|
1804
|
+
macdHistogram: indicators?.macd?.histogram ?? 0,
|
|
1805
|
+
bollingerPercentB: indicators?.bollingerBands?.percentB ?? 0.5,
|
|
1806
|
+
ema12,
|
|
1807
|
+
ema26,
|
|
1808
|
+
atr,
|
|
1809
|
+
obv: indicators?.obv ?? 0,
|
|
1810
|
+
fundingRate: funding?.fundingRate ?? 0,
|
|
1811
|
+
fearGreed: fg?.current.value ?? 50,
|
|
1812
|
+
priceChange24h: ticker?.change24h ?? 0,
|
|
1813
|
+
rsiSlope,
|
|
1814
|
+
volumeRatio,
|
|
1815
|
+
emaCrossoverPct,
|
|
1816
|
+
atrPct,
|
|
1817
|
+
symbol: symbol.toUpperCase(),
|
|
1818
|
+
timestamp: Date.now()
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
var init_feature_engineer = __esm({
|
|
1822
|
+
"src/ml/feature-engineer.ts"() {
|
|
1823
|
+
"use strict";
|
|
1824
|
+
init_technical_analysis();
|
|
1825
|
+
init_binance();
|
|
1826
|
+
init_fear_greed();
|
|
1827
|
+
init_indicators();
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1713
1831
|
// src/core/trends/predictor.ts
|
|
1714
1832
|
async function generatePrediction(symbol) {
|
|
1715
1833
|
const reasoning = [];
|
|
@@ -1830,7 +1948,7 @@ async function generatePrediction(symbol) {
|
|
|
1830
1948
|
const negativeCount = signalValues.filter((v) => v < 0).length;
|
|
1831
1949
|
const agreement = Math.max(positiveCount, negativeCount) / Math.max(1, positiveCount + negativeCount);
|
|
1832
1950
|
const confidence = Math.round(Math.min(95, completeness / 5 * agreement * 100));
|
|
1833
|
-
|
|
1951
|
+
const rulePrediction = {
|
|
1834
1952
|
symbol: symbol.toUpperCase(),
|
|
1835
1953
|
direction,
|
|
1836
1954
|
confidence,
|
|
@@ -1840,6 +1958,34 @@ async function generatePrediction(symbol) {
|
|
|
1840
1958
|
composite: Math.round(composite),
|
|
1841
1959
|
disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
|
|
1842
1960
|
};
|
|
1961
|
+
const mlClient2 = getMLClient();
|
|
1962
|
+
if (mlClient2) {
|
|
1963
|
+
try {
|
|
1964
|
+
const features = await buildFeatureVector(symbol);
|
|
1965
|
+
const mlPred = await mlClient2.predict(features);
|
|
1966
|
+
if (mlPred) {
|
|
1967
|
+
return mergePredictions(rulePrediction, mlPred);
|
|
1968
|
+
}
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return rulePrediction;
|
|
1973
|
+
}
|
|
1974
|
+
function mergePredictions(rule, ml) {
|
|
1975
|
+
const mlComposite = ml.direction === "up" ? ml.probability * 100 : ml.direction === "down" ? -(ml.probability * 100) : 0;
|
|
1976
|
+
const mergedComposite = Math.round(rule.composite * 0.4 + mlComposite * 0.6);
|
|
1977
|
+
const mergedDirection = mergedComposite > 15 ? "up" : mergedComposite < -15 ? "down" : "sideways";
|
|
1978
|
+
const mergedConfidence = Math.round(Math.min(95, rule.confidence * 0.4 + ml.confidence * 0.6));
|
|
1979
|
+
return {
|
|
1980
|
+
...rule,
|
|
1981
|
+
direction: mergedDirection,
|
|
1982
|
+
confidence: mergedConfidence,
|
|
1983
|
+
composite: mergedComposite,
|
|
1984
|
+
reasoning: [
|
|
1985
|
+
...rule.reasoning,
|
|
1986
|
+
`ML (${ml.model}): ${ml.direction} with ${(ml.probability * 100).toFixed(1)}% probability (horizon: ${ml.horizon})`
|
|
1987
|
+
]
|
|
1988
|
+
};
|
|
1843
1989
|
}
|
|
1844
1990
|
var WEIGHTS2;
|
|
1845
1991
|
var init_predictor = __esm({
|
|
@@ -1849,6 +1995,8 @@ var init_predictor = __esm({
|
|
|
1849
1995
|
init_sentiment();
|
|
1850
1996
|
init_fear_greed();
|
|
1851
1997
|
init_binance();
|
|
1998
|
+
init_client();
|
|
1999
|
+
init_feature_engineer();
|
|
1852
2000
|
WEIGHTS2 = {
|
|
1853
2001
|
technical: 40,
|
|
1854
2002
|
sentiment: 20,
|
|
@@ -2561,7 +2709,17 @@ async function handleConfigSet(key, value) {
|
|
|
2561
2709
|
}
|
|
2562
2710
|
await loadConfig();
|
|
2563
2711
|
const config2 = getConfig();
|
|
2564
|
-
const updatedConfig =
|
|
2712
|
+
const updatedConfig = JSON.parse(JSON.stringify(config2));
|
|
2713
|
+
if (key.includes(".")) {
|
|
2714
|
+
const [section, field] = key.split(".");
|
|
2715
|
+
if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
|
|
2716
|
+
updatedConfig[section] = {};
|
|
2717
|
+
}
|
|
2718
|
+
const parsed = field === "maxTokens" ? Number(value) : value;
|
|
2719
|
+
updatedConfig[section][field] = parsed;
|
|
2720
|
+
} else {
|
|
2721
|
+
updatedConfig[key] = value;
|
|
2722
|
+
}
|
|
2565
2723
|
writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
|
|
2566
2724
|
const displayValue = isSensitive ? maskKey(value) : value;
|
|
2567
2725
|
console.log(chalk6.green(`Set ${key} = ${displayValue}`));
|
|
@@ -5219,7 +5377,7 @@ async function analyze(systemPrompt, userMessage, tools) {
|
|
|
5219
5377
|
return p.analyze(systemPrompt, userMessage, tools, toolHandler);
|
|
5220
5378
|
}
|
|
5221
5379
|
var provider, config, toolHandler;
|
|
5222
|
-
var
|
|
5380
|
+
var init_client2 = __esm({
|
|
5223
5381
|
"src/ai/client.ts"() {
|
|
5224
5382
|
"use strict";
|
|
5225
5383
|
init_registry2();
|
|
@@ -5514,6 +5672,26 @@ function getDb() {
|
|
|
5514
5672
|
`);
|
|
5515
5673
|
return db;
|
|
5516
5674
|
}
|
|
5675
|
+
function getCached(key) {
|
|
5676
|
+
const row = getDb().prepare("SELECT value, expires_at FROM cache WHERE key = ?").get(key);
|
|
5677
|
+
if (!row) {
|
|
5678
|
+
return null;
|
|
5679
|
+
}
|
|
5680
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5681
|
+
if (row.expires_at <= now) {
|
|
5682
|
+
getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
|
|
5683
|
+
return null;
|
|
5684
|
+
}
|
|
5685
|
+
return JSON.parse(row.value);
|
|
5686
|
+
}
|
|
5687
|
+
function setCache(key, value, ttlSeconds) {
|
|
5688
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5689
|
+
const expiresAt = now + ttlSeconds;
|
|
5690
|
+
getDb().prepare(
|
|
5691
|
+
`INSERT OR REPLACE INTO cache (key, value, expires_at, created_at)
|
|
5692
|
+
VALUES (?, ?, ?, ?)`
|
|
5693
|
+
).run(key, JSON.stringify(value), expiresAt, now);
|
|
5694
|
+
}
|
|
5517
5695
|
var db;
|
|
5518
5696
|
var init_cache = __esm({
|
|
5519
5697
|
"src/data/cache.ts"() {
|
|
@@ -5523,32 +5701,6 @@ var init_cache = __esm({
|
|
|
5523
5701
|
}
|
|
5524
5702
|
});
|
|
5525
5703
|
|
|
5526
|
-
// src/utils/logger.ts
|
|
5527
|
-
import pino from "pino";
|
|
5528
|
-
function createLogger(name) {
|
|
5529
|
-
const isDev = process.env["NODE_ENV"] !== "production";
|
|
5530
|
-
if (isDev) {
|
|
5531
|
-
return pino({
|
|
5532
|
-
name,
|
|
5533
|
-
level,
|
|
5534
|
-
transport: {
|
|
5535
|
-
target: "pino-pretty",
|
|
5536
|
-
options: {
|
|
5537
|
-
colorize: true
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
|
-
});
|
|
5541
|
-
}
|
|
5542
|
-
return pino({ name, level });
|
|
5543
|
-
}
|
|
5544
|
-
var level;
|
|
5545
|
-
var init_logger = __esm({
|
|
5546
|
-
"src/utils/logger.ts"() {
|
|
5547
|
-
"use strict";
|
|
5548
|
-
level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
|
|
5549
|
-
}
|
|
5550
|
-
});
|
|
5551
|
-
|
|
5552
5704
|
// src/core/agent/engine.ts
|
|
5553
5705
|
var logger, AgentEngine;
|
|
5554
5706
|
var init_engine = __esm({
|
|
@@ -6019,6 +6171,436 @@ var init_agent = __esm({
|
|
|
6019
6171
|
}
|
|
6020
6172
|
});
|
|
6021
6173
|
|
|
6174
|
+
// src/data/sqlite-store.ts
|
|
6175
|
+
function ensureAgentTables2() {
|
|
6176
|
+
const db2 = getDb();
|
|
6177
|
+
db2.exec(`
|
|
6178
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
6179
|
+
id TEXT PRIMARY KEY,
|
|
6180
|
+
name TEXT NOT NULL UNIQUE,
|
|
6181
|
+
strategy TEXT NOT NULL,
|
|
6182
|
+
pairs TEXT NOT NULL,
|
|
6183
|
+
interval_seconds INTEGER NOT NULL DEFAULT 60,
|
|
6184
|
+
created_at INTEGER NOT NULL,
|
|
6185
|
+
updated_at INTEGER NOT NULL
|
|
6186
|
+
)
|
|
6187
|
+
`);
|
|
6188
|
+
db2.exec(`
|
|
6189
|
+
CREATE TABLE IF NOT EXISTS agent_decisions (
|
|
6190
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6191
|
+
agent_id TEXT NOT NULL,
|
|
6192
|
+
symbol TEXT NOT NULL,
|
|
6193
|
+
action TEXT NOT NULL,
|
|
6194
|
+
confidence INTEGER NOT NULL,
|
|
6195
|
+
reasoning TEXT NOT NULL,
|
|
6196
|
+
signals TEXT NOT NULL,
|
|
6197
|
+
created_at INTEGER NOT NULL,
|
|
6198
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
|
6199
|
+
)
|
|
6200
|
+
`);
|
|
6201
|
+
}
|
|
6202
|
+
function rowToConfig(row) {
|
|
6203
|
+
return {
|
|
6204
|
+
id: row.id,
|
|
6205
|
+
name: row.name,
|
|
6206
|
+
strategy: row.strategy,
|
|
6207
|
+
pairs: JSON.parse(row.pairs),
|
|
6208
|
+
interval: row.interval_seconds,
|
|
6209
|
+
createdAt: row.created_at,
|
|
6210
|
+
updatedAt: row.updated_at
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
var SqliteStore;
|
|
6214
|
+
var init_sqlite_store = __esm({
|
|
6215
|
+
"src/data/sqlite-store.ts"() {
|
|
6216
|
+
"use strict";
|
|
6217
|
+
init_cache();
|
|
6218
|
+
SqliteStore = class {
|
|
6219
|
+
// ---- Cache ---------------------------------------------------------------
|
|
6220
|
+
async getCached(key) {
|
|
6221
|
+
return getCached(key);
|
|
6222
|
+
}
|
|
6223
|
+
async setCache(key, value, ttlSeconds) {
|
|
6224
|
+
setCache(key, value, ttlSeconds);
|
|
6225
|
+
}
|
|
6226
|
+
// ---- Agents --------------------------------------------------------------
|
|
6227
|
+
async createAgent(config2) {
|
|
6228
|
+
ensureAgentTables2();
|
|
6229
|
+
getDb().prepare(
|
|
6230
|
+
`INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
|
|
6231
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
6232
|
+
).run(
|
|
6233
|
+
config2.id,
|
|
6234
|
+
config2.name,
|
|
6235
|
+
config2.strategy,
|
|
6236
|
+
JSON.stringify(config2.pairs),
|
|
6237
|
+
config2.interval,
|
|
6238
|
+
config2.createdAt,
|
|
6239
|
+
config2.updatedAt
|
|
6240
|
+
);
|
|
6241
|
+
return config2;
|
|
6242
|
+
}
|
|
6243
|
+
async listAgents() {
|
|
6244
|
+
ensureAgentTables2();
|
|
6245
|
+
const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
|
|
6246
|
+
return rows.map(rowToConfig);
|
|
6247
|
+
}
|
|
6248
|
+
async getAgentById(id) {
|
|
6249
|
+
ensureAgentTables2();
|
|
6250
|
+
const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
|
|
6251
|
+
return row ? rowToConfig(row) : null;
|
|
6252
|
+
}
|
|
6253
|
+
async getAgentByName(name) {
|
|
6254
|
+
ensureAgentTables2();
|
|
6255
|
+
const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
|
|
6256
|
+
return row ? rowToConfig(row) : null;
|
|
6257
|
+
}
|
|
6258
|
+
async deleteAgent(id) {
|
|
6259
|
+
ensureAgentTables2();
|
|
6260
|
+
const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
6261
|
+
getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
|
|
6262
|
+
return result.changes > 0;
|
|
6263
|
+
}
|
|
6264
|
+
async logDecision(result) {
|
|
6265
|
+
ensureAgentTables2();
|
|
6266
|
+
getDb().prepare(
|
|
6267
|
+
`INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
|
|
6268
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
6269
|
+
).run(
|
|
6270
|
+
result.agentId,
|
|
6271
|
+
result.symbol,
|
|
6272
|
+
result.decision.action,
|
|
6273
|
+
result.decision.confidence,
|
|
6274
|
+
JSON.stringify(result.decision.reasoning),
|
|
6275
|
+
JSON.stringify(result.signals),
|
|
6276
|
+
result.timestamp
|
|
6277
|
+
);
|
|
6278
|
+
}
|
|
6279
|
+
async getDecisions(agentId, limit) {
|
|
6280
|
+
ensureAgentTables2();
|
|
6281
|
+
const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
|
|
6282
|
+
return rows.map((r) => ({
|
|
6283
|
+
agentId: r.agent_id,
|
|
6284
|
+
symbol: r.symbol,
|
|
6285
|
+
timestamp: r.created_at,
|
|
6286
|
+
signals: JSON.parse(r.signals),
|
|
6287
|
+
decision: {
|
|
6288
|
+
action: r.action,
|
|
6289
|
+
confidence: r.confidence,
|
|
6290
|
+
reasoning: JSON.parse(r.reasoning)
|
|
6291
|
+
}
|
|
6292
|
+
}));
|
|
6293
|
+
}
|
|
6294
|
+
// ---- Time-series (no-op for SQLite) --------------------------------------
|
|
6295
|
+
async insertOHLCV(_records) {
|
|
6296
|
+
}
|
|
6297
|
+
async queryOHLCV(_symbol, _timeframe, _from, _to) {
|
|
6298
|
+
return [];
|
|
6299
|
+
}
|
|
6300
|
+
// ---- Predictions (no-op for SQLite) --------------------------------------
|
|
6301
|
+
async logPrediction(_prediction) {
|
|
6302
|
+
}
|
|
6303
|
+
async getPredictionAccuracy(_model, _days) {
|
|
6304
|
+
return {
|
|
6305
|
+
model: _model,
|
|
6306
|
+
totalPredictions: 0,
|
|
6307
|
+
correctPredictions: 0,
|
|
6308
|
+
accuracy: 0,
|
|
6309
|
+
byDirection: {
|
|
6310
|
+
up: { total: 0, correct: 0, accuracy: 0 },
|
|
6311
|
+
down: { total: 0, correct: 0, accuracy: 0 },
|
|
6312
|
+
sideways: { total: 0, correct: 0, accuracy: 0 }
|
|
6313
|
+
},
|
|
6314
|
+
period: `${_days}d`
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
6318
|
+
async close() {
|
|
6319
|
+
}
|
|
6320
|
+
};
|
|
6321
|
+
}
|
|
6322
|
+
});
|
|
6323
|
+
|
|
6324
|
+
// src/data/postgres-store.ts
|
|
6325
|
+
var postgres_store_exports = {};
|
|
6326
|
+
__export(postgres_store_exports, {
|
|
6327
|
+
PostgresStore: () => PostgresStore
|
|
6328
|
+
});
|
|
6329
|
+
import pg from "pg";
|
|
6330
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
6331
|
+
import { resolve as resolve2, dirname } from "path";
|
|
6332
|
+
import { fileURLToPath } from "url";
|
|
6333
|
+
var __filename, __dirname, PostgresStore;
|
|
6334
|
+
var init_postgres_store = __esm({
|
|
6335
|
+
"src/data/postgres-store.ts"() {
|
|
6336
|
+
"use strict";
|
|
6337
|
+
__filename = fileURLToPath(import.meta.url);
|
|
6338
|
+
__dirname = dirname(__filename);
|
|
6339
|
+
PostgresStore = class {
|
|
6340
|
+
pool;
|
|
6341
|
+
initialized = false;
|
|
6342
|
+
constructor(connectionUrl) {
|
|
6343
|
+
this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
|
|
6344
|
+
}
|
|
6345
|
+
async init() {
|
|
6346
|
+
if (this.initialized) return;
|
|
6347
|
+
const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
|
|
6348
|
+
const sql = readFileSync2(migrationPath, "utf-8");
|
|
6349
|
+
await this.pool.query(sql);
|
|
6350
|
+
this.initialized = true;
|
|
6351
|
+
}
|
|
6352
|
+
async query(text, params) {
|
|
6353
|
+
await this.init();
|
|
6354
|
+
return this.pool.query(text, params);
|
|
6355
|
+
}
|
|
6356
|
+
// ---- Cache ---------------------------------------------------------------
|
|
6357
|
+
async getCached(key) {
|
|
6358
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
6359
|
+
const { rows } = await this.query(
|
|
6360
|
+
"SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
|
|
6361
|
+
[key, now]
|
|
6362
|
+
);
|
|
6363
|
+
if (rows.length === 0) return null;
|
|
6364
|
+
return rows[0].value;
|
|
6365
|
+
}
|
|
6366
|
+
async setCache(key, value, ttlSeconds) {
|
|
6367
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
6368
|
+
const expiresAt = now + ttlSeconds;
|
|
6369
|
+
await this.query(
|
|
6370
|
+
`INSERT INTO cache (key, value, expires_at, created_at)
|
|
6371
|
+
VALUES ($1, $2, $3, $4)
|
|
6372
|
+
ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
|
|
6373
|
+
[key, JSON.stringify(value), expiresAt, now]
|
|
6374
|
+
);
|
|
6375
|
+
}
|
|
6376
|
+
// ---- Agents --------------------------------------------------------------
|
|
6377
|
+
async createAgent(config2) {
|
|
6378
|
+
await this.query(
|
|
6379
|
+
`INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
|
|
6380
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
6381
|
+
[
|
|
6382
|
+
config2.id,
|
|
6383
|
+
config2.name,
|
|
6384
|
+
config2.strategy,
|
|
6385
|
+
JSON.stringify(config2.pairs),
|
|
6386
|
+
config2.interval,
|
|
6387
|
+
config2.createdAt,
|
|
6388
|
+
config2.updatedAt
|
|
6389
|
+
]
|
|
6390
|
+
);
|
|
6391
|
+
return config2;
|
|
6392
|
+
}
|
|
6393
|
+
async listAgents() {
|
|
6394
|
+
const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
|
|
6395
|
+
return rows.map((r) => ({
|
|
6396
|
+
id: r.id,
|
|
6397
|
+
name: r.name,
|
|
6398
|
+
strategy: r.strategy,
|
|
6399
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
6400
|
+
interval: r.interval_seconds,
|
|
6401
|
+
createdAt: Number(r.created_at),
|
|
6402
|
+
updatedAt: Number(r.updated_at)
|
|
6403
|
+
}));
|
|
6404
|
+
}
|
|
6405
|
+
async getAgentById(id) {
|
|
6406
|
+
const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
|
|
6407
|
+
if (rows.length === 0) return null;
|
|
6408
|
+
const r = rows[0];
|
|
6409
|
+
return {
|
|
6410
|
+
id: r.id,
|
|
6411
|
+
name: r.name,
|
|
6412
|
+
strategy: r.strategy,
|
|
6413
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
6414
|
+
interval: r.interval_seconds,
|
|
6415
|
+
createdAt: Number(r.created_at),
|
|
6416
|
+
updatedAt: Number(r.updated_at)
|
|
6417
|
+
};
|
|
6418
|
+
}
|
|
6419
|
+
async getAgentByName(name) {
|
|
6420
|
+
const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
|
|
6421
|
+
if (rows.length === 0) return null;
|
|
6422
|
+
const r = rows[0];
|
|
6423
|
+
return {
|
|
6424
|
+
id: r.id,
|
|
6425
|
+
name: r.name,
|
|
6426
|
+
strategy: r.strategy,
|
|
6427
|
+
pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
|
|
6428
|
+
interval: r.interval_seconds,
|
|
6429
|
+
createdAt: Number(r.created_at),
|
|
6430
|
+
updatedAt: Number(r.updated_at)
|
|
6431
|
+
};
|
|
6432
|
+
}
|
|
6433
|
+
async deleteAgent(id) {
|
|
6434
|
+
const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
|
|
6435
|
+
return (result.rowCount ?? 0) > 0;
|
|
6436
|
+
}
|
|
6437
|
+
async logDecision(result) {
|
|
6438
|
+
await this.query(
|
|
6439
|
+
`INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
|
|
6440
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
6441
|
+
[
|
|
6442
|
+
result.agentId,
|
|
6443
|
+
result.symbol,
|
|
6444
|
+
result.decision.action,
|
|
6445
|
+
result.decision.confidence,
|
|
6446
|
+
JSON.stringify(result.decision.reasoning),
|
|
6447
|
+
JSON.stringify(result.signals),
|
|
6448
|
+
result.timestamp
|
|
6449
|
+
]
|
|
6450
|
+
);
|
|
6451
|
+
}
|
|
6452
|
+
async getDecisions(agentId, limit) {
|
|
6453
|
+
const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
|
|
6454
|
+
agentId,
|
|
6455
|
+
limit
|
|
6456
|
+
]);
|
|
6457
|
+
return rows.map((r) => ({
|
|
6458
|
+
agentId: r.agent_id,
|
|
6459
|
+
symbol: r.symbol,
|
|
6460
|
+
timestamp: Number(r.created_at),
|
|
6461
|
+
signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
|
|
6462
|
+
decision: {
|
|
6463
|
+
action: r.action,
|
|
6464
|
+
confidence: r.confidence,
|
|
6465
|
+
reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
|
|
6466
|
+
}
|
|
6467
|
+
}));
|
|
6468
|
+
}
|
|
6469
|
+
// ---- Time-series ---------------------------------------------------------
|
|
6470
|
+
async insertOHLCV(records) {
|
|
6471
|
+
if (records.length === 0) return;
|
|
6472
|
+
const values = [];
|
|
6473
|
+
const placeholders = [];
|
|
6474
|
+
for (let i = 0; i < records.length; i++) {
|
|
6475
|
+
const r = records[i];
|
|
6476
|
+
const offset = i * 9;
|
|
6477
|
+
placeholders.push(
|
|
6478
|
+
`($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
|
|
6479
|
+
);
|
|
6480
|
+
values.push(
|
|
6481
|
+
new Date(r.time).toISOString(),
|
|
6482
|
+
r.symbol,
|
|
6483
|
+
r.timeframe,
|
|
6484
|
+
r.open,
|
|
6485
|
+
r.high,
|
|
6486
|
+
r.low,
|
|
6487
|
+
r.close,
|
|
6488
|
+
r.volume,
|
|
6489
|
+
r.trades
|
|
6490
|
+
);
|
|
6491
|
+
}
|
|
6492
|
+
await this.query(
|
|
6493
|
+
`INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
|
|
6494
|
+
VALUES ${placeholders.join(", ")}
|
|
6495
|
+
ON CONFLICT (symbol, timeframe, time) DO UPDATE
|
|
6496
|
+
SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
|
|
6497
|
+
close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
|
|
6498
|
+
values
|
|
6499
|
+
);
|
|
6500
|
+
}
|
|
6501
|
+
async queryOHLCV(symbol, timeframe, from, to) {
|
|
6502
|
+
const { rows } = await this.query(
|
|
6503
|
+
`SELECT * FROM ohlcv
|
|
6504
|
+
WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
|
|
6505
|
+
ORDER BY time ASC`,
|
|
6506
|
+
[symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
|
|
6507
|
+
);
|
|
6508
|
+
return rows.map((r) => ({
|
|
6509
|
+
time: new Date(r.time).getTime(),
|
|
6510
|
+
symbol: r.symbol,
|
|
6511
|
+
timeframe: r.timeframe,
|
|
6512
|
+
open: r.open,
|
|
6513
|
+
high: r.high,
|
|
6514
|
+
low: r.low,
|
|
6515
|
+
close: r.close,
|
|
6516
|
+
volume: r.volume,
|
|
6517
|
+
trades: r.trades
|
|
6518
|
+
}));
|
|
6519
|
+
}
|
|
6520
|
+
// ---- Predictions ---------------------------------------------------------
|
|
6521
|
+
async logPrediction(prediction) {
|
|
6522
|
+
await this.query(
|
|
6523
|
+
`INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
|
|
6524
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
6525
|
+
[
|
|
6526
|
+
prediction.symbol,
|
|
6527
|
+
prediction.model,
|
|
6528
|
+
prediction.direction,
|
|
6529
|
+
prediction.probability,
|
|
6530
|
+
prediction.horizon,
|
|
6531
|
+
JSON.stringify(prediction.features),
|
|
6532
|
+
new Date(prediction.predictedAt).toISOString()
|
|
6533
|
+
]
|
|
6534
|
+
);
|
|
6535
|
+
}
|
|
6536
|
+
async getPredictionAccuracy(model, days) {
|
|
6537
|
+
const since = new Date(Date.now() - days * 864e5).toISOString();
|
|
6538
|
+
const { rows: totals } = await this.query(
|
|
6539
|
+
`SELECT direction,
|
|
6540
|
+
COUNT(*)::text AS total,
|
|
6541
|
+
COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
|
|
6542
|
+
FROM predictions
|
|
6543
|
+
WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
|
|
6544
|
+
GROUP BY direction`,
|
|
6545
|
+
[model, since]
|
|
6546
|
+
);
|
|
6547
|
+
const byDir = {
|
|
6548
|
+
up: { total: 0, correct: 0, accuracy: 0 },
|
|
6549
|
+
down: { total: 0, correct: 0, accuracy: 0 },
|
|
6550
|
+
sideways: { total: 0, correct: 0, accuracy: 0 }
|
|
6551
|
+
};
|
|
6552
|
+
let totalAll = 0;
|
|
6553
|
+
let correctAll = 0;
|
|
6554
|
+
for (const row of totals) {
|
|
6555
|
+
const t = parseInt(row.total, 10);
|
|
6556
|
+
const c = parseInt(row.correct, 10);
|
|
6557
|
+
totalAll += t;
|
|
6558
|
+
correctAll += c;
|
|
6559
|
+
const dir = row.direction;
|
|
6560
|
+
if (byDir[dir]) {
|
|
6561
|
+
byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
return {
|
|
6565
|
+
model,
|
|
6566
|
+
totalPredictions: totalAll,
|
|
6567
|
+
correctPredictions: correctAll,
|
|
6568
|
+
accuracy: totalAll > 0 ? correctAll / totalAll : 0,
|
|
6569
|
+
byDirection: byDir,
|
|
6570
|
+
period: `${days}d`
|
|
6571
|
+
};
|
|
6572
|
+
}
|
|
6573
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
6574
|
+
async close() {
|
|
6575
|
+
await this.pool.end();
|
|
6576
|
+
}
|
|
6577
|
+
};
|
|
6578
|
+
}
|
|
6579
|
+
});
|
|
6580
|
+
|
|
6581
|
+
// src/data/store-factory.ts
|
|
6582
|
+
async function getStore(config2) {
|
|
6583
|
+
if (instance) return instance;
|
|
6584
|
+
if (config2.database?.type === "postgres" && config2.database.url) {
|
|
6585
|
+
const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
|
|
6586
|
+
instance = new PostgresStore2(config2.database.url);
|
|
6587
|
+
} else {
|
|
6588
|
+
instance = new SqliteStore();
|
|
6589
|
+
}
|
|
6590
|
+
return instance;
|
|
6591
|
+
}
|
|
6592
|
+
function getStoreInstance() {
|
|
6593
|
+
return instance;
|
|
6594
|
+
}
|
|
6595
|
+
var instance;
|
|
6596
|
+
var init_store_factory = __esm({
|
|
6597
|
+
"src/data/store-factory.ts"() {
|
|
6598
|
+
"use strict";
|
|
6599
|
+
init_sqlite_store();
|
|
6600
|
+
instance = null;
|
|
6601
|
+
}
|
|
6602
|
+
});
|
|
6603
|
+
|
|
6022
6604
|
// src/ai/tool-handler.ts
|
|
6023
6605
|
async function handleTool(name, input) {
|
|
6024
6606
|
const params = input;
|
|
@@ -6315,6 +6897,69 @@ async function handleTool(name, input) {
|
|
|
6315
6897
|
disclaimer: prediction.disclaimer
|
|
6316
6898
|
};
|
|
6317
6899
|
}
|
|
6900
|
+
case "get_ml_prediction": {
|
|
6901
|
+
const symbol = String(params["symbol"] ?? "BTC");
|
|
6902
|
+
const mlClient2 = getMLClient();
|
|
6903
|
+
if (!mlClient2) {
|
|
6904
|
+
const prediction = await generatePrediction(symbol);
|
|
6905
|
+
return {
|
|
6906
|
+
...prediction,
|
|
6907
|
+
mlAvailable: false,
|
|
6908
|
+
note: "ML sidecar not configured; using rule-based prediction"
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
const features = await buildFeatureVector(symbol);
|
|
6912
|
+
const mlPred = await mlClient2.predict(features);
|
|
6913
|
+
if (!mlPred) {
|
|
6914
|
+
const prediction = await generatePrediction(symbol);
|
|
6915
|
+
return {
|
|
6916
|
+
...prediction,
|
|
6917
|
+
mlAvailable: false,
|
|
6918
|
+
note: "ML sidecar unavailable; using rule-based prediction"
|
|
6919
|
+
};
|
|
6920
|
+
}
|
|
6921
|
+
return {
|
|
6922
|
+
symbol: mlPred.symbol,
|
|
6923
|
+
direction: mlPred.direction,
|
|
6924
|
+
probability: mlPred.probability,
|
|
6925
|
+
confidence: mlPred.confidence,
|
|
6926
|
+
model: mlPred.model,
|
|
6927
|
+
horizon: mlPred.horizon,
|
|
6928
|
+
mlAvailable: true,
|
|
6929
|
+
features: {
|
|
6930
|
+
rsi: features.rsi,
|
|
6931
|
+
macdHistogram: features.macdHistogram,
|
|
6932
|
+
bollingerPercentB: features.bollingerPercentB,
|
|
6933
|
+
fundingRate: features.fundingRate,
|
|
6934
|
+
fearGreed: features.fearGreed,
|
|
6935
|
+
rsiSlope: features.rsiSlope,
|
|
6936
|
+
volumeRatio: features.volumeRatio,
|
|
6937
|
+
emaCrossoverPct: features.emaCrossoverPct,
|
|
6938
|
+
atrPct: features.atrPct
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6941
|
+
}
|
|
6942
|
+
case "get_model_accuracy": {
|
|
6943
|
+
const model = String(params["model"] ?? "lstm-predictor");
|
|
6944
|
+
const days = params["days"] ? Number(params["days"]) : 30;
|
|
6945
|
+
const store = getStoreInstance();
|
|
6946
|
+
if (!store) {
|
|
6947
|
+
return { error: "DataStore not initialized. ML accuracy requires PostgreSQL backend." };
|
|
6948
|
+
}
|
|
6949
|
+
const accuracy = await store.getPredictionAccuracy(model, days);
|
|
6950
|
+
return {
|
|
6951
|
+
model: accuracy.model,
|
|
6952
|
+
period: accuracy.period,
|
|
6953
|
+
totalPredictions: accuracy.totalPredictions,
|
|
6954
|
+
correctPredictions: accuracy.correctPredictions,
|
|
6955
|
+
accuracy: `${(accuracy.accuracy * 100).toFixed(1)}%`,
|
|
6956
|
+
byDirection: {
|
|
6957
|
+
up: `${(accuracy.byDirection.up.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.up.correct}/${accuracy.byDirection.up.total})`,
|
|
6958
|
+
down: `${(accuracy.byDirection.down.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.down.correct}/${accuracy.byDirection.down.total})`,
|
|
6959
|
+
sideways: `${(accuracy.byDirection.sideways.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.sideways.correct}/${accuracy.byDirection.sideways.total})`
|
|
6960
|
+
}
|
|
6961
|
+
};
|
|
6962
|
+
}
|
|
6318
6963
|
case "create_agent": {
|
|
6319
6964
|
const agentName = String(params["name"] ?? "");
|
|
6320
6965
|
const strategy = String(params["strategy"] ?? "momentum");
|
|
@@ -6392,6 +7037,9 @@ var init_tool_handler = __esm({
|
|
|
6392
7037
|
init_technical_analysis();
|
|
6393
7038
|
init_predictor();
|
|
6394
7039
|
init_agent();
|
|
7040
|
+
init_client();
|
|
7041
|
+
init_feature_engineer();
|
|
7042
|
+
init_store_factory();
|
|
6395
7043
|
}
|
|
6396
7044
|
});
|
|
6397
7045
|
|
|
@@ -6649,6 +7297,38 @@ var init_tools = __esm({
|
|
|
6649
7297
|
required: ["symbol"]
|
|
6650
7298
|
}
|
|
6651
7299
|
},
|
|
7300
|
+
{
|
|
7301
|
+
name: "get_ml_prediction",
|
|
7302
|
+
description: "Get an ML-enhanced prediction using LSTM/Random Forest models from the ML sidecar. Returns direction, probability, model confidence, and feature importance. Falls back to rule-based prediction if ML sidecar is unavailable.",
|
|
7303
|
+
input_schema: {
|
|
7304
|
+
type: "object",
|
|
7305
|
+
properties: {
|
|
7306
|
+
symbol: {
|
|
7307
|
+
type: "string",
|
|
7308
|
+
description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
|
|
7309
|
+
}
|
|
7310
|
+
},
|
|
7311
|
+
required: ["symbol"]
|
|
7312
|
+
}
|
|
7313
|
+
},
|
|
7314
|
+
{
|
|
7315
|
+
name: "get_model_accuracy",
|
|
7316
|
+
description: "Get historical accuracy metrics for ML prediction models. Shows total predictions, accuracy percentage, and breakdown by direction (up/down/sideways).",
|
|
7317
|
+
input_schema: {
|
|
7318
|
+
type: "object",
|
|
7319
|
+
properties: {
|
|
7320
|
+
model: {
|
|
7321
|
+
type: "string",
|
|
7322
|
+
description: 'Model name (e.g. "lstm-predictor", "signal-classifier"). Defaults to "lstm-predictor".'
|
|
7323
|
+
},
|
|
7324
|
+
days: {
|
|
7325
|
+
type: "number",
|
|
7326
|
+
description: "Number of days to look back for accuracy stats. Defaults to 30."
|
|
7327
|
+
}
|
|
7328
|
+
},
|
|
7329
|
+
required: []
|
|
7330
|
+
}
|
|
7331
|
+
},
|
|
6652
7332
|
{
|
|
6653
7333
|
name: "create_agent",
|
|
6654
7334
|
description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
|
|
@@ -7311,7 +7991,7 @@ var init_bot = __esm({
|
|
|
7311
7991
|
"src/discord/bot.ts"() {
|
|
7312
7992
|
"use strict";
|
|
7313
7993
|
init_loader();
|
|
7314
|
-
|
|
7994
|
+
init_client2();
|
|
7315
7995
|
init_tool_handler();
|
|
7316
7996
|
init_tools();
|
|
7317
7997
|
init_chat();
|
|
@@ -7993,7 +8673,7 @@ var init_bot2 = __esm({
|
|
|
7993
8673
|
"src/telegram/bot.ts"() {
|
|
7994
8674
|
"use strict";
|
|
7995
8675
|
init_loader();
|
|
7996
|
-
|
|
8676
|
+
init_client2();
|
|
7997
8677
|
init_tool_handler();
|
|
7998
8678
|
init_tools();
|
|
7999
8679
|
init_chat();
|
|
@@ -8048,40 +8728,18 @@ function handleBotValidate() {
|
|
|
8048
8728
|
const config2 = getConfig();
|
|
8049
8729
|
console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
|
|
8050
8730
|
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
|
-
}
|
|
8731
|
+
{ label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
|
|
8732
|
+
{ label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
|
|
8733
|
+
{ label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
|
|
8734
|
+
{ label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
|
|
8735
|
+
{ label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
|
|
8736
|
+
{ label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
|
|
8077
8737
|
];
|
|
8078
8738
|
let allRequired = true;
|
|
8079
8739
|
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;
|
|
8740
|
+
const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
|
|
8741
|
+
console.log(` ${status.padEnd(18)} ${check.label}`);
|
|
8742
|
+
if (check.required && !check.isSet) allRequired = false;
|
|
8085
8743
|
}
|
|
8086
8744
|
console.log();
|
|
8087
8745
|
if (hasKey(config2.discordToken)) {
|
|
@@ -8114,6 +8772,186 @@ var init_bot3 = __esm({
|
|
|
8114
8772
|
}
|
|
8115
8773
|
});
|
|
8116
8774
|
|
|
8775
|
+
// src/data/collector.ts
|
|
8776
|
+
var log2, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
|
|
8777
|
+
var init_collector = __esm({
|
|
8778
|
+
"src/data/collector.ts"() {
|
|
8779
|
+
"use strict";
|
|
8780
|
+
init_binance();
|
|
8781
|
+
init_logger();
|
|
8782
|
+
log2 = createLogger("collector");
|
|
8783
|
+
MAJOR_SYMBOLS = [
|
|
8784
|
+
"BTC",
|
|
8785
|
+
"ETH",
|
|
8786
|
+
"SOL",
|
|
8787
|
+
"BNB",
|
|
8788
|
+
"XRP",
|
|
8789
|
+
"ADA",
|
|
8790
|
+
"DOGE",
|
|
8791
|
+
"DOT",
|
|
8792
|
+
"AVAX",
|
|
8793
|
+
"MATIC",
|
|
8794
|
+
"LINK",
|
|
8795
|
+
"UNI",
|
|
8796
|
+
"ATOM",
|
|
8797
|
+
"NEAR",
|
|
8798
|
+
"ARB",
|
|
8799
|
+
"OP",
|
|
8800
|
+
"SUI",
|
|
8801
|
+
"APT",
|
|
8802
|
+
"PEPE",
|
|
8803
|
+
"SHIB",
|
|
8804
|
+
"FLOKI",
|
|
8805
|
+
"BONK",
|
|
8806
|
+
"WIF"
|
|
8807
|
+
];
|
|
8808
|
+
TIMEFRAMES = ["1h", "4h"];
|
|
8809
|
+
COLLECTION_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8810
|
+
DataCollector = class {
|
|
8811
|
+
constructor(store, symbols = MAJOR_SYMBOLS, intervalMs = COLLECTION_INTERVAL_MS) {
|
|
8812
|
+
this.store = store;
|
|
8813
|
+
this.symbols = symbols;
|
|
8814
|
+
this.intervalMs = intervalMs;
|
|
8815
|
+
this.status.symbols = symbols;
|
|
8816
|
+
this.status.intervalMs = intervalMs;
|
|
8817
|
+
}
|
|
8818
|
+
timer = null;
|
|
8819
|
+
status = {
|
|
8820
|
+
running: false,
|
|
8821
|
+
symbols: MAJOR_SYMBOLS,
|
|
8822
|
+
timeframes: [...TIMEFRAMES],
|
|
8823
|
+
intervalMs: COLLECTION_INTERVAL_MS,
|
|
8824
|
+
lastRun: null,
|
|
8825
|
+
totalRecords: 0,
|
|
8826
|
+
errors: 0
|
|
8827
|
+
};
|
|
8828
|
+
getStatus() {
|
|
8829
|
+
return { ...this.status };
|
|
8830
|
+
}
|
|
8831
|
+
start() {
|
|
8832
|
+
if (this.status.running) {
|
|
8833
|
+
log2.warn("Collector already running");
|
|
8834
|
+
return;
|
|
8835
|
+
}
|
|
8836
|
+
log2.info(
|
|
8837
|
+
`Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
|
|
8838
|
+
);
|
|
8839
|
+
this.status.running = true;
|
|
8840
|
+
void this.collectAll();
|
|
8841
|
+
this.timer = setInterval(() => void this.collectAll(), this.intervalMs);
|
|
8842
|
+
}
|
|
8843
|
+
stop() {
|
|
8844
|
+
if (this.timer) {
|
|
8845
|
+
clearInterval(this.timer);
|
|
8846
|
+
this.timer = null;
|
|
8847
|
+
}
|
|
8848
|
+
this.status.running = false;
|
|
8849
|
+
log2.info("Data collector stopped");
|
|
8850
|
+
}
|
|
8851
|
+
async collectAll() {
|
|
8852
|
+
log2.info("Collection cycle starting");
|
|
8853
|
+
const start = Date.now();
|
|
8854
|
+
for (const timeframe of TIMEFRAMES) {
|
|
8855
|
+
for (const symbol of this.symbols) {
|
|
8856
|
+
try {
|
|
8857
|
+
await this.collectSymbol(symbol, timeframe);
|
|
8858
|
+
} catch (err) {
|
|
8859
|
+
this.status.errors++;
|
|
8860
|
+
log2.error(
|
|
8861
|
+
`Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
|
|
8862
|
+
);
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8866
|
+
this.status.lastRun = Date.now();
|
|
8867
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
8868
|
+
log2.info(
|
|
8869
|
+
`Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
|
|
8870
|
+
);
|
|
8871
|
+
}
|
|
8872
|
+
async collectSymbol(symbol, timeframe) {
|
|
8873
|
+
const klines = await fetchKlines(symbol, timeframe, 100);
|
|
8874
|
+
const records = klines.map((k) => ({
|
|
8875
|
+
time: k.openTime,
|
|
8876
|
+
symbol: symbol.toUpperCase(),
|
|
8877
|
+
timeframe,
|
|
8878
|
+
open: k.open,
|
|
8879
|
+
high: k.high,
|
|
8880
|
+
low: k.low,
|
|
8881
|
+
close: k.close,
|
|
8882
|
+
volume: k.volume,
|
|
8883
|
+
trades: k.trades
|
|
8884
|
+
}));
|
|
8885
|
+
await this.store.insertOHLCV(records);
|
|
8886
|
+
this.status.totalRecords += records.length;
|
|
8887
|
+
}
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
});
|
|
8891
|
+
|
|
8892
|
+
// src/cli/commands/collect.ts
|
|
8893
|
+
var collect_exports = {};
|
|
8894
|
+
__export(collect_exports, {
|
|
8895
|
+
handleCollectStart: () => handleCollectStart,
|
|
8896
|
+
handleCollectStatus: () => handleCollectStatus
|
|
8897
|
+
});
|
|
8898
|
+
import chalk8 from "chalk";
|
|
8899
|
+
async function handleCollectStart(options) {
|
|
8900
|
+
const config2 = getConfig();
|
|
8901
|
+
if (config2.database?.type !== "postgres" || !config2.database.url) {
|
|
8902
|
+
console.log(
|
|
8903
|
+
chalk8.yellow(
|
|
8904
|
+
'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'
|
|
8905
|
+
)
|
|
8906
|
+
);
|
|
8907
|
+
return;
|
|
8908
|
+
}
|
|
8909
|
+
const store = await getStore(config2);
|
|
8910
|
+
const symbols = options.symbols ? options.symbols.split(",").map((s) => s.trim().toUpperCase()) : void 0;
|
|
8911
|
+
const intervalMs = options.interval ? options.interval * 1e3 : void 0;
|
|
8912
|
+
collector = new DataCollector(store, symbols, intervalMs);
|
|
8913
|
+
collector.start();
|
|
8914
|
+
console.log(chalk8.green("Data collector started"));
|
|
8915
|
+
const status = collector.getStatus();
|
|
8916
|
+
console.log(` Symbols: ${status.symbols.length} pairs`);
|
|
8917
|
+
console.log(` Timeframes: ${status.timeframes.join(", ")}`);
|
|
8918
|
+
console.log(` Interval: ${status.intervalMs / 1e3}s`);
|
|
8919
|
+
console.log(chalk8.dim("\nPress Ctrl+C to stop"));
|
|
8920
|
+
process.on("SIGINT", () => {
|
|
8921
|
+
collector?.stop();
|
|
8922
|
+
void store.close();
|
|
8923
|
+
process.exit(0);
|
|
8924
|
+
});
|
|
8925
|
+
await new Promise(() => {
|
|
8926
|
+
});
|
|
8927
|
+
}
|
|
8928
|
+
function handleCollectStatus() {
|
|
8929
|
+
if (!collector) {
|
|
8930
|
+
console.log(chalk8.yellow("No collector running in this process."));
|
|
8931
|
+
console.log(chalk8.dim("Start with: vizzor collect start"));
|
|
8932
|
+
return;
|
|
8933
|
+
}
|
|
8934
|
+
const status = collector.getStatus();
|
|
8935
|
+
console.log(chalk8.bold("Data Collector Status"));
|
|
8936
|
+
console.log(` Running: ${status.running ? chalk8.green("yes") : chalk8.red("no")}`);
|
|
8937
|
+
console.log(` Symbols: ${status.symbols.length} pairs`);
|
|
8938
|
+
console.log(` Timeframes: ${status.timeframes.join(", ")}`);
|
|
8939
|
+
console.log(` Interval: ${status.intervalMs / 1e3}s`);
|
|
8940
|
+
console.log(` Last run: ${status.lastRun ? new Date(status.lastRun).toISOString() : "never"}`);
|
|
8941
|
+
console.log(` Total records: ${status.totalRecords.toLocaleString()}`);
|
|
8942
|
+
console.log(` Errors: ${status.errors}`);
|
|
8943
|
+
}
|
|
8944
|
+
var collector;
|
|
8945
|
+
var init_collect = __esm({
|
|
8946
|
+
"src/cli/commands/collect.ts"() {
|
|
8947
|
+
"use strict";
|
|
8948
|
+
init_loader();
|
|
8949
|
+
init_store_factory();
|
|
8950
|
+
init_collector();
|
|
8951
|
+
collector = null;
|
|
8952
|
+
}
|
|
8953
|
+
});
|
|
8954
|
+
|
|
8117
8955
|
// src/tui/components/status-bar.tsx
|
|
8118
8956
|
import React, { useState, useEffect } from "react";
|
|
8119
8957
|
import { Box, Text, Spacer } from "ink";
|
|
@@ -8854,7 +9692,7 @@ function useAIStream() {
|
|
|
8854
9692
|
var init_use_ai_stream = __esm({
|
|
8855
9693
|
"src/tui/hooks/use-ai-stream.ts"() {
|
|
8856
9694
|
"use strict";
|
|
8857
|
-
|
|
9695
|
+
init_client2();
|
|
8858
9696
|
init_chat();
|
|
8859
9697
|
init_tools();
|
|
8860
9698
|
init_context_injector();
|
|
@@ -9406,7 +10244,7 @@ var init_commands3 = __esm({
|
|
|
9406
10244
|
init_constants();
|
|
9407
10245
|
init_binance();
|
|
9408
10246
|
init_agent();
|
|
9409
|
-
|
|
10247
|
+
init_client2();
|
|
9410
10248
|
init_registry2();
|
|
9411
10249
|
init_types();
|
|
9412
10250
|
}
|
|
@@ -9848,7 +10686,7 @@ var init_app = __esm({
|
|
|
9848
10686
|
init_commands3();
|
|
9849
10687
|
init_loader();
|
|
9850
10688
|
init_constants();
|
|
9851
|
-
|
|
10689
|
+
init_client2();
|
|
9852
10690
|
init_tool_handler();
|
|
9853
10691
|
init_tools();
|
|
9854
10692
|
init_binance();
|
|
@@ -9858,12 +10696,12 @@ var init_app = __esm({
|
|
|
9858
10696
|
// src/index.ts
|
|
9859
10697
|
init_loader();
|
|
9860
10698
|
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(
|
|
10699
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10700
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10701
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
10702
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
10703
|
+
var __dirname2 = dirname2(__filename2);
|
|
10704
|
+
var pkg = JSON.parse(readFileSync3(resolve3(__dirname2, "..", "package.json"), "utf-8"));
|
|
9867
10705
|
var program = new Command().name("vizzor").description("Crypto chronovisor \u2014 AI-powered on-chain intelligence").version(pkg.version);
|
|
9868
10706
|
program.hook("preAction", async () => {
|
|
9869
10707
|
await loadConfig();
|
|
@@ -9911,6 +10749,15 @@ botCmd.command("validate").description("Check bot token configuration").action(a
|
|
|
9911
10749
|
const { handleBotValidate: handleBotValidate2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
|
|
9912
10750
|
handleBotValidate2();
|
|
9913
10751
|
});
|
|
10752
|
+
var collectCmd = program.command("collect").description("Data collection pipeline");
|
|
10753
|
+
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) => {
|
|
10754
|
+
const { handleCollectStart: handleCollectStart2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
|
|
10755
|
+
await handleCollectStart2(options);
|
|
10756
|
+
});
|
|
10757
|
+
collectCmd.command("status").description("Show data collection status").action(async () => {
|
|
10758
|
+
const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
|
|
10759
|
+
handleCollectStatus2();
|
|
10760
|
+
});
|
|
9914
10761
|
var args = process.argv.slice(2);
|
|
9915
10762
|
if (args.length === 0) {
|
|
9916
10763
|
await loadConfig();
|