@vizzor/cli 0.10.5 → 0.11.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 +204 -6
- package/dist/index.js +1841 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -74,6 +74,19 @@ var init_schema = __esm({
|
|
|
74
74
|
enabled: z.boolean().default(false),
|
|
75
75
|
webhookUrl: z.string().optional()
|
|
76
76
|
}).default(() => ({ enabled: false })),
|
|
77
|
+
realtime: z.object({
|
|
78
|
+
enabled: z.boolean().default(false),
|
|
79
|
+
provider: z.enum(["binance"]).default("binance"),
|
|
80
|
+
symbols: z.array(z.string()).default([])
|
|
81
|
+
}).optional(),
|
|
82
|
+
trading: z.object({
|
|
83
|
+
enabled: z.boolean().default(false),
|
|
84
|
+
maxSlippage: z.number().default(0.5),
|
|
85
|
+
gasMultiplier: z.number().default(1.2),
|
|
86
|
+
confirmBeforeExecute: z.boolean().default(true),
|
|
87
|
+
dryRun: z.boolean().default(true),
|
|
88
|
+
walletName: z.string().optional()
|
|
89
|
+
}).optional(),
|
|
77
90
|
discordToken: z.string().optional(),
|
|
78
91
|
discordGuildId: z.string().optional(),
|
|
79
92
|
telegramToken: z.string().optional()
|
|
@@ -82,6 +95,14 @@ var init_schema = __esm({
|
|
|
82
95
|
});
|
|
83
96
|
|
|
84
97
|
// src/config/loader.ts
|
|
98
|
+
var loader_exports = {};
|
|
99
|
+
__export(loader_exports, {
|
|
100
|
+
getConfig: () => getConfig,
|
|
101
|
+
getConfigDir: () => getConfigDir,
|
|
102
|
+
getSettableKeys: () => getSettableKeys,
|
|
103
|
+
loadConfig: () => loadConfig,
|
|
104
|
+
saveConfigValue: () => saveConfigValue
|
|
105
|
+
});
|
|
85
106
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
|
|
86
107
|
import { join } from "path";
|
|
87
108
|
import { homedir } from "os";
|
|
@@ -610,7 +631,7 @@ import {
|
|
|
610
631
|
createPublicClient,
|
|
611
632
|
http
|
|
612
633
|
} from "viem";
|
|
613
|
-
import { mainnet, polygon, arbitrum, optimism, base } from "viem/chains";
|
|
634
|
+
import { mainnet, polygon, arbitrum, optimism, base, bsc, avalanche } from "viem/chains";
|
|
614
635
|
var CHAIN_MAP, EvmAdapter;
|
|
615
636
|
var init_adapter = __esm({
|
|
616
637
|
"src/chains/evm/adapter.ts"() {
|
|
@@ -624,7 +645,9 @@ var init_adapter = __esm({
|
|
|
624
645
|
polygon,
|
|
625
646
|
arbitrum,
|
|
626
647
|
optimism,
|
|
627
|
-
base
|
|
648
|
+
base,
|
|
649
|
+
bsc,
|
|
650
|
+
avalanche
|
|
628
651
|
};
|
|
629
652
|
EvmAdapter = class {
|
|
630
653
|
chainId;
|
|
@@ -730,12 +753,12 @@ var init_adapter = __esm({
|
|
|
730
753
|
toBlock: options?.toBlock,
|
|
731
754
|
args: options?.args
|
|
732
755
|
});
|
|
733
|
-
return logs.map((
|
|
734
|
-
eventName:
|
|
735
|
-
blockNumber:
|
|
736
|
-
transactionHash:
|
|
737
|
-
args:
|
|
738
|
-
logIndex:
|
|
756
|
+
return logs.map((log15) => ({
|
|
757
|
+
eventName: log15.eventName ?? eventName,
|
|
758
|
+
blockNumber: log15.blockNumber ?? 0n,
|
|
759
|
+
transactionHash: log15.transactionHash ?? "0x",
|
|
760
|
+
args: log15.args ?? {},
|
|
761
|
+
logIndex: log15.logIndex ?? 0
|
|
739
762
|
}));
|
|
740
763
|
}
|
|
741
764
|
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
@@ -800,6 +823,46 @@ var init_adapter = __esm({
|
|
|
800
823
|
}
|
|
801
824
|
});
|
|
802
825
|
|
|
826
|
+
// src/utils/logger.ts
|
|
827
|
+
import pino from "pino";
|
|
828
|
+
function createLogger(name) {
|
|
829
|
+
const isDev = process.env["NODE_ENV"] !== "production";
|
|
830
|
+
if (isDev) {
|
|
831
|
+
return pino({
|
|
832
|
+
name,
|
|
833
|
+
level,
|
|
834
|
+
transport: {
|
|
835
|
+
target: "pino-pretty",
|
|
836
|
+
options: {
|
|
837
|
+
colorize: true
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
return pino({ name, level });
|
|
843
|
+
}
|
|
844
|
+
var level;
|
|
845
|
+
var init_logger = __esm({
|
|
846
|
+
"src/utils/logger.ts"() {
|
|
847
|
+
"use strict";
|
|
848
|
+
level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// src/chains/evm/writable-adapter.ts
|
|
853
|
+
import { createWalletClient, http as http2 } from "viem";
|
|
854
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
855
|
+
import { mainnet as mainnet2, polygon as polygon2, arbitrum as arbitrum2, optimism as optimism2, base as base2, bsc as bsc2, avalanche as avalanche2 } from "viem/chains";
|
|
856
|
+
var log;
|
|
857
|
+
var init_writable_adapter = __esm({
|
|
858
|
+
"src/chains/evm/writable-adapter.ts"() {
|
|
859
|
+
"use strict";
|
|
860
|
+
init_adapter();
|
|
861
|
+
init_logger();
|
|
862
|
+
log = createLogger("writable-evm");
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
|
|
803
866
|
// src/chains/zk/adapter.ts
|
|
804
867
|
function getZkChainIds() {
|
|
805
868
|
return Object.keys(ZK_CHAIN_CONFIG);
|
|
@@ -857,6 +920,457 @@ var init_adapter2 = __esm({
|
|
|
857
920
|
}
|
|
858
921
|
});
|
|
859
922
|
|
|
923
|
+
// src/chains/solana/adapter.ts
|
|
924
|
+
var log2, SolanaAdapter;
|
|
925
|
+
var init_adapter3 = __esm({
|
|
926
|
+
"src/chains/solana/adapter.ts"() {
|
|
927
|
+
"use strict";
|
|
928
|
+
init_logger();
|
|
929
|
+
log2 = createLogger("solana-adapter");
|
|
930
|
+
SolanaAdapter = class {
|
|
931
|
+
chainId = "solana";
|
|
932
|
+
name = "Solana";
|
|
933
|
+
nativeCurrency = { symbol: "SOL", decimals: 9 };
|
|
934
|
+
rpcUrl = "https://api.mainnet-beta.solana.com";
|
|
935
|
+
connected = false;
|
|
936
|
+
async connect(rpcUrl) {
|
|
937
|
+
if (rpcUrl) this.rpcUrl = rpcUrl;
|
|
938
|
+
this.connected = true;
|
|
939
|
+
log2.info(`Connected to Solana: ${this.rpcUrl}`);
|
|
940
|
+
}
|
|
941
|
+
async disconnect() {
|
|
942
|
+
this.connected = false;
|
|
943
|
+
}
|
|
944
|
+
isConnected() {
|
|
945
|
+
return this.connected;
|
|
946
|
+
}
|
|
947
|
+
async rpc(method, params = []) {
|
|
948
|
+
const res = await fetch(this.rpcUrl, {
|
|
949
|
+
method: "POST",
|
|
950
|
+
headers: { "Content-Type": "application/json" },
|
|
951
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
952
|
+
signal: AbortSignal.timeout(15e3)
|
|
953
|
+
});
|
|
954
|
+
const json = await res.json();
|
|
955
|
+
if (json.error) throw new Error(`Solana RPC: ${json.error.message}`);
|
|
956
|
+
return json.result;
|
|
957
|
+
}
|
|
958
|
+
async getBalance(address) {
|
|
959
|
+
const result = await this.rpc("getBalance", [address]);
|
|
960
|
+
return BigInt(result.value);
|
|
961
|
+
}
|
|
962
|
+
async getTokenBalance(address, tokenAddress) {
|
|
963
|
+
const result = await this.rpc("getTokenAccountsByOwner", [
|
|
964
|
+
address,
|
|
965
|
+
{ mint: tokenAddress },
|
|
966
|
+
{ encoding: "jsonParsed" }
|
|
967
|
+
]);
|
|
968
|
+
if (result.value.length === 0) return 0n;
|
|
969
|
+
return BigInt(result.value[0].account.data.parsed.info.tokenAmount.amount);
|
|
970
|
+
}
|
|
971
|
+
async getTransactionHistory(address, options) {
|
|
972
|
+
const limit = options?.limit ?? 20;
|
|
973
|
+
const sigs = await this.rpc("getSignaturesForAddress", [address, { limit }]);
|
|
974
|
+
return sigs.map((s) => ({
|
|
975
|
+
hash: s.signature,
|
|
976
|
+
blockNumber: BigInt(s.slot),
|
|
977
|
+
from: address,
|
|
978
|
+
to: null,
|
|
979
|
+
value: 0n,
|
|
980
|
+
gasUsed: 0n,
|
|
981
|
+
gasPrice: 0n,
|
|
982
|
+
timestamp: s.blockTime ?? 0,
|
|
983
|
+
status: s.err ? "reverted" : "success",
|
|
984
|
+
input: ""
|
|
985
|
+
}));
|
|
986
|
+
}
|
|
987
|
+
async getTokenTransfers(_address, _options) {
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
async getContractCode(address) {
|
|
991
|
+
const result = await this.rpc("getAccountInfo", [address, { encoding: "base64" }]);
|
|
992
|
+
return result?.value?.data?.[0] ?? "";
|
|
993
|
+
}
|
|
994
|
+
async readContract(_address, _abi, _functionName, _args) {
|
|
995
|
+
throw new Error("readContract not supported on Solana \u2014 use program-specific methods");
|
|
996
|
+
}
|
|
997
|
+
async getContractEvents(_address, _abi, _eventName, _options) {
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
async getTokenInfo(address) {
|
|
1001
|
+
const result = await this.rpc("getAccountInfo", [address, { encoding: "jsonParsed" }]);
|
|
1002
|
+
const info = result?.value?.data?.parsed?.info;
|
|
1003
|
+
return {
|
|
1004
|
+
address,
|
|
1005
|
+
name: "SPL Token",
|
|
1006
|
+
symbol: "SPL",
|
|
1007
|
+
decimals: info?.decimals ?? 9,
|
|
1008
|
+
totalSupply: BigInt(info?.supply ?? "0")
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
async getTopHolders(_tokenAddress, _limit) {
|
|
1012
|
+
return [];
|
|
1013
|
+
}
|
|
1014
|
+
async getBlockNumber() {
|
|
1015
|
+
const slot = await this.rpc("getSlot");
|
|
1016
|
+
return BigInt(slot);
|
|
1017
|
+
}
|
|
1018
|
+
async getBlock(blockNumber) {
|
|
1019
|
+
const result = await this.rpc("getBlock", [
|
|
1020
|
+
Number(blockNumber),
|
|
1021
|
+
{ transactionDetails: "none" }
|
|
1022
|
+
]);
|
|
1023
|
+
return {
|
|
1024
|
+
number: blockNumber,
|
|
1025
|
+
hash: result?.blockhash ?? "",
|
|
1026
|
+
parentHash: "",
|
|
1027
|
+
timestamp: result?.blockTime ?? 0,
|
|
1028
|
+
gasUsed: 0n,
|
|
1029
|
+
gasLimit: 0n,
|
|
1030
|
+
baseFeePerGas: null,
|
|
1031
|
+
transactionCount: result?.transactions?.length ?? 0
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// src/chains/sui/adapter.ts
|
|
1039
|
+
var log3, SuiAdapter;
|
|
1040
|
+
var init_adapter4 = __esm({
|
|
1041
|
+
"src/chains/sui/adapter.ts"() {
|
|
1042
|
+
"use strict";
|
|
1043
|
+
init_logger();
|
|
1044
|
+
log3 = createLogger("sui-adapter");
|
|
1045
|
+
SuiAdapter = class {
|
|
1046
|
+
chainId = "sui";
|
|
1047
|
+
name = "Sui";
|
|
1048
|
+
nativeCurrency = { symbol: "SUI", decimals: 9 };
|
|
1049
|
+
rpcUrl = "https://fullnode.mainnet.sui.io:443";
|
|
1050
|
+
connected = false;
|
|
1051
|
+
async connect(rpcUrl) {
|
|
1052
|
+
if (rpcUrl) this.rpcUrl = rpcUrl;
|
|
1053
|
+
this.connected = true;
|
|
1054
|
+
log3.info(`Connected to Sui: ${this.rpcUrl}`);
|
|
1055
|
+
}
|
|
1056
|
+
async disconnect() {
|
|
1057
|
+
this.connected = false;
|
|
1058
|
+
}
|
|
1059
|
+
isConnected() {
|
|
1060
|
+
return this.connected;
|
|
1061
|
+
}
|
|
1062
|
+
async rpc(method, params = []) {
|
|
1063
|
+
const res = await fetch(this.rpcUrl, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
headers: { "Content-Type": "application/json" },
|
|
1066
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
1067
|
+
signal: AbortSignal.timeout(15e3)
|
|
1068
|
+
});
|
|
1069
|
+
const json = await res.json();
|
|
1070
|
+
if (json.error) throw new Error(`Sui RPC: ${json.error.message}`);
|
|
1071
|
+
return json.result;
|
|
1072
|
+
}
|
|
1073
|
+
async getBalance(address) {
|
|
1074
|
+
const result = await this.rpc("suix_getBalance", [address, "0x2::sui::SUI"]);
|
|
1075
|
+
return BigInt(result.totalBalance);
|
|
1076
|
+
}
|
|
1077
|
+
async getTokenBalance(address, tokenAddress) {
|
|
1078
|
+
const result = await this.rpc("suix_getBalance", [address, tokenAddress]);
|
|
1079
|
+
return BigInt(result.totalBalance);
|
|
1080
|
+
}
|
|
1081
|
+
async getTransactionHistory(address, options) {
|
|
1082
|
+
const limit = options?.limit ?? 20;
|
|
1083
|
+
const result = await this.rpc("suix_queryTransactionBlocks", [
|
|
1084
|
+
{ filter: { FromAddress: address } },
|
|
1085
|
+
null,
|
|
1086
|
+
limit,
|
|
1087
|
+
true
|
|
1088
|
+
]);
|
|
1089
|
+
return (result.data ?? []).map((tx) => ({
|
|
1090
|
+
hash: tx.digest,
|
|
1091
|
+
blockNumber: BigInt(tx.checkpoint ?? "0"),
|
|
1092
|
+
from: address,
|
|
1093
|
+
to: null,
|
|
1094
|
+
value: 0n,
|
|
1095
|
+
gasUsed: 0n,
|
|
1096
|
+
gasPrice: 0n,
|
|
1097
|
+
timestamp: Math.floor(Number(tx.timestampMs) / 1e3),
|
|
1098
|
+
status: "success",
|
|
1099
|
+
input: ""
|
|
1100
|
+
}));
|
|
1101
|
+
}
|
|
1102
|
+
async getTokenTransfers(_address, _options) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
async getContractCode(address) {
|
|
1106
|
+
const result = await this.rpc("sui_getObject", [address, { showContent: true }]);
|
|
1107
|
+
return result.data?.content?.type ?? "";
|
|
1108
|
+
}
|
|
1109
|
+
async readContract(_address, _abi, _functionName, _args) {
|
|
1110
|
+
throw new Error("readContract not supported on Sui \u2014 use Move call methods");
|
|
1111
|
+
}
|
|
1112
|
+
async getContractEvents(_address, _abi, _eventName, _options) {
|
|
1113
|
+
return [];
|
|
1114
|
+
}
|
|
1115
|
+
async getTokenInfo(address) {
|
|
1116
|
+
const result = await this.rpc("suix_getCoinMetadata", [address]);
|
|
1117
|
+
return {
|
|
1118
|
+
address,
|
|
1119
|
+
name: result?.name ?? "Unknown",
|
|
1120
|
+
symbol: result?.symbol ?? "???",
|
|
1121
|
+
decimals: result?.decimals ?? 9,
|
|
1122
|
+
totalSupply: 0n
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
async getTopHolders(_tokenAddress, _limit) {
|
|
1126
|
+
return [];
|
|
1127
|
+
}
|
|
1128
|
+
async getBlockNumber() {
|
|
1129
|
+
const result = await this.rpc("sui_getLatestCheckpointSequenceNumber");
|
|
1130
|
+
return BigInt(result);
|
|
1131
|
+
}
|
|
1132
|
+
async getBlock(blockNumber) {
|
|
1133
|
+
const result = await this.rpc("sui_getCheckpoint", [String(blockNumber)]);
|
|
1134
|
+
return {
|
|
1135
|
+
number: blockNumber,
|
|
1136
|
+
hash: result?.digest ?? "",
|
|
1137
|
+
parentHash: result?.previousDigest ?? "",
|
|
1138
|
+
timestamp: result ? Math.floor(Number(result.timestampMs) / 1e3) : 0,
|
|
1139
|
+
gasUsed: 0n,
|
|
1140
|
+
gasLimit: 0n,
|
|
1141
|
+
baseFeePerGas: null,
|
|
1142
|
+
transactionCount: result?.transactions?.length ?? 0
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
// src/chains/aptos/adapter.ts
|
|
1150
|
+
var log4, AptosAdapter;
|
|
1151
|
+
var init_adapter5 = __esm({
|
|
1152
|
+
"src/chains/aptos/adapter.ts"() {
|
|
1153
|
+
"use strict";
|
|
1154
|
+
init_logger();
|
|
1155
|
+
log4 = createLogger("aptos-adapter");
|
|
1156
|
+
AptosAdapter = class {
|
|
1157
|
+
chainId = "aptos";
|
|
1158
|
+
name = "Aptos";
|
|
1159
|
+
nativeCurrency = { symbol: "APT", decimals: 8 };
|
|
1160
|
+
apiUrl = "https://fullnode.mainnet.aptoslabs.com/v1";
|
|
1161
|
+
connected = false;
|
|
1162
|
+
async connect(rpcUrl) {
|
|
1163
|
+
if (rpcUrl) this.apiUrl = rpcUrl;
|
|
1164
|
+
this.connected = true;
|
|
1165
|
+
log4.info(`Connected to Aptos: ${this.apiUrl}`);
|
|
1166
|
+
}
|
|
1167
|
+
async disconnect() {
|
|
1168
|
+
this.connected = false;
|
|
1169
|
+
}
|
|
1170
|
+
isConnected() {
|
|
1171
|
+
return this.connected;
|
|
1172
|
+
}
|
|
1173
|
+
async api(path) {
|
|
1174
|
+
const res = await fetch(`${this.apiUrl}${path}`, {
|
|
1175
|
+
signal: AbortSignal.timeout(15e3)
|
|
1176
|
+
});
|
|
1177
|
+
if (!res.ok) throw new Error(`Aptos API: ${res.status} ${res.statusText}`);
|
|
1178
|
+
return res.json();
|
|
1179
|
+
}
|
|
1180
|
+
async getBalance(address) {
|
|
1181
|
+
try {
|
|
1182
|
+
const result = await this.api(
|
|
1183
|
+
`/accounts/${address}/resource/0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>`
|
|
1184
|
+
);
|
|
1185
|
+
return BigInt(result.data.coin.value);
|
|
1186
|
+
} catch {
|
|
1187
|
+
return 0n;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async getTokenBalance(address, tokenAddress) {
|
|
1191
|
+
try {
|
|
1192
|
+
const result = await this.api(
|
|
1193
|
+
`/accounts/${address}/resource/0x1::coin::CoinStore<${tokenAddress}>`
|
|
1194
|
+
);
|
|
1195
|
+
return BigInt(result.data.coin.value);
|
|
1196
|
+
} catch {
|
|
1197
|
+
return 0n;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
async getTransactionHistory(address, options) {
|
|
1201
|
+
const limit = options?.limit ?? 20;
|
|
1202
|
+
const txns = await this.api(`/accounts/${address}/transactions?limit=${limit}`);
|
|
1203
|
+
return txns.map((tx) => ({
|
|
1204
|
+
hash: tx.hash,
|
|
1205
|
+
blockNumber: BigInt(tx.version),
|
|
1206
|
+
from: tx.sender,
|
|
1207
|
+
to: null,
|
|
1208
|
+
value: 0n,
|
|
1209
|
+
gasUsed: BigInt(tx.gas_used),
|
|
1210
|
+
gasPrice: BigInt(tx.gas_unit_price),
|
|
1211
|
+
timestamp: Math.floor(Number(tx.timestamp) / 1e6),
|
|
1212
|
+
status: tx.success ? "success" : "reverted",
|
|
1213
|
+
input: ""
|
|
1214
|
+
}));
|
|
1215
|
+
}
|
|
1216
|
+
async getTokenTransfers(_address, _options) {
|
|
1217
|
+
return [];
|
|
1218
|
+
}
|
|
1219
|
+
async getContractCode(address) {
|
|
1220
|
+
try {
|
|
1221
|
+
const result = await this.api(`/accounts/${address}/modules`);
|
|
1222
|
+
return result.map((m) => m.bytecode).join("");
|
|
1223
|
+
} catch {
|
|
1224
|
+
return "";
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
async readContract(_address, _abi, _functionName, _args) {
|
|
1228
|
+
throw new Error("readContract not supported on Aptos \u2014 use Move view functions");
|
|
1229
|
+
}
|
|
1230
|
+
async getContractEvents(_address, _abi, _eventName, _options) {
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
async getTokenInfo(address) {
|
|
1234
|
+
return {
|
|
1235
|
+
address,
|
|
1236
|
+
name: "Move Coin",
|
|
1237
|
+
symbol: "MOVE",
|
|
1238
|
+
decimals: 8,
|
|
1239
|
+
totalSupply: 0n
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
async getTopHolders(_tokenAddress, _limit) {
|
|
1243
|
+
return [];
|
|
1244
|
+
}
|
|
1245
|
+
async getBlockNumber() {
|
|
1246
|
+
const result = await this.api("/");
|
|
1247
|
+
return BigInt(result.ledger_version);
|
|
1248
|
+
}
|
|
1249
|
+
async getBlock(blockNumber) {
|
|
1250
|
+
const result = await this.api(`/blocks/by_version/${blockNumber}`);
|
|
1251
|
+
return {
|
|
1252
|
+
number: BigInt(result.block_height),
|
|
1253
|
+
hash: result.block_hash,
|
|
1254
|
+
parentHash: "",
|
|
1255
|
+
timestamp: Math.floor(Number(result.block_timestamp) / 1e6),
|
|
1256
|
+
gasUsed: 0n,
|
|
1257
|
+
gasLimit: 0n,
|
|
1258
|
+
baseFeePerGas: null,
|
|
1259
|
+
transactionCount: result.transactions?.length ?? 0
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// src/chains/ton/adapter.ts
|
|
1267
|
+
var log5, TonAdapter;
|
|
1268
|
+
var init_adapter6 = __esm({
|
|
1269
|
+
"src/chains/ton/adapter.ts"() {
|
|
1270
|
+
"use strict";
|
|
1271
|
+
init_logger();
|
|
1272
|
+
log5 = createLogger("ton-adapter");
|
|
1273
|
+
TonAdapter = class {
|
|
1274
|
+
chainId = "ton";
|
|
1275
|
+
name = "TON";
|
|
1276
|
+
nativeCurrency = { symbol: "TON", decimals: 9 };
|
|
1277
|
+
apiUrl = "https://toncenter.com/api/v2";
|
|
1278
|
+
connected = false;
|
|
1279
|
+
async connect(rpcUrl) {
|
|
1280
|
+
if (rpcUrl) this.apiUrl = rpcUrl;
|
|
1281
|
+
this.connected = true;
|
|
1282
|
+
log5.info(`Connected to TON: ${this.apiUrl}`);
|
|
1283
|
+
}
|
|
1284
|
+
async disconnect() {
|
|
1285
|
+
this.connected = false;
|
|
1286
|
+
}
|
|
1287
|
+
isConnected() {
|
|
1288
|
+
return this.connected;
|
|
1289
|
+
}
|
|
1290
|
+
async api(method, params = {}) {
|
|
1291
|
+
const qs = new URLSearchParams(params).toString();
|
|
1292
|
+
const url = `${this.apiUrl}/${method}${qs ? `?${qs}` : ""}`;
|
|
1293
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
|
|
1294
|
+
if (!res.ok) throw new Error(`TON API: ${res.status}`);
|
|
1295
|
+
const json = await res.json();
|
|
1296
|
+
if (!json.ok) throw new Error(`TON API: ${json.error ?? "unknown error"}`);
|
|
1297
|
+
return json.result;
|
|
1298
|
+
}
|
|
1299
|
+
async getBalance(address) {
|
|
1300
|
+
const result = await this.api("getAddressBalance", { address });
|
|
1301
|
+
return BigInt(result);
|
|
1302
|
+
}
|
|
1303
|
+
async getTokenBalance(_address, _tokenAddress) {
|
|
1304
|
+
return 0n;
|
|
1305
|
+
}
|
|
1306
|
+
async getTransactionHistory(address, options) {
|
|
1307
|
+
const limit = options?.limit ?? 20;
|
|
1308
|
+
const txns = await this.api("getTransactions", {
|
|
1309
|
+
address,
|
|
1310
|
+
limit: String(limit)
|
|
1311
|
+
});
|
|
1312
|
+
return txns.map((tx) => ({
|
|
1313
|
+
hash: tx.transaction_id.hash,
|
|
1314
|
+
blockNumber: 0n,
|
|
1315
|
+
from: tx.in_msg?.source ?? address,
|
|
1316
|
+
to: tx.in_msg?.destination ?? null,
|
|
1317
|
+
value: BigInt(tx.in_msg?.value ?? "0"),
|
|
1318
|
+
gasUsed: BigInt(tx.fee),
|
|
1319
|
+
gasPrice: 0n,
|
|
1320
|
+
timestamp: tx.utime,
|
|
1321
|
+
status: "success",
|
|
1322
|
+
input: ""
|
|
1323
|
+
}));
|
|
1324
|
+
}
|
|
1325
|
+
async getTokenTransfers(_address, _options) {
|
|
1326
|
+
return [];
|
|
1327
|
+
}
|
|
1328
|
+
async getContractCode(address) {
|
|
1329
|
+
try {
|
|
1330
|
+
const result = await this.api("getAddressInformation", { address });
|
|
1331
|
+
return result.code ?? "";
|
|
1332
|
+
} catch {
|
|
1333
|
+
return "";
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async readContract(_address, _abi, _functionName, _args) {
|
|
1337
|
+
throw new Error("readContract not supported on TON \u2014 use get methods");
|
|
1338
|
+
}
|
|
1339
|
+
async getContractEvents(_address, _abi, _eventName, _options) {
|
|
1340
|
+
return [];
|
|
1341
|
+
}
|
|
1342
|
+
async getTokenInfo(_address) {
|
|
1343
|
+
return {
|
|
1344
|
+
address: _address,
|
|
1345
|
+
name: "Jetton",
|
|
1346
|
+
symbol: "JET",
|
|
1347
|
+
decimals: 9,
|
|
1348
|
+
totalSupply: 0n
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async getTopHolders(_tokenAddress, _limit) {
|
|
1352
|
+
return [];
|
|
1353
|
+
}
|
|
1354
|
+
async getBlockNumber() {
|
|
1355
|
+
const result = await this.api("getMasterchainInfo");
|
|
1356
|
+
return BigInt(result.last.seqno);
|
|
1357
|
+
}
|
|
1358
|
+
async getBlock(blockNumber) {
|
|
1359
|
+
return {
|
|
1360
|
+
number: blockNumber,
|
|
1361
|
+
hash: "",
|
|
1362
|
+
parentHash: "",
|
|
1363
|
+
timestamp: 0,
|
|
1364
|
+
gasUsed: 0n,
|
|
1365
|
+
gasLimit: 0n,
|
|
1366
|
+
baseFeePerGas: null,
|
|
1367
|
+
transactionCount: 0
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
|
|
860
1374
|
// src/chains/registry.ts
|
|
861
1375
|
function getAdapter(chainId) {
|
|
862
1376
|
const factory = registry.get(chainId);
|
|
@@ -875,7 +1389,12 @@ var init_registry = __esm({
|
|
|
875
1389
|
"src/chains/registry.ts"() {
|
|
876
1390
|
"use strict";
|
|
877
1391
|
init_adapter();
|
|
1392
|
+
init_writable_adapter();
|
|
878
1393
|
init_adapter2();
|
|
1394
|
+
init_adapter3();
|
|
1395
|
+
init_adapter4();
|
|
1396
|
+
init_adapter5();
|
|
1397
|
+
init_adapter6();
|
|
879
1398
|
registry = /* @__PURE__ */ new Map();
|
|
880
1399
|
evmFactory = (chainId) => new EvmAdapter(chainId);
|
|
881
1400
|
BUILTIN_EVM_CHAINS = ["ethereum", "polygon", "arbitrum", "optimism", "base"];
|
|
@@ -886,32 +1405,12 @@ var init_registry = __esm({
|
|
|
886
1405
|
for (const chainId of getZkChainIds()) {
|
|
887
1406
|
registry.set(chainId, zkFactory);
|
|
888
1407
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const isDev = process.env["NODE_ENV"] !== "production";
|
|
896
|
-
if (isDev) {
|
|
897
|
-
return pino({
|
|
898
|
-
name,
|
|
899
|
-
level,
|
|
900
|
-
transport: {
|
|
901
|
-
target: "pino-pretty",
|
|
902
|
-
options: {
|
|
903
|
-
colorize: true
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
return pino({ name, level });
|
|
909
|
-
}
|
|
910
|
-
var level;
|
|
911
|
-
var init_logger = __esm({
|
|
912
|
-
"src/utils/logger.ts"() {
|
|
913
|
-
"use strict";
|
|
914
|
-
level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
|
|
1408
|
+
registry.set("bsc", evmFactory);
|
|
1409
|
+
registry.set("avalanche", evmFactory);
|
|
1410
|
+
registry.set("solana", () => new SolanaAdapter());
|
|
1411
|
+
registry.set("sui", () => new SuiAdapter());
|
|
1412
|
+
registry.set("aptos", () => new AptosAdapter());
|
|
1413
|
+
registry.set("ton", () => new TonAdapter());
|
|
915
1414
|
}
|
|
916
1415
|
});
|
|
917
1416
|
|
|
@@ -923,12 +1422,12 @@ function initMLClient(url) {
|
|
|
923
1422
|
function getMLClient() {
|
|
924
1423
|
return mlClient;
|
|
925
1424
|
}
|
|
926
|
-
var
|
|
1425
|
+
var log6, MLClient, mlClient;
|
|
927
1426
|
var init_client = __esm({
|
|
928
1427
|
"src/ml/client.ts"() {
|
|
929
1428
|
"use strict";
|
|
930
1429
|
init_logger();
|
|
931
|
-
|
|
1430
|
+
log6 = createLogger("ml-client");
|
|
932
1431
|
MLClient = class {
|
|
933
1432
|
baseUrl;
|
|
934
1433
|
healthy = false;
|
|
@@ -946,7 +1445,7 @@ var init_client = __esm({
|
|
|
946
1445
|
if (!res.ok) return null;
|
|
947
1446
|
return await res.json();
|
|
948
1447
|
} catch (err) {
|
|
949
|
-
|
|
1448
|
+
log6.debug(`ML predict failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
950
1449
|
return null;
|
|
951
1450
|
}
|
|
952
1451
|
}
|
|
@@ -962,7 +1461,7 @@ var init_client = __esm({
|
|
|
962
1461
|
const data = await res.json();
|
|
963
1462
|
return data.predictions;
|
|
964
1463
|
} catch (err) {
|
|
965
|
-
|
|
1464
|
+
log6.debug(`ML batch predict failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
966
1465
|
return [];
|
|
967
1466
|
}
|
|
968
1467
|
}
|
|
@@ -978,7 +1477,7 @@ var init_client = __esm({
|
|
|
978
1477
|
const data = await res.json();
|
|
979
1478
|
return data.anomalies;
|
|
980
1479
|
} catch (err) {
|
|
981
|
-
|
|
1480
|
+
log6.debug(`ML anomaly detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
982
1481
|
return [];
|
|
983
1482
|
}
|
|
984
1483
|
}
|
|
@@ -1005,7 +1504,7 @@ var init_client = __esm({
|
|
|
1005
1504
|
if (!res.ok) return null;
|
|
1006
1505
|
return await res.json();
|
|
1007
1506
|
} catch (err) {
|
|
1008
|
-
|
|
1507
|
+
log6.debug(`ML rug predict failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1009
1508
|
return null;
|
|
1010
1509
|
}
|
|
1011
1510
|
}
|
|
@@ -1020,7 +1519,7 @@ var init_client = __esm({
|
|
|
1020
1519
|
if (!res.ok) return null;
|
|
1021
1520
|
return await res.json();
|
|
1022
1521
|
} catch (err) {
|
|
1023
|
-
|
|
1522
|
+
log6.debug(`ML wallet classify failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1024
1523
|
return null;
|
|
1025
1524
|
}
|
|
1026
1525
|
}
|
|
@@ -1035,7 +1534,7 @@ var init_client = __esm({
|
|
|
1035
1534
|
if (!res.ok) return null;
|
|
1036
1535
|
return await res.json();
|
|
1037
1536
|
} catch (err) {
|
|
1038
|
-
|
|
1537
|
+
log6.debug(`ML sentiment failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1039
1538
|
return null;
|
|
1040
1539
|
}
|
|
1041
1540
|
}
|
|
@@ -1051,7 +1550,7 @@ var init_client = __esm({
|
|
|
1051
1550
|
const data = await res.json();
|
|
1052
1551
|
return data.results;
|
|
1053
1552
|
} catch (err) {
|
|
1054
|
-
|
|
1553
|
+
log6.debug(`ML sentiment batch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1055
1554
|
return [];
|
|
1056
1555
|
}
|
|
1057
1556
|
}
|
|
@@ -1069,7 +1568,7 @@ var init_client = __esm({
|
|
|
1069
1568
|
if (!res.ok) return null;
|
|
1070
1569
|
return await res.json();
|
|
1071
1570
|
} catch (err) {
|
|
1072
|
-
|
|
1571
|
+
log6.debug(`ML trend score failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1073
1572
|
return null;
|
|
1074
1573
|
}
|
|
1075
1574
|
}
|
|
@@ -1084,7 +1583,7 @@ var init_client = __esm({
|
|
|
1084
1583
|
if (!res.ok) return null;
|
|
1085
1584
|
return await res.json();
|
|
1086
1585
|
} catch (err) {
|
|
1087
|
-
|
|
1586
|
+
log6.debug(`ML TA interpret failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1088
1587
|
return null;
|
|
1089
1588
|
}
|
|
1090
1589
|
}
|
|
@@ -1099,7 +1598,7 @@ var init_client = __esm({
|
|
|
1099
1598
|
if (!res.ok) return null;
|
|
1100
1599
|
return await res.json();
|
|
1101
1600
|
} catch (err) {
|
|
1102
|
-
|
|
1601
|
+
log6.debug(`ML strategy eval failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1103
1602
|
return null;
|
|
1104
1603
|
}
|
|
1105
1604
|
}
|
|
@@ -1114,7 +1613,7 @@ var init_client = __esm({
|
|
|
1114
1613
|
if (!res.ok) return null;
|
|
1115
1614
|
return await res.json();
|
|
1116
1615
|
} catch (err) {
|
|
1117
|
-
|
|
1616
|
+
log6.debug(`ML regime detect failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1118
1617
|
return null;
|
|
1119
1618
|
}
|
|
1120
1619
|
}
|
|
@@ -1129,7 +1628,7 @@ var init_client = __esm({
|
|
|
1129
1628
|
if (!res.ok) return null;
|
|
1130
1629
|
return await res.json();
|
|
1131
1630
|
} catch (err) {
|
|
1132
|
-
|
|
1631
|
+
log6.debug(`ML project risk failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1133
1632
|
return null;
|
|
1134
1633
|
}
|
|
1135
1634
|
}
|
|
@@ -1144,7 +1643,7 @@ var init_client = __esm({
|
|
|
1144
1643
|
if (!res.ok) return null;
|
|
1145
1644
|
return await res.json();
|
|
1146
1645
|
} catch (err) {
|
|
1147
|
-
|
|
1646
|
+
log6.debug(`ML portfolio opt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1148
1647
|
return null;
|
|
1149
1648
|
}
|
|
1150
1649
|
}
|
|
@@ -1159,7 +1658,7 @@ var init_client = __esm({
|
|
|
1159
1658
|
if (!res.ok) return null;
|
|
1160
1659
|
return await res.json();
|
|
1161
1660
|
} catch (err) {
|
|
1162
|
-
|
|
1661
|
+
log6.debug(`ML intent classify failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1163
1662
|
return null;
|
|
1164
1663
|
}
|
|
1165
1664
|
}
|
|
@@ -1174,7 +1673,7 @@ var init_client = __esm({
|
|
|
1174
1673
|
if (!res.ok) return null;
|
|
1175
1674
|
return await res.json();
|
|
1176
1675
|
} catch (err) {
|
|
1177
|
-
|
|
1676
|
+
log6.debug(`ML bytecode risk failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1178
1677
|
return null;
|
|
1179
1678
|
}
|
|
1180
1679
|
}
|
|
@@ -1189,7 +1688,38 @@ var init_client = __esm({
|
|
|
1189
1688
|
if (!res.ok) return null;
|
|
1190
1689
|
return await res.json();
|
|
1191
1690
|
} catch (err) {
|
|
1192
|
-
|
|
1691
|
+
log6.debug(`ML portfolio forward failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
async trainModel(modelName, params) {
|
|
1696
|
+
try {
|
|
1697
|
+
const res = await fetch(`${this.baseUrl}/train`, {
|
|
1698
|
+
method: "POST",
|
|
1699
|
+
headers: { "Content-Type": "application/json" },
|
|
1700
|
+
body: JSON.stringify({ model: modelName, ...params }),
|
|
1701
|
+
signal: AbortSignal.timeout(3e5)
|
|
1702
|
+
// 5 min for training
|
|
1703
|
+
});
|
|
1704
|
+
if (!res.ok) return null;
|
|
1705
|
+
return await res.json();
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
log6.debug(`ML train failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async evaluateModel(modelName) {
|
|
1712
|
+
try {
|
|
1713
|
+
const res = await fetch(`${this.baseUrl}/evaluate`, {
|
|
1714
|
+
method: "POST",
|
|
1715
|
+
headers: { "Content-Type": "application/json" },
|
|
1716
|
+
body: JSON.stringify({ model: modelName }),
|
|
1717
|
+
signal: AbortSignal.timeout(6e4)
|
|
1718
|
+
});
|
|
1719
|
+
if (!res.ok) return null;
|
|
1720
|
+
return await res.json();
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
log6.debug(`ML evaluate failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1193
1723
|
return null;
|
|
1194
1724
|
}
|
|
1195
1725
|
}
|
|
@@ -1583,9 +2113,18 @@ async function fetchCryptoNews(symbol, apiToken) {
|
|
|
1583
2113
|
const url = `${BASE_URL2}/posts/?${params.toString()}`;
|
|
1584
2114
|
const res = await fetch(url);
|
|
1585
2115
|
if (!res.ok) {
|
|
1586
|
-
|
|
2116
|
+
return [];
|
|
2117
|
+
}
|
|
2118
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
2119
|
+
if (!contentType.includes("application/json")) {
|
|
2120
|
+
return [];
|
|
2121
|
+
}
|
|
2122
|
+
let data;
|
|
2123
|
+
try {
|
|
2124
|
+
data = await res.json();
|
|
2125
|
+
} catch {
|
|
2126
|
+
return [];
|
|
1587
2127
|
}
|
|
1588
|
-
const data = await res.json();
|
|
1589
2128
|
const posts = data.results ?? [];
|
|
1590
2129
|
return posts.map((post) => {
|
|
1591
2130
|
const { positive, negative } = post.votes;
|
|
@@ -4407,7 +4946,10 @@ var init_goplus = __esm({
|
|
|
4407
4946
|
optimism: "10",
|
|
4408
4947
|
base: "8453",
|
|
4409
4948
|
avalanche: "43114",
|
|
4410
|
-
solana: "solana"
|
|
4949
|
+
solana: "solana",
|
|
4950
|
+
sui: "sui",
|
|
4951
|
+
aptos: "aptos",
|
|
4952
|
+
ton: "ton"
|
|
4411
4953
|
};
|
|
4412
4954
|
}
|
|
4413
4955
|
});
|
|
@@ -4709,7 +5251,7 @@ async function buildContextBlock(userMessage) {
|
|
|
4709
5251
|
return wrapUntrustedData("MARKET_CONTEXT", raw);
|
|
4710
5252
|
}
|
|
4711
5253
|
function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
4712
|
-
const
|
|
5254
|
+
const base3 = [
|
|
4713
5255
|
"CRITICAL INSTRUCTIONS (MUST FOLLOW):",
|
|
4714
5256
|
'- NEVER output "--- REAL-TIME DATA ---" or "--- END REAL-TIME DATA ---" markers.',
|
|
4715
5257
|
"- You MUST use ONLY the real-time data above. Do NOT invent or fabricate any data.",
|
|
@@ -4718,7 +5260,7 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4718
5260
|
];
|
|
4719
5261
|
switch (queryType) {
|
|
4720
5262
|
case "news":
|
|
4721
|
-
|
|
5263
|
+
base3.push(
|
|
4722
5264
|
"",
|
|
4723
5265
|
"QUERY TYPE: NEWS \u2014 The user wants crypto news and market updates.",
|
|
4724
5266
|
"- Summarize the news headlines from the data above.",
|
|
@@ -4729,7 +5271,7 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4729
5271
|
);
|
|
4730
5272
|
break;
|
|
4731
5273
|
case "trends":
|
|
4732
|
-
|
|
5274
|
+
base3.push(
|
|
4733
5275
|
"",
|
|
4734
5276
|
"QUERY TYPE: TRENDS/MARKET OVERVIEW \u2014 The user wants to know what is trending.",
|
|
4735
5277
|
"- Lead with the overall market sentiment (Fear & Greed + BTC direction).",
|
|
@@ -4740,7 +5282,7 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4740
5282
|
);
|
|
4741
5283
|
break;
|
|
4742
5284
|
case "token_analysis":
|
|
4743
|
-
|
|
5285
|
+
base3.push(
|
|
4744
5286
|
"",
|
|
4745
5287
|
"QUERY TYPE: TOKEN ANALYSIS \u2014 The user wants deep analysis of a specific token.",
|
|
4746
5288
|
"- Follow the analysis report structure above (Verdict \u2192 Market \u2192 Security \u2192 Signals \u2192 Risks).",
|
|
@@ -4751,7 +5293,7 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4751
5293
|
);
|
|
4752
5294
|
break;
|
|
4753
5295
|
case "prediction":
|
|
4754
|
-
|
|
5296
|
+
base3.push(
|
|
4755
5297
|
"",
|
|
4756
5298
|
"QUERY TYPE: PRICE PREDICTION \u2014 The user wants future price projections.",
|
|
4757
5299
|
"- ALWAYS include the PRICE PREDICTION SCENARIOS with the exact dollar values from the data.",
|
|
@@ -4764,7 +5306,7 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4764
5306
|
);
|
|
4765
5307
|
break;
|
|
4766
5308
|
default:
|
|
4767
|
-
|
|
5309
|
+
base3.push(
|
|
4768
5310
|
"",
|
|
4769
5311
|
"QUERY TYPE: GENERAL \u2014 Answer naturally using the data above as context.",
|
|
4770
5312
|
"- Use the real-time prices and sentiment as background context.",
|
|
@@ -4775,12 +5317,12 @@ function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
|
4775
5317
|
break;
|
|
4776
5318
|
}
|
|
4777
5319
|
if (isComplexQuery && hasSpecificToken) {
|
|
4778
|
-
|
|
5320
|
+
base3.push(
|
|
4779
5321
|
"",
|
|
4780
5322
|
"COMPLEX ANALYSIS: Structure your reasoning as Data \u2192 Signals \u2192 Alignment \u2192 Confidence \u2192 Risks \u2192 Conclusion."
|
|
4781
5323
|
);
|
|
4782
5324
|
}
|
|
4783
|
-
return
|
|
5325
|
+
return base3;
|
|
4784
5326
|
}
|
|
4785
5327
|
async function fetchTokenData(tokens, address) {
|
|
4786
5328
|
const lines = ["## Token / Price Data"];
|
|
@@ -6720,7 +7262,7 @@ var init_engine = __esm({
|
|
|
6720
7262
|
for (const symbol of this.state.config.pairs) {
|
|
6721
7263
|
try {
|
|
6722
7264
|
const signals = await this.gatherSignals(symbol);
|
|
6723
|
-
const decision = this.strategy.evaluate(signals);
|
|
7265
|
+
const decision = this.strategy.evaluateAsync ? await this.strategy.evaluateAsync(signals, symbol) : this.strategy.evaluate(signals);
|
|
6724
7266
|
const result = {
|
|
6725
7267
|
agentId: this.state.config.id,
|
|
6726
7268
|
symbol,
|
|
@@ -7017,6 +7559,60 @@ function evaluateWithRules(signals) {
|
|
|
7017
7559
|
reasoning: [...reasoning, "Mixed signals \u2014 holding"]
|
|
7018
7560
|
};
|
|
7019
7561
|
}
|
|
7562
|
+
async function evaluateWithML(symbol, signals) {
|
|
7563
|
+
const mlClient2 = getMLClient();
|
|
7564
|
+
if (!mlClient2) return evaluateWithRules(signals);
|
|
7565
|
+
try {
|
|
7566
|
+
const regime = await detectMarketRegime(symbol, {
|
|
7567
|
+
returns_1d: signals.priceChange24h ?? 0,
|
|
7568
|
+
returns_7d: 0,
|
|
7569
|
+
volatility_14d: signals.atr !== null && signals.price !== null && signals.price > 0 ? signals.atr / signals.price * 100 : 3,
|
|
7570
|
+
volume_ratio: 1,
|
|
7571
|
+
rsi: signals.rsi ?? 50,
|
|
7572
|
+
bb_width: 0,
|
|
7573
|
+
fear_greed: signals.fearGreed ?? 50,
|
|
7574
|
+
funding_rate: signals.fundingRate ?? 0,
|
|
7575
|
+
price_vs_sma200: 0
|
|
7576
|
+
});
|
|
7577
|
+
const strategyResult = await mlClient2.evaluateStrategy({
|
|
7578
|
+
rsi: signals.rsi ?? 50,
|
|
7579
|
+
macd_histogram: signals.macdHistogram ?? 0,
|
|
7580
|
+
ema12: signals.ema12 ?? 0,
|
|
7581
|
+
ema26: signals.ema26 ?? 0,
|
|
7582
|
+
bollinger_pct_b: signals.bollingerPercentB ?? 0.5,
|
|
7583
|
+
atr: signals.atr ?? 0,
|
|
7584
|
+
obv: signals.obv ?? 0,
|
|
7585
|
+
funding_rate: signals.fundingRate ?? 0,
|
|
7586
|
+
fear_greed: signals.fearGreed ?? 50,
|
|
7587
|
+
price_change_24h: signals.priceChange24h ?? 0,
|
|
7588
|
+
price: signals.price ?? 0,
|
|
7589
|
+
regime: regime.regime
|
|
7590
|
+
});
|
|
7591
|
+
if (strategyResult && strategyResult.confidence > 60) {
|
|
7592
|
+
return {
|
|
7593
|
+
action: strategyResult.action,
|
|
7594
|
+
confidence: Math.round(strategyResult.confidence),
|
|
7595
|
+
reasoning: [
|
|
7596
|
+
`Regime: ${regime.regime} (${regime.confidence.toFixed(0)}% confidence)`,
|
|
7597
|
+
...strategyResult.reasoning
|
|
7598
|
+
]
|
|
7599
|
+
};
|
|
7600
|
+
}
|
|
7601
|
+
const ruleDecision = evaluateWithRules(signals);
|
|
7602
|
+
return {
|
|
7603
|
+
action: ruleDecision.action,
|
|
7604
|
+
confidence: Math.round(((strategyResult?.confidence ?? 0) + ruleDecision.confidence) / 2),
|
|
7605
|
+
reasoning: [
|
|
7606
|
+
`Regime: ${regime.regime}`,
|
|
7607
|
+
...strategyResult?.reasoning ?? [],
|
|
7608
|
+
"(Low ML confidence \u2014 blending with rules)",
|
|
7609
|
+
...ruleDecision.reasoning
|
|
7610
|
+
]
|
|
7611
|
+
};
|
|
7612
|
+
} catch {
|
|
7613
|
+
return evaluateWithRules(signals);
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
7020
7616
|
var mlAdaptiveStrategy;
|
|
7021
7617
|
var init_ml_adaptive = __esm({
|
|
7022
7618
|
"src/core/agent/strategies/ml-adaptive.ts"() {
|
|
@@ -7028,6 +7624,9 @@ var init_ml_adaptive = __esm({
|
|
|
7028
7624
|
description: "ML-powered strategy using contextual bandit approach. Uses LSTM/RF predictions when available, falls back to rule-based when < 100 training samples.",
|
|
7029
7625
|
evaluate(signals) {
|
|
7030
7626
|
return evaluateWithRules(signals);
|
|
7627
|
+
},
|
|
7628
|
+
async evaluateAsync(signals, symbol) {
|
|
7629
|
+
return evaluateWithML(symbol, signals);
|
|
7031
7630
|
}
|
|
7032
7631
|
};
|
|
7033
7632
|
}
|
|
@@ -7075,6 +7674,10 @@ function ensureAgentTables() {
|
|
|
7075
7674
|
function createAgent(name, strategy, pairs, interval = 60) {
|
|
7076
7675
|
ensureAgentTables();
|
|
7077
7676
|
getStrategy(strategy);
|
|
7677
|
+
const existing = getAgentByName(name);
|
|
7678
|
+
if (existing) {
|
|
7679
|
+
throw new Error(`Agent "${name}" already exists. Delete it first or choose a different name.`);
|
|
7680
|
+
}
|
|
7078
7681
|
const id = randomUUID();
|
|
7079
7682
|
const now = Date.now();
|
|
7080
7683
|
getDb().prepare(
|
|
@@ -7131,8 +7734,8 @@ function deleteAgent(id) {
|
|
|
7131
7734
|
engine.stop();
|
|
7132
7735
|
engines.delete(id);
|
|
7133
7736
|
}
|
|
7134
|
-
const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
7135
7737
|
getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
|
|
7738
|
+
const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
7136
7739
|
return result.changes > 0;
|
|
7137
7740
|
}
|
|
7138
7741
|
function startAgent(id) {
|
|
@@ -7152,7 +7755,11 @@ function startAgent(id) {
|
|
|
7152
7755
|
}
|
|
7153
7756
|
function stopAgent(id) {
|
|
7154
7757
|
const engine = engines.get(id);
|
|
7155
|
-
if (!engine)
|
|
7758
|
+
if (!engine) {
|
|
7759
|
+
const config2 = getAgentById(id);
|
|
7760
|
+
if (!config2) throw new Error(`Agent not found: ${id}`);
|
|
7761
|
+
return { config: config2, status: "idle", lastCycle: null, cycleCount: 0, error: null };
|
|
7762
|
+
}
|
|
7156
7763
|
engine.stop();
|
|
7157
7764
|
return engine.getState();
|
|
7158
7765
|
}
|
|
@@ -7239,8 +7846,8 @@ var init_manager = __esm({
|
|
|
7239
7846
|
});
|
|
7240
7847
|
|
|
7241
7848
|
// src/core/agent/wallet.ts
|
|
7242
|
-
import { createPublicClient as createPublicClient2, http as
|
|
7243
|
-
import { mainnet as
|
|
7849
|
+
import { createPublicClient as createPublicClient2, http as http3, formatEther } from "viem";
|
|
7850
|
+
import { mainnet as mainnet3 } from "viem/chains";
|
|
7244
7851
|
async function getWalletBalance(address) {
|
|
7245
7852
|
const balance = await client.getBalance({ address });
|
|
7246
7853
|
return formatEther(balance);
|
|
@@ -7253,8 +7860,8 @@ var init_wallet = __esm({
|
|
|
7253
7860
|
"src/core/agent/wallet.ts"() {
|
|
7254
7861
|
"use strict";
|
|
7255
7862
|
client = createPublicClient2({
|
|
7256
|
-
chain:
|
|
7257
|
-
transport:
|
|
7863
|
+
chain: mainnet3,
|
|
7864
|
+
transport: http3()
|
|
7258
7865
|
});
|
|
7259
7866
|
}
|
|
7260
7867
|
});
|
|
@@ -7699,6 +8306,170 @@ var init_store_factory = __esm({
|
|
|
7699
8306
|
}
|
|
7700
8307
|
});
|
|
7701
8308
|
|
|
8309
|
+
// src/core/backtest/engine.ts
|
|
8310
|
+
var engine_exports = {};
|
|
8311
|
+
__export(engine_exports, {
|
|
8312
|
+
BacktestEngine: () => BacktestEngine
|
|
8313
|
+
});
|
|
8314
|
+
var log7, BacktestEngine;
|
|
8315
|
+
var init_engine2 = __esm({
|
|
8316
|
+
"src/core/backtest/engine.ts"() {
|
|
8317
|
+
"use strict";
|
|
8318
|
+
init_binance();
|
|
8319
|
+
init_manager();
|
|
8320
|
+
init_logger();
|
|
8321
|
+
log7 = createLogger("backtest");
|
|
8322
|
+
BacktestEngine = class {
|
|
8323
|
+
config;
|
|
8324
|
+
constructor(config2) {
|
|
8325
|
+
this.config = config2;
|
|
8326
|
+
}
|
|
8327
|
+
async run() {
|
|
8328
|
+
log7.info(
|
|
8329
|
+
`Backtest: ${this.config.strategy} on ${this.config.pair} (${this.config.from} \u2192 ${this.config.to})`
|
|
8330
|
+
);
|
|
8331
|
+
const strategy = getStrategy(this.config.strategy);
|
|
8332
|
+
const klines = await fetchKlines(this.config.pair, this.config.timeframe, 1e3);
|
|
8333
|
+
const fromTs = new Date(this.config.from).getTime();
|
|
8334
|
+
const toTs = new Date(this.config.to).getTime();
|
|
8335
|
+
const filtered = klines.filter((k) => k.openTime >= fromTs && k.openTime <= toTs);
|
|
8336
|
+
if (filtered.length === 0) {
|
|
8337
|
+
return this.emptyResult();
|
|
8338
|
+
}
|
|
8339
|
+
const trades = [];
|
|
8340
|
+
const equityCurve = [];
|
|
8341
|
+
const drawdownCurve = [];
|
|
8342
|
+
let cash = this.config.initialCapital;
|
|
8343
|
+
let position = null;
|
|
8344
|
+
let peak = cash;
|
|
8345
|
+
for (const candle of filtered) {
|
|
8346
|
+
const price = candle.close;
|
|
8347
|
+
const equity = position ? cash + position.size * price : cash;
|
|
8348
|
+
if (equity > peak) peak = equity;
|
|
8349
|
+
const drawdown = peak > 0 ? (peak - equity) / peak * 100 : 0;
|
|
8350
|
+
equityCurve.push({ time: candle.openTime, equity });
|
|
8351
|
+
drawdownCurve.push({ time: candle.openTime, drawdown });
|
|
8352
|
+
const signals = {
|
|
8353
|
+
rsi: null,
|
|
8354
|
+
macdHistogram: null,
|
|
8355
|
+
ema12: null,
|
|
8356
|
+
ema26: null,
|
|
8357
|
+
bollingerPercentB: null,
|
|
8358
|
+
atr: null,
|
|
8359
|
+
obv: null,
|
|
8360
|
+
fundingRate: null,
|
|
8361
|
+
fearGreed: null,
|
|
8362
|
+
priceChange24h: null,
|
|
8363
|
+
price
|
|
8364
|
+
};
|
|
8365
|
+
const decision = strategy.evaluate(signals);
|
|
8366
|
+
if (decision.action === "buy" && !position && cash > 0) {
|
|
8367
|
+
const size = cash * 0.95 / price;
|
|
8368
|
+
const commission = cash * 0.95 * (this.config.commissionPct / 100);
|
|
8369
|
+
cash -= size * price + commission;
|
|
8370
|
+
position = { side: "long", entryPrice: price, entryTime: candle.openTime, size };
|
|
8371
|
+
} else if (decision.action === "sell" && position) {
|
|
8372
|
+
const exitValue = position.size * price;
|
|
8373
|
+
const commission = exitValue * (this.config.commissionPct / 100);
|
|
8374
|
+
const pnl = exitValue - position.size * position.entryPrice - commission;
|
|
8375
|
+
const pnlPct = pnl / (position.size * position.entryPrice) * 100;
|
|
8376
|
+
trades.push({
|
|
8377
|
+
entryTime: position.entryTime,
|
|
8378
|
+
exitTime: candle.openTime,
|
|
8379
|
+
side: position.side,
|
|
8380
|
+
entryPrice: position.entryPrice,
|
|
8381
|
+
exitPrice: price,
|
|
8382
|
+
size: position.size,
|
|
8383
|
+
pnl,
|
|
8384
|
+
pnlPct,
|
|
8385
|
+
reason: decision.reasoning.join("; ")
|
|
8386
|
+
});
|
|
8387
|
+
cash += exitValue - commission;
|
|
8388
|
+
position = null;
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
if (position && filtered.length > 0) {
|
|
8392
|
+
const lastPrice = filtered[filtered.length - 1].close;
|
|
8393
|
+
const exitValue = position.size * lastPrice;
|
|
8394
|
+
const pnl = exitValue - position.size * position.entryPrice;
|
|
8395
|
+
trades.push({
|
|
8396
|
+
entryTime: position.entryTime,
|
|
8397
|
+
exitTime: filtered[filtered.length - 1].openTime,
|
|
8398
|
+
side: position.side,
|
|
8399
|
+
entryPrice: position.entryPrice,
|
|
8400
|
+
exitPrice: lastPrice,
|
|
8401
|
+
size: position.size,
|
|
8402
|
+
pnl,
|
|
8403
|
+
pnlPct: pnl / (position.size * position.entryPrice) * 100,
|
|
8404
|
+
reason: "End of backtest period"
|
|
8405
|
+
});
|
|
8406
|
+
cash += exitValue;
|
|
8407
|
+
}
|
|
8408
|
+
const metrics = this.calculateMetrics(trades, cash);
|
|
8409
|
+
return {
|
|
8410
|
+
config: this.config,
|
|
8411
|
+
trades,
|
|
8412
|
+
metrics,
|
|
8413
|
+
equityCurve,
|
|
8414
|
+
drawdownCurve
|
|
8415
|
+
};
|
|
8416
|
+
}
|
|
8417
|
+
calculateMetrics(trades, finalCash) {
|
|
8418
|
+
const wins = trades.filter((t) => t.pnl > 0);
|
|
8419
|
+
const losses = trades.filter((t) => t.pnl <= 0);
|
|
8420
|
+
const totalReturn = finalCash - this.config.initialCapital;
|
|
8421
|
+
const totalReturnPct = totalReturn / this.config.initialCapital * 100;
|
|
8422
|
+
const winRate = trades.length > 0 ? wins.length / trades.length : 0;
|
|
8423
|
+
const grossProfit = wins.reduce((sum, t) => sum + t.pnl, 0);
|
|
8424
|
+
const grossLoss = Math.abs(losses.reduce((sum, t) => sum + t.pnl, 0));
|
|
8425
|
+
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : grossProfit > 0 ? Infinity : 0;
|
|
8426
|
+
const returns = trades.map((t) => t.pnlPct / 100);
|
|
8427
|
+
const avgReturn = returns.length > 0 ? returns.reduce((a, b) => a + b, 0) / returns.length : 0;
|
|
8428
|
+
const variance = returns.length > 1 ? returns.reduce((s, r) => s + (r - avgReturn) ** 2, 0) / (returns.length - 1) : 0;
|
|
8429
|
+
const sharpeRatio = Math.sqrt(variance) > 0 ? avgReturn / Math.sqrt(variance) * Math.sqrt(365) : 0;
|
|
8430
|
+
let maxDrawdown = 0;
|
|
8431
|
+
let peak = this.config.initialCapital;
|
|
8432
|
+
let running = this.config.initialCapital;
|
|
8433
|
+
for (const t of trades) {
|
|
8434
|
+
running += t.pnl;
|
|
8435
|
+
if (running > peak) peak = running;
|
|
8436
|
+
const dd = peak > 0 ? (peak - running) / peak * 100 : 0;
|
|
8437
|
+
if (dd > maxDrawdown) maxDrawdown = dd;
|
|
8438
|
+
}
|
|
8439
|
+
const avgHoldingPeriodMs = trades.length > 0 ? trades.reduce((sum, t) => sum + (t.exitTime - t.entryTime), 0) / trades.length : 0;
|
|
8440
|
+
return {
|
|
8441
|
+
totalReturn,
|
|
8442
|
+
totalReturnPct,
|
|
8443
|
+
winRate,
|
|
8444
|
+
totalTrades: trades.length,
|
|
8445
|
+
profitFactor,
|
|
8446
|
+
sharpeRatio,
|
|
8447
|
+
maxDrawdown,
|
|
8448
|
+
avgHoldingPeriodMs
|
|
8449
|
+
};
|
|
8450
|
+
}
|
|
8451
|
+
emptyResult() {
|
|
8452
|
+
return {
|
|
8453
|
+
config: this.config,
|
|
8454
|
+
trades: [],
|
|
8455
|
+
metrics: {
|
|
8456
|
+
totalReturn: 0,
|
|
8457
|
+
totalReturnPct: 0,
|
|
8458
|
+
winRate: 0,
|
|
8459
|
+
totalTrades: 0,
|
|
8460
|
+
profitFactor: 0,
|
|
8461
|
+
sharpeRatio: 0,
|
|
8462
|
+
maxDrawdown: 0,
|
|
8463
|
+
avgHoldingPeriodMs: 0
|
|
8464
|
+
},
|
|
8465
|
+
equityCurve: [],
|
|
8466
|
+
drawdownCurve: []
|
|
8467
|
+
};
|
|
8468
|
+
}
|
|
8469
|
+
};
|
|
8470
|
+
}
|
|
8471
|
+
});
|
|
8472
|
+
|
|
7702
8473
|
// src/ai/tool-handler.ts
|
|
7703
8474
|
async function handleTool(name, input) {
|
|
7704
8475
|
const raw = await handleToolUnsafe(name, input);
|
|
@@ -8376,6 +9147,92 @@ async function handleToolUnsafe(name, input) {
|
|
|
8376
9147
|
}))
|
|
8377
9148
|
};
|
|
8378
9149
|
}
|
|
9150
|
+
case "get_ml_model_health": {
|
|
9151
|
+
let mlClient2 = getMLClient();
|
|
9152
|
+
if (!mlClient2) {
|
|
9153
|
+
try {
|
|
9154
|
+
const cfg = getConfig();
|
|
9155
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
9156
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
9157
|
+
}
|
|
9158
|
+
} catch {
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
if (!mlClient2) {
|
|
9162
|
+
return { error: "ML sidecar not configured" };
|
|
9163
|
+
}
|
|
9164
|
+
const health = await mlClient2.getModelHealth();
|
|
9165
|
+
if (!health) {
|
|
9166
|
+
return { error: "ML sidecar not responding" };
|
|
9167
|
+
}
|
|
9168
|
+
return {
|
|
9169
|
+
models: health.models.map((m) => ({
|
|
9170
|
+
name: m.name,
|
|
9171
|
+
version: m.version,
|
|
9172
|
+
loaded: m.loaded,
|
|
9173
|
+
lastTrained: m.lastTrained,
|
|
9174
|
+
accuracy: m.accuracy
|
|
9175
|
+
})),
|
|
9176
|
+
uptime: health.uptime,
|
|
9177
|
+
predictionsServed: health.predictionsServed
|
|
9178
|
+
};
|
|
9179
|
+
}
|
|
9180
|
+
case "classify_user_intent": {
|
|
9181
|
+
const text = String(params["text"] ?? "");
|
|
9182
|
+
let mlClient2 = getMLClient();
|
|
9183
|
+
if (!mlClient2) {
|
|
9184
|
+
try {
|
|
9185
|
+
const cfg = getConfig();
|
|
9186
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
9187
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
9188
|
+
}
|
|
9189
|
+
} catch {
|
|
9190
|
+
}
|
|
9191
|
+
}
|
|
9192
|
+
if (!mlClient2) {
|
|
9193
|
+
return { error: "ML sidecar not configured for intent classification" };
|
|
9194
|
+
}
|
|
9195
|
+
const intent = await mlClient2.classifyIntent(text);
|
|
9196
|
+
if (!intent) {
|
|
9197
|
+
return { error: "Intent classification failed" };
|
|
9198
|
+
}
|
|
9199
|
+
return {
|
|
9200
|
+
intent: intent.intent,
|
|
9201
|
+
confidence: intent.confidence,
|
|
9202
|
+
secondaryIntent: intent.secondary_intent,
|
|
9203
|
+
detectedTokens: intent.detected_tokens,
|
|
9204
|
+
detectedAddresses: intent.detected_addresses,
|
|
9205
|
+
model: intent.model
|
|
9206
|
+
};
|
|
9207
|
+
}
|
|
9208
|
+
case "run_backtest": {
|
|
9209
|
+
const { BacktestEngine: BacktestEngine2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
|
|
9210
|
+
const engine = new BacktestEngine2({
|
|
9211
|
+
strategy: String(params["strategy"] ?? "momentum"),
|
|
9212
|
+
pair: String(params["pair"] ?? "BTCUSDT"),
|
|
9213
|
+
from: String(params["from"] ?? ""),
|
|
9214
|
+
to: String(params["to"] ?? ""),
|
|
9215
|
+
initialCapital: 1e4,
|
|
9216
|
+
timeframe: String(params["timeframe"] ?? "4h"),
|
|
9217
|
+
slippageBps: 10,
|
|
9218
|
+
commissionPct: 0.1
|
|
9219
|
+
});
|
|
9220
|
+
const result = await engine.run();
|
|
9221
|
+
return {
|
|
9222
|
+
strategy: result.config.strategy,
|
|
9223
|
+
pair: result.config.pair,
|
|
9224
|
+
period: `${result.config.from} \u2192 ${result.config.to}`,
|
|
9225
|
+
metrics: result.metrics,
|
|
9226
|
+
tradeCount: result.trades.length,
|
|
9227
|
+
lastTrades: result.trades.slice(-5).map((t) => ({
|
|
9228
|
+
entry: t.entryPrice,
|
|
9229
|
+
exit: t.exitPrice,
|
|
9230
|
+
pnl: t.pnl,
|
|
9231
|
+
pnlPct: t.pnlPct,
|
|
9232
|
+
side: t.side
|
|
9233
|
+
}))
|
|
9234
|
+
};
|
|
9235
|
+
}
|
|
8379
9236
|
default:
|
|
8380
9237
|
return { error: `Unknown tool: ${name}` };
|
|
8381
9238
|
}
|
|
@@ -8808,6 +9665,59 @@ var init_tools = __esm({
|
|
|
8808
9665
|
required: ["agentName"]
|
|
8809
9666
|
}
|
|
8810
9667
|
},
|
|
9668
|
+
{
|
|
9669
|
+
name: "get_ml_model_health",
|
|
9670
|
+
description: "Get health status of all ML models in the sidecar including loaded status, version, last training date, accuracy, uptime, and total predictions served.",
|
|
9671
|
+
input_schema: {
|
|
9672
|
+
type: "object",
|
|
9673
|
+
properties: {},
|
|
9674
|
+
required: []
|
|
9675
|
+
}
|
|
9676
|
+
},
|
|
9677
|
+
{
|
|
9678
|
+
name: "classify_user_intent",
|
|
9679
|
+
description: "Classify a user message into an intent category (e.g. price_check, token_analysis, prediction, news, portfolio, agent_management) using ML NLP. Returns intent, confidence, detected tokens, and detected addresses.",
|
|
9680
|
+
input_schema: {
|
|
9681
|
+
type: "object",
|
|
9682
|
+
properties: {
|
|
9683
|
+
text: {
|
|
9684
|
+
type: "string",
|
|
9685
|
+
description: "The user message text to classify."
|
|
9686
|
+
}
|
|
9687
|
+
},
|
|
9688
|
+
required: ["text"]
|
|
9689
|
+
}
|
|
9690
|
+
},
|
|
9691
|
+
{
|
|
9692
|
+
name: "run_backtest",
|
|
9693
|
+
description: "Run a historical backtest for a trading strategy. Simulates strategy execution on historical kline data and returns trades, metrics (return, win rate, Sharpe, drawdown), and equity curve.",
|
|
9694
|
+
input_schema: {
|
|
9695
|
+
type: "object",
|
|
9696
|
+
properties: {
|
|
9697
|
+
strategy: {
|
|
9698
|
+
type: "string",
|
|
9699
|
+
description: 'Strategy name: "momentum", "trend-following", or "ml-adaptive".'
|
|
9700
|
+
},
|
|
9701
|
+
pair: {
|
|
9702
|
+
type: "string",
|
|
9703
|
+
description: 'Trading pair (e.g. "BTCUSDT").'
|
|
9704
|
+
},
|
|
9705
|
+
from: {
|
|
9706
|
+
type: "string",
|
|
9707
|
+
description: "Start date (YYYY-MM-DD)."
|
|
9708
|
+
},
|
|
9709
|
+
to: {
|
|
9710
|
+
type: "string",
|
|
9711
|
+
description: "End date (YYYY-MM-DD)."
|
|
9712
|
+
},
|
|
9713
|
+
timeframe: {
|
|
9714
|
+
type: "string",
|
|
9715
|
+
description: 'Candle timeframe: "1h", "4h", "1d". Defaults to "4h".'
|
|
9716
|
+
}
|
|
9717
|
+
},
|
|
9718
|
+
required: ["strategy", "pair", "from", "to"]
|
|
9719
|
+
}
|
|
9720
|
+
},
|
|
8811
9721
|
{
|
|
8812
9722
|
name: "create_agent",
|
|
8813
9723
|
description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
|
|
@@ -10258,13 +11168,13 @@ var init_bot3 = __esm({
|
|
|
10258
11168
|
});
|
|
10259
11169
|
|
|
10260
11170
|
// src/data/collector.ts
|
|
10261
|
-
var
|
|
11171
|
+
var log8, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
|
|
10262
11172
|
var init_collector = __esm({
|
|
10263
11173
|
"src/data/collector.ts"() {
|
|
10264
11174
|
"use strict";
|
|
10265
11175
|
init_binance();
|
|
10266
11176
|
init_logger();
|
|
10267
|
-
|
|
11177
|
+
log8 = createLogger("collector");
|
|
10268
11178
|
MAJOR_SYMBOLS = [
|
|
10269
11179
|
"BTC",
|
|
10270
11180
|
"ETH",
|
|
@@ -10315,10 +11225,10 @@ var init_collector = __esm({
|
|
|
10315
11225
|
}
|
|
10316
11226
|
start() {
|
|
10317
11227
|
if (this.status.running) {
|
|
10318
|
-
|
|
11228
|
+
log8.warn("Collector already running");
|
|
10319
11229
|
return;
|
|
10320
11230
|
}
|
|
10321
|
-
|
|
11231
|
+
log8.info(
|
|
10322
11232
|
`Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
|
|
10323
11233
|
);
|
|
10324
11234
|
this.status.running = true;
|
|
@@ -10331,10 +11241,10 @@ var init_collector = __esm({
|
|
|
10331
11241
|
this.timer = null;
|
|
10332
11242
|
}
|
|
10333
11243
|
this.status.running = false;
|
|
10334
|
-
|
|
11244
|
+
log8.info("Data collector stopped");
|
|
10335
11245
|
}
|
|
10336
11246
|
async collectAll() {
|
|
10337
|
-
|
|
11247
|
+
log8.info("Collection cycle starting");
|
|
10338
11248
|
const start = Date.now();
|
|
10339
11249
|
for (const timeframe of TIMEFRAMES) {
|
|
10340
11250
|
for (const symbol of this.symbols) {
|
|
@@ -10342,7 +11252,7 @@ var init_collector = __esm({
|
|
|
10342
11252
|
await this.collectSymbol(symbol, timeframe);
|
|
10343
11253
|
} catch (err) {
|
|
10344
11254
|
this.status.errors++;
|
|
10345
|
-
|
|
11255
|
+
log8.error(
|
|
10346
11256
|
`Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
|
|
10347
11257
|
);
|
|
10348
11258
|
}
|
|
@@ -10350,7 +11260,7 @@ var init_collector = __esm({
|
|
|
10350
11260
|
}
|
|
10351
11261
|
this.status.lastRun = Date.now();
|
|
10352
11262
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
10353
|
-
|
|
11263
|
+
log8.info(
|
|
10354
11264
|
`Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
|
|
10355
11265
|
);
|
|
10356
11266
|
}
|
|
@@ -10442,59 +11352,188 @@ async function registerMarketRoutes(server) {
|
|
|
10442
11352
|
server.get("/price/:symbol", {
|
|
10443
11353
|
schema: {
|
|
10444
11354
|
tags: ["Market"],
|
|
10445
|
-
summary: "Get live price and market data for a symbol",
|
|
10446
|
-
params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
|
|
11355
|
+
summary: "Get live price and market data for a symbol",
|
|
11356
|
+
params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
|
|
11357
|
+
},
|
|
11358
|
+
handler: async (request) => {
|
|
11359
|
+
const { symbol } = request.params;
|
|
11360
|
+
return handleTool("get_market_data", { symbol });
|
|
11361
|
+
}
|
|
11362
|
+
});
|
|
11363
|
+
server.get("/trending", {
|
|
11364
|
+
schema: {
|
|
11365
|
+
tags: ["Market"],
|
|
11366
|
+
summary: "Get trending tokens from DEX and CoinGecko"
|
|
11367
|
+
},
|
|
11368
|
+
handler: async () => {
|
|
11369
|
+
return handleTool("get_trending", {});
|
|
11370
|
+
}
|
|
11371
|
+
});
|
|
11372
|
+
server.get("/fear-greed", {
|
|
11373
|
+
schema: {
|
|
11374
|
+
tags: ["Market"],
|
|
11375
|
+
summary: "Get Crypto Fear & Greed Index with history"
|
|
11376
|
+
},
|
|
11377
|
+
handler: async () => {
|
|
11378
|
+
return handleTool("get_fear_greed", {});
|
|
11379
|
+
}
|
|
11380
|
+
});
|
|
11381
|
+
server.get("/news", {
|
|
11382
|
+
schema: {
|
|
11383
|
+
tags: ["Market"],
|
|
11384
|
+
summary: "Get latest crypto news with sentiment",
|
|
11385
|
+
querystring: {
|
|
11386
|
+
type: "object",
|
|
11387
|
+
properties: { symbol: { type: "string" } }
|
|
11388
|
+
}
|
|
11389
|
+
},
|
|
11390
|
+
handler: async (request) => {
|
|
11391
|
+
try {
|
|
11392
|
+
const { symbol } = request.query;
|
|
11393
|
+
return await handleTool("get_crypto_news", { symbol });
|
|
11394
|
+
} catch {
|
|
11395
|
+
return { news: [] };
|
|
11396
|
+
}
|
|
11397
|
+
}
|
|
11398
|
+
});
|
|
11399
|
+
server.get("/prediction", {
|
|
11400
|
+
schema: {
|
|
11401
|
+
tags: ["Market"],
|
|
11402
|
+
summary: "Get AI prediction for a symbol",
|
|
11403
|
+
querystring: {
|
|
11404
|
+
type: "object",
|
|
11405
|
+
properties: { symbol: { type: "string" } },
|
|
11406
|
+
required: ["symbol"]
|
|
11407
|
+
}
|
|
11408
|
+
},
|
|
11409
|
+
handler: async (request) => {
|
|
11410
|
+
const { symbol } = request.query;
|
|
11411
|
+
return handleTool("get_prediction", { symbol });
|
|
11412
|
+
}
|
|
11413
|
+
});
|
|
11414
|
+
server.get("/dex/search", {
|
|
11415
|
+
schema: {
|
|
11416
|
+
tags: ["Market"],
|
|
11417
|
+
summary: "Search tokens on decentralized exchanges",
|
|
11418
|
+
querystring: {
|
|
11419
|
+
type: "object",
|
|
11420
|
+
properties: { q: { type: "string" } },
|
|
11421
|
+
required: ["q"]
|
|
11422
|
+
}
|
|
10447
11423
|
},
|
|
10448
11424
|
handler: async (request) => {
|
|
10449
|
-
const {
|
|
10450
|
-
return handleTool("
|
|
11425
|
+
const { q } = request.query;
|
|
11426
|
+
return handleTool("search_token_dex", { query: q });
|
|
10451
11427
|
}
|
|
10452
11428
|
});
|
|
10453
|
-
server.get("/
|
|
11429
|
+
server.get("/ml-health", {
|
|
10454
11430
|
schema: {
|
|
10455
11431
|
tags: ["Market"],
|
|
10456
|
-
summary: "Get
|
|
11432
|
+
summary: "Get ML sidecar health and model status"
|
|
10457
11433
|
},
|
|
10458
11434
|
handler: async () => {
|
|
10459
|
-
|
|
11435
|
+
let mlClient2 = getMLClient();
|
|
11436
|
+
if (!mlClient2) {
|
|
11437
|
+
const cfg = loadConfig();
|
|
11438
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
11439
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
11440
|
+
}
|
|
11441
|
+
}
|
|
11442
|
+
if (!mlClient2) {
|
|
11443
|
+
return { available: false, models: [], uptime: 0, predictionsServed: 0 };
|
|
11444
|
+
}
|
|
11445
|
+
const health = await mlClient2.getModelHealth();
|
|
11446
|
+
if (!health) {
|
|
11447
|
+
return { available: false, models: [], uptime: 0, predictionsServed: 0 };
|
|
11448
|
+
}
|
|
11449
|
+
return { available: true, ...health };
|
|
10460
11450
|
}
|
|
10461
11451
|
});
|
|
10462
|
-
server.
|
|
11452
|
+
server.post("/ml/sentiment", {
|
|
10463
11453
|
schema: {
|
|
10464
11454
|
tags: ["Market"],
|
|
10465
|
-
summary: "
|
|
11455
|
+
summary: "Analyze text sentiment via ML",
|
|
11456
|
+
body: {
|
|
11457
|
+
type: "object",
|
|
11458
|
+
properties: { text: { type: "string" } },
|
|
11459
|
+
required: ["text"]
|
|
11460
|
+
}
|
|
10466
11461
|
},
|
|
10467
|
-
handler: async () => {
|
|
10468
|
-
|
|
11462
|
+
handler: async (request) => {
|
|
11463
|
+
const { text } = request.body;
|
|
11464
|
+
let mlClient2 = getMLClient();
|
|
11465
|
+
if (!mlClient2) {
|
|
11466
|
+
const cfg = loadConfig();
|
|
11467
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
11468
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11471
|
+
if (!mlClient2) return { error: "ML sidecar unavailable" };
|
|
11472
|
+
const result = await mlClient2.analyzeSentiment(text);
|
|
11473
|
+
return result ?? { error: "Sentiment analysis failed" };
|
|
10469
11474
|
}
|
|
10470
11475
|
});
|
|
10471
|
-
server.
|
|
11476
|
+
server.post("/ml/regime", {
|
|
10472
11477
|
schema: {
|
|
10473
11478
|
tags: ["Market"],
|
|
10474
|
-
summary: "
|
|
10475
|
-
|
|
11479
|
+
summary: "Detect market regime via ML",
|
|
11480
|
+
body: {
|
|
10476
11481
|
type: "object",
|
|
10477
|
-
properties: {
|
|
11482
|
+
properties: {
|
|
11483
|
+
returns_1d: { type: "number" },
|
|
11484
|
+
returns_7d: { type: "number" },
|
|
11485
|
+
volatility_14d: { type: "number" },
|
|
11486
|
+
volume_ratio: { type: "number" },
|
|
11487
|
+
rsi: { type: "number" },
|
|
11488
|
+
bb_width: { type: "number" },
|
|
11489
|
+
fear_greed: { type: "number" },
|
|
11490
|
+
funding_rate: { type: "number" },
|
|
11491
|
+
price_vs_sma200: { type: "number" }
|
|
11492
|
+
}
|
|
10478
11493
|
}
|
|
10479
11494
|
},
|
|
10480
11495
|
handler: async (request) => {
|
|
10481
|
-
const
|
|
10482
|
-
|
|
11496
|
+
const features = request.body;
|
|
11497
|
+
let mlClient2 = getMLClient();
|
|
11498
|
+
if (!mlClient2) {
|
|
11499
|
+
const cfg = loadConfig();
|
|
11500
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
11501
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
11502
|
+
}
|
|
11503
|
+
}
|
|
11504
|
+
if (!mlClient2) return { error: "ML sidecar unavailable" };
|
|
11505
|
+
const result = await mlClient2.detectRegime(features);
|
|
11506
|
+
return result ?? { error: "Regime detection failed" };
|
|
10483
11507
|
}
|
|
10484
11508
|
});
|
|
10485
|
-
server.
|
|
11509
|
+
server.post("/ml/trend", {
|
|
10486
11510
|
schema: {
|
|
10487
11511
|
tags: ["Market"],
|
|
10488
|
-
summary: "
|
|
10489
|
-
|
|
11512
|
+
summary: "Score trend via ML",
|
|
11513
|
+
body: {
|
|
10490
11514
|
type: "object",
|
|
10491
|
-
properties: {
|
|
10492
|
-
|
|
11515
|
+
properties: {
|
|
11516
|
+
price_change_24h: { type: "number" },
|
|
11517
|
+
price_change_7d: { type: "number" },
|
|
11518
|
+
volume_24h: { type: "number" },
|
|
11519
|
+
market_cap: { type: "number" },
|
|
11520
|
+
volume_to_mcap_ratio: { type: "number" },
|
|
11521
|
+
rank: { type: "number" }
|
|
11522
|
+
}
|
|
10493
11523
|
}
|
|
10494
11524
|
},
|
|
10495
11525
|
handler: async (request) => {
|
|
10496
|
-
const
|
|
10497
|
-
|
|
11526
|
+
const features = request.body;
|
|
11527
|
+
let mlClient2 = getMLClient();
|
|
11528
|
+
if (!mlClient2) {
|
|
11529
|
+
const cfg = loadConfig();
|
|
11530
|
+
if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
|
|
11531
|
+
mlClient2 = initMLClient(cfg.ml.sidecarUrl);
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
if (!mlClient2) return { error: "ML sidecar unavailable" };
|
|
11535
|
+
const result = await mlClient2.scoreTrend(features);
|
|
11536
|
+
return result ?? { error: "Trend scoring failed" };
|
|
10498
11537
|
}
|
|
10499
11538
|
});
|
|
10500
11539
|
server.get("/derivatives/:symbol", {
|
|
@@ -10513,6 +11552,8 @@ var init_market3 = __esm({
|
|
|
10513
11552
|
"src/api/routes/v1/market.ts"() {
|
|
10514
11553
|
"use strict";
|
|
10515
11554
|
init_tool_handler();
|
|
11555
|
+
init_client();
|
|
11556
|
+
init_loader();
|
|
10516
11557
|
}
|
|
10517
11558
|
});
|
|
10518
11559
|
|
|
@@ -10645,6 +11686,134 @@ var init_security = __esm({
|
|
|
10645
11686
|
}
|
|
10646
11687
|
});
|
|
10647
11688
|
|
|
11689
|
+
// src/api/routes/v1/backtest.ts
|
|
11690
|
+
async function backtestRoutes(fastify) {
|
|
11691
|
+
fastify.post("/v1/backtest", async (request, reply) => {
|
|
11692
|
+
const body = request.body;
|
|
11693
|
+
const config2 = {
|
|
11694
|
+
strategy: String(body["strategy"] ?? "momentum"),
|
|
11695
|
+
pair: String(body["pair"] ?? "BTCUSDT"),
|
|
11696
|
+
from: String(body["from"] ?? ""),
|
|
11697
|
+
to: String(body["to"] ?? ""),
|
|
11698
|
+
initialCapital: Number(body["initialCapital"] ?? 1e4),
|
|
11699
|
+
timeframe: String(body["timeframe"] ?? "4h"),
|
|
11700
|
+
slippageBps: Number(body["slippageBps"] ?? 10),
|
|
11701
|
+
commissionPct: Number(body["commissionPct"] ?? 0.1)
|
|
11702
|
+
};
|
|
11703
|
+
if (!config2.from || !config2.to) {
|
|
11704
|
+
return reply.status(400).send({ error: "from and to dates are required" });
|
|
11705
|
+
}
|
|
11706
|
+
const engine = new BacktestEngine(config2);
|
|
11707
|
+
const result = await engine.run();
|
|
11708
|
+
return result;
|
|
11709
|
+
});
|
|
11710
|
+
}
|
|
11711
|
+
var init_backtest = __esm({
|
|
11712
|
+
"src/api/routes/v1/backtest.ts"() {
|
|
11713
|
+
"use strict";
|
|
11714
|
+
init_engine2();
|
|
11715
|
+
}
|
|
11716
|
+
});
|
|
11717
|
+
|
|
11718
|
+
// src/api/routes/v1/agents.ts
|
|
11719
|
+
async function agentRoutes(fastify) {
|
|
11720
|
+
fastify.get("/v1/agents", async () => {
|
|
11721
|
+
const agents = listAgents();
|
|
11722
|
+
return {
|
|
11723
|
+
agents: agents.map((a) => {
|
|
11724
|
+
const status = getAgentStatus(a.id);
|
|
11725
|
+
return {
|
|
11726
|
+
name: a.name,
|
|
11727
|
+
strategy: a.strategy,
|
|
11728
|
+
pairs: a.pairs,
|
|
11729
|
+
interval: a.interval,
|
|
11730
|
+
status: status?.status ?? "idle",
|
|
11731
|
+
cycleCount: status?.cycleCount ?? 0
|
|
11732
|
+
};
|
|
11733
|
+
})
|
|
11734
|
+
};
|
|
11735
|
+
});
|
|
11736
|
+
fastify.post("/v1/agents", async (request) => {
|
|
11737
|
+
const body = request.body;
|
|
11738
|
+
const name = String(body["name"] ?? "");
|
|
11739
|
+
const strategy = String(body["strategy"] ?? "momentum");
|
|
11740
|
+
const pairs = body["pairs"] ?? ["BTC", "ETH"];
|
|
11741
|
+
const interval = Number(body["interval"] ?? 60);
|
|
11742
|
+
const agent = createAgent(name, strategy, pairs, interval);
|
|
11743
|
+
return { id: agent.id, name: agent.name, strategy: agent.strategy, pairs: agent.pairs };
|
|
11744
|
+
});
|
|
11745
|
+
fastify.get("/v1/agents/:name", async (request) => {
|
|
11746
|
+
const { name } = request.params;
|
|
11747
|
+
const agent = getAgentByName(name);
|
|
11748
|
+
if (!agent) return { error: "Agent not found" };
|
|
11749
|
+
const status = getAgentStatus(agent.id);
|
|
11750
|
+
return {
|
|
11751
|
+
name: agent.name,
|
|
11752
|
+
strategy: agent.strategy,
|
|
11753
|
+
pairs: agent.pairs,
|
|
11754
|
+
status: status?.status ?? "idle",
|
|
11755
|
+
cycleCount: status?.cycleCount ?? 0
|
|
11756
|
+
};
|
|
11757
|
+
});
|
|
11758
|
+
fastify.post("/v1/agents/:name/start", async (request) => {
|
|
11759
|
+
const { name } = request.params;
|
|
11760
|
+
const agent = getAgentByName(name);
|
|
11761
|
+
if (!agent) return { error: "Agent not found" };
|
|
11762
|
+
startAgent(agent.id);
|
|
11763
|
+
return { message: `Agent "${name}" started` };
|
|
11764
|
+
});
|
|
11765
|
+
fastify.post("/v1/agents/:name/stop", async (request) => {
|
|
11766
|
+
const { name } = request.params;
|
|
11767
|
+
const agent = getAgentByName(name);
|
|
11768
|
+
if (!agent) return { error: "Agent not found" };
|
|
11769
|
+
stopAgent(agent.id);
|
|
11770
|
+
return { message: `Agent "${name}" stopped` };
|
|
11771
|
+
});
|
|
11772
|
+
fastify.delete("/v1/agents/:name", async (request) => {
|
|
11773
|
+
const { name } = request.params;
|
|
11774
|
+
const agent = getAgentByName(name);
|
|
11775
|
+
if (!agent) return { error: "Agent not found" };
|
|
11776
|
+
deleteAgent(agent.id);
|
|
11777
|
+
return { message: `Agent "${name}" deleted` };
|
|
11778
|
+
});
|
|
11779
|
+
}
|
|
11780
|
+
var init_agents = __esm({
|
|
11781
|
+
"src/api/routes/v1/agents.ts"() {
|
|
11782
|
+
"use strict";
|
|
11783
|
+
init_agent();
|
|
11784
|
+
}
|
|
11785
|
+
});
|
|
11786
|
+
|
|
11787
|
+
// src/api/routes/v1/portfolio.ts
|
|
11788
|
+
async function portfolioRoutes(fastify) {
|
|
11789
|
+
fastify.get("/v1/portfolio/:agentId", async (request) => {
|
|
11790
|
+
const { agentId } = request.params;
|
|
11791
|
+
return {
|
|
11792
|
+
agentId,
|
|
11793
|
+
totalValue: 1e4,
|
|
11794
|
+
cash: 1e4,
|
|
11795
|
+
positions: [],
|
|
11796
|
+
totalReturn: 0,
|
|
11797
|
+
totalReturnPct: 0,
|
|
11798
|
+
winRate: 0,
|
|
11799
|
+
sharpeRatio: 0,
|
|
11800
|
+
maxDrawdown: 0
|
|
11801
|
+
};
|
|
11802
|
+
});
|
|
11803
|
+
fastify.get("/v1/portfolio/:agentId/trades", async (request) => {
|
|
11804
|
+
const { agentId } = request.params;
|
|
11805
|
+
return {
|
|
11806
|
+
agentId,
|
|
11807
|
+
trades: []
|
|
11808
|
+
};
|
|
11809
|
+
});
|
|
11810
|
+
}
|
|
11811
|
+
var init_portfolio = __esm({
|
|
11812
|
+
"src/api/routes/v1/portfolio.ts"() {
|
|
11813
|
+
"use strict";
|
|
11814
|
+
}
|
|
11815
|
+
});
|
|
11816
|
+
|
|
10648
11817
|
// src/api/auth/keys.ts
|
|
10649
11818
|
import { randomBytes, scryptSync } from "crypto";
|
|
10650
11819
|
function hashApiKey(key) {
|
|
@@ -10820,7 +11989,7 @@ async function startApiServer(options) {
|
|
|
10820
11989
|
info: {
|
|
10821
11990
|
title: "Vizzor API",
|
|
10822
11991
|
description: "AI-powered crypto intelligence REST API",
|
|
10823
|
-
version: "0.
|
|
11992
|
+
version: "0.11.0"
|
|
10824
11993
|
},
|
|
10825
11994
|
servers: [{ url: `http://${options.host}:${options.port}` }],
|
|
10826
11995
|
components: {
|
|
@@ -10842,7 +12011,7 @@ async function startApiServer(options) {
|
|
|
10842
12011
|
if (options.enableAuth !== false) {
|
|
10843
12012
|
server.addHook("onRequest", authMiddleware);
|
|
10844
12013
|
} else if (isProd) {
|
|
10845
|
-
|
|
12014
|
+
log9.warn("API authentication is DISABLED in production \u2014 this is insecure");
|
|
10846
12015
|
}
|
|
10847
12016
|
server.setErrorHandler(errorHandler);
|
|
10848
12017
|
server.get("/health", async () => {
|
|
@@ -10851,7 +12020,7 @@ async function startApiServer(options) {
|
|
|
10851
12020
|
}
|
|
10852
12021
|
return {
|
|
10853
12022
|
status: "ok",
|
|
10854
|
-
version: "0.
|
|
12023
|
+
version: "0.11.0",
|
|
10855
12024
|
uptime: process.uptime(),
|
|
10856
12025
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10857
12026
|
};
|
|
@@ -10859,13 +12028,16 @@ async function startApiServer(options) {
|
|
|
10859
12028
|
await server.register(registerMarketRoutes, { prefix: "/v1/market" });
|
|
10860
12029
|
await server.register(registerAnalysisRoutes, { prefix: "/v1/analysis" });
|
|
10861
12030
|
await server.register(registerSecurityRoutes, { prefix: "/v1/security" });
|
|
12031
|
+
await server.register(backtestRoutes);
|
|
12032
|
+
await server.register(agentRoutes);
|
|
12033
|
+
await server.register(portfolioRoutes);
|
|
10862
12034
|
await server.listen({ port: options.port, host: options.host });
|
|
10863
|
-
|
|
12035
|
+
log9.info(`Vizzor API listening on ${options.host}:${options.port}`);
|
|
10864
12036
|
if (!isProd) {
|
|
10865
|
-
|
|
12037
|
+
log9.info(`OpenAPI docs at http://${options.host}:${options.port}/docs`);
|
|
10866
12038
|
}
|
|
10867
12039
|
}
|
|
10868
|
-
var
|
|
12040
|
+
var log9;
|
|
10869
12041
|
var init_server = __esm({
|
|
10870
12042
|
"src/api/server.ts"() {
|
|
10871
12043
|
"use strict";
|
|
@@ -10873,9 +12045,12 @@ var init_server = __esm({
|
|
|
10873
12045
|
init_market3();
|
|
10874
12046
|
init_analysis();
|
|
10875
12047
|
init_security();
|
|
12048
|
+
init_backtest();
|
|
12049
|
+
init_agents();
|
|
12050
|
+
init_portfolio();
|
|
10876
12051
|
init_middleware();
|
|
10877
12052
|
init_error_handler();
|
|
10878
|
-
|
|
12053
|
+
log9 = createLogger("api");
|
|
10879
12054
|
}
|
|
10880
12055
|
});
|
|
10881
12056
|
|
|
@@ -10957,6 +12132,480 @@ var init_api = __esm({
|
|
|
10957
12132
|
}
|
|
10958
12133
|
});
|
|
10959
12134
|
|
|
12135
|
+
// src/cli/commands/backtest.ts
|
|
12136
|
+
var backtest_exports = {};
|
|
12137
|
+
__export(backtest_exports, {
|
|
12138
|
+
registerBacktestCommand: () => registerBacktestCommand
|
|
12139
|
+
});
|
|
12140
|
+
function registerBacktestCommand(program2) {
|
|
12141
|
+
program2.command("backtest").description("Run a historical backtest for a trading strategy").requiredOption(
|
|
12142
|
+
"-s, --strategy <strategy>",
|
|
12143
|
+
"Strategy name (momentum, trend-following, ml-adaptive)"
|
|
12144
|
+
).requiredOption("--pair <pair>", "Trading pair (e.g. BTCUSDT)").requiredOption("--from <date>", "Start date (YYYY-MM-DD)").requiredOption("--to <date>", "End date (YYYY-MM-DD)").option("-c, --capital <amount>", "Initial capital in USD", "10000").option("-t, --timeframe <tf>", "Candle timeframe", "4h").option("--slippage <bps>", "Slippage in basis points", "10").option("--commission <pct>", "Commission percentage", "0.1").action(async (opts) => {
|
|
12145
|
+
try {
|
|
12146
|
+
const engine = new BacktestEngine({
|
|
12147
|
+
strategy: opts.strategy,
|
|
12148
|
+
pair: opts.pair,
|
|
12149
|
+
from: opts.from,
|
|
12150
|
+
to: opts.to,
|
|
12151
|
+
initialCapital: Number(opts.capital),
|
|
12152
|
+
timeframe: opts.timeframe,
|
|
12153
|
+
slippageBps: Number(opts.slippage),
|
|
12154
|
+
commissionPct: Number(opts.commission)
|
|
12155
|
+
});
|
|
12156
|
+
console.log(
|
|
12157
|
+
`Running backtest: ${opts.strategy} on ${opts.pair} (${opts.from} \u2192 ${opts.to})...
|
|
12158
|
+
`
|
|
12159
|
+
);
|
|
12160
|
+
const result = await engine.run();
|
|
12161
|
+
console.log("=== Backtest Results ===");
|
|
12162
|
+
console.log(`Strategy: ${result.config.strategy}`);
|
|
12163
|
+
console.log(`Pair: ${result.config.pair}`);
|
|
12164
|
+
console.log(`Period: ${result.config.from} \u2192 ${result.config.to}`);
|
|
12165
|
+
console.log(`Initial Capital: $${result.config.initialCapital.toLocaleString()}`);
|
|
12166
|
+
console.log("");
|
|
12167
|
+
console.log(
|
|
12168
|
+
`Total Return: $${result.metrics.totalReturn.toFixed(2)} (${result.metrics.totalReturnPct.toFixed(2)}%)`
|
|
12169
|
+
);
|
|
12170
|
+
console.log(`Win Rate: ${(result.metrics.winRate * 100).toFixed(1)}%`);
|
|
12171
|
+
console.log(`Total Trades: ${result.metrics.totalTrades}`);
|
|
12172
|
+
console.log(
|
|
12173
|
+
`Profit Factor: ${result.metrics.profitFactor === Infinity ? "\u221E" : result.metrics.profitFactor.toFixed(2)}`
|
|
12174
|
+
);
|
|
12175
|
+
console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(2)}`);
|
|
12176
|
+
console.log(`Max Drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`);
|
|
12177
|
+
if (result.trades.length > 0) {
|
|
12178
|
+
console.log("\nLast 5 Trades:");
|
|
12179
|
+
for (const t of result.trades.slice(-5)) {
|
|
12180
|
+
const dir = t.pnl >= 0 ? "+" : "";
|
|
12181
|
+
console.log(
|
|
12182
|
+
` ${new Date(t.entryTime).toISOString().split("T")[0]} \u2192 ${new Date(t.exitTime).toISOString().split("T")[0]} | ${t.side} | Entry: $${t.entryPrice.toFixed(2)} \u2192 Exit: $${t.exitPrice.toFixed(2)} | PnL: ${dir}$${t.pnl.toFixed(2)} (${dir}${t.pnlPct.toFixed(2)}%)`
|
|
12183
|
+
);
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
} catch (err) {
|
|
12187
|
+
log10.error(`Backtest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12188
|
+
process.exitCode = 1;
|
|
12189
|
+
}
|
|
12190
|
+
});
|
|
12191
|
+
}
|
|
12192
|
+
var log10;
|
|
12193
|
+
var init_backtest2 = __esm({
|
|
12194
|
+
"src/cli/commands/backtest.ts"() {
|
|
12195
|
+
"use strict";
|
|
12196
|
+
init_engine2();
|
|
12197
|
+
init_logger();
|
|
12198
|
+
log10 = createLogger("cli-backtest");
|
|
12199
|
+
}
|
|
12200
|
+
});
|
|
12201
|
+
|
|
12202
|
+
// src/core/agent/wallet-manager.ts
|
|
12203
|
+
import { randomBytes as randomBytes2, scryptSync as scryptSync2, createCipheriv, createDecipheriv } from "crypto";
|
|
12204
|
+
import {
|
|
12205
|
+
existsSync as existsSync3,
|
|
12206
|
+
mkdirSync as mkdirSync2,
|
|
12207
|
+
readFileSync as readFileSync3,
|
|
12208
|
+
writeFileSync as writeFileSync3,
|
|
12209
|
+
unlinkSync,
|
|
12210
|
+
readdirSync
|
|
12211
|
+
} from "fs";
|
|
12212
|
+
import { join as join3 } from "path";
|
|
12213
|
+
import { homedir as homedir2 } from "os";
|
|
12214
|
+
function ensureDir() {
|
|
12215
|
+
if (!existsSync3(WALLETS_DIR)) {
|
|
12216
|
+
mkdirSync2(WALLETS_DIR, { recursive: true });
|
|
12217
|
+
}
|
|
12218
|
+
}
|
|
12219
|
+
function deriveKey(password, salt) {
|
|
12220
|
+
return scryptSync2(password, salt, KEY_LENGTH, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P });
|
|
12221
|
+
}
|
|
12222
|
+
function encrypt(plaintext, password) {
|
|
12223
|
+
const salt = randomBytes2(32);
|
|
12224
|
+
const key = deriveKey(password, salt);
|
|
12225
|
+
const iv = randomBytes2(IV_LENGTH);
|
|
12226
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
12227
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
12228
|
+
const tag = cipher.getAuthTag();
|
|
12229
|
+
return {
|
|
12230
|
+
salt: salt.toString("hex"),
|
|
12231
|
+
iv: iv.toString("hex"),
|
|
12232
|
+
tag: tag.toString("hex"),
|
|
12233
|
+
ciphertext: encrypted.toString("hex")
|
|
12234
|
+
};
|
|
12235
|
+
}
|
|
12236
|
+
function createWallet(name, password) {
|
|
12237
|
+
ensureDir();
|
|
12238
|
+
const filePath = join3(WALLETS_DIR, `${name}.json`);
|
|
12239
|
+
if (existsSync3(filePath)) {
|
|
12240
|
+
throw new Error(`Wallet "${name}" already exists`);
|
|
12241
|
+
}
|
|
12242
|
+
const privateKey = `0x${randomBytes2(32).toString("hex")}`;
|
|
12243
|
+
const { salt, iv, tag, ciphertext } = encrypt(privateKey, password);
|
|
12244
|
+
const wallet = {
|
|
12245
|
+
name,
|
|
12246
|
+
address: "(derived on load)",
|
|
12247
|
+
salt,
|
|
12248
|
+
iv,
|
|
12249
|
+
tag,
|
|
12250
|
+
ciphertext,
|
|
12251
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12252
|
+
};
|
|
12253
|
+
writeFileSync3(filePath, JSON.stringify(wallet, null, 2));
|
|
12254
|
+
log11.info(`Wallet "${name}" created at ${filePath}`);
|
|
12255
|
+
return { name, address: wallet.address };
|
|
12256
|
+
}
|
|
12257
|
+
function importWallet(name, privateKey, password) {
|
|
12258
|
+
ensureDir();
|
|
12259
|
+
const filePath = join3(WALLETS_DIR, `${name}.json`);
|
|
12260
|
+
if (existsSync3(filePath)) {
|
|
12261
|
+
throw new Error(`Wallet "${name}" already exists`);
|
|
12262
|
+
}
|
|
12263
|
+
const pk = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
12264
|
+
const { salt, iv, tag, ciphertext } = encrypt(pk, password);
|
|
12265
|
+
const wallet = {
|
|
12266
|
+
name,
|
|
12267
|
+
address: "(derived on load)",
|
|
12268
|
+
salt,
|
|
12269
|
+
iv,
|
|
12270
|
+
tag,
|
|
12271
|
+
ciphertext,
|
|
12272
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12273
|
+
};
|
|
12274
|
+
writeFileSync3(filePath, JSON.stringify(wallet, null, 2));
|
|
12275
|
+
log11.info(`Wallet "${name}" imported`);
|
|
12276
|
+
return { name, address: wallet.address };
|
|
12277
|
+
}
|
|
12278
|
+
function listWallets() {
|
|
12279
|
+
ensureDir();
|
|
12280
|
+
const files = readdirSync(WALLETS_DIR).filter((f) => f.endsWith(".json"));
|
|
12281
|
+
return files.map((f) => {
|
|
12282
|
+
const wallet = JSON.parse(readFileSync3(join3(WALLETS_DIR, f), "utf8"));
|
|
12283
|
+
return { name: wallet.name, address: wallet.address, createdAt: wallet.createdAt };
|
|
12284
|
+
});
|
|
12285
|
+
}
|
|
12286
|
+
function deleteWallet(name) {
|
|
12287
|
+
const filePath = join3(WALLETS_DIR, `${name}.json`);
|
|
12288
|
+
if (!existsSync3(filePath)) {
|
|
12289
|
+
throw new Error(`Wallet "${name}" not found`);
|
|
12290
|
+
}
|
|
12291
|
+
unlinkSync(filePath);
|
|
12292
|
+
log11.info(`Wallet "${name}" deleted`);
|
|
12293
|
+
}
|
|
12294
|
+
var log11, WALLETS_DIR, SCRYPT_N, SCRYPT_R, SCRYPT_P, KEY_LENGTH, IV_LENGTH;
|
|
12295
|
+
var init_wallet_manager = __esm({
|
|
12296
|
+
"src/core/agent/wallet-manager.ts"() {
|
|
12297
|
+
"use strict";
|
|
12298
|
+
init_logger();
|
|
12299
|
+
log11 = createLogger("wallet-manager");
|
|
12300
|
+
WALLETS_DIR = join3(homedir2(), ".vizzor", "wallets");
|
|
12301
|
+
SCRYPT_N = 2 ** 14;
|
|
12302
|
+
SCRYPT_R = 8;
|
|
12303
|
+
SCRYPT_P = 1;
|
|
12304
|
+
KEY_LENGTH = 32;
|
|
12305
|
+
IV_LENGTH = 12;
|
|
12306
|
+
}
|
|
12307
|
+
});
|
|
12308
|
+
|
|
12309
|
+
// src/cli/commands/wallet.ts
|
|
12310
|
+
var wallet_exports = {};
|
|
12311
|
+
__export(wallet_exports, {
|
|
12312
|
+
registerWalletCommand: () => registerWalletCommand
|
|
12313
|
+
});
|
|
12314
|
+
function registerWalletCommand(program2) {
|
|
12315
|
+
const wallet = program2.command("wallet").description("Manage encrypted trading wallets");
|
|
12316
|
+
wallet.command("create <name>").description("Create a new wallet with a random private key").requiredOption("-p, --password <password>", "Encryption password").action((name, opts) => {
|
|
12317
|
+
try {
|
|
12318
|
+
const result = createWallet(name, opts.password);
|
|
12319
|
+
console.log(`Wallet "${result.name}" created successfully.`);
|
|
12320
|
+
console.log(`Address: ${result.address}`);
|
|
12321
|
+
console.log("Stored in ~/.vizzor/wallets/");
|
|
12322
|
+
} catch (err) {
|
|
12323
|
+
log12.error(`Failed to create wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
12324
|
+
process.exitCode = 1;
|
|
12325
|
+
}
|
|
12326
|
+
});
|
|
12327
|
+
wallet.command("import <name>").description("Import a wallet from a private key").requiredOption("-k, --key <privateKey>", "Private key (hex)").requiredOption("-p, --password <password>", "Encryption password").action((name, opts) => {
|
|
12328
|
+
try {
|
|
12329
|
+
const result = importWallet(name, opts.key, opts.password);
|
|
12330
|
+
console.log(`Wallet "${result.name}" imported successfully.`);
|
|
12331
|
+
} catch (err) {
|
|
12332
|
+
log12.error(`Failed to import wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
12333
|
+
process.exitCode = 1;
|
|
12334
|
+
}
|
|
12335
|
+
});
|
|
12336
|
+
wallet.command("list").description("List all saved wallets").action(() => {
|
|
12337
|
+
const wallets = listWallets();
|
|
12338
|
+
if (wallets.length === 0) {
|
|
12339
|
+
console.log("No wallets found.");
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
12342
|
+
console.log("Wallets:");
|
|
12343
|
+
for (const w of wallets) {
|
|
12344
|
+
console.log(` ${w.name} \u2014 ${w.address} (created: ${w.createdAt})`);
|
|
12345
|
+
}
|
|
12346
|
+
});
|
|
12347
|
+
wallet.command("delete <name>").description("Delete a wallet").action((name) => {
|
|
12348
|
+
try {
|
|
12349
|
+
deleteWallet(name);
|
|
12350
|
+
console.log(`Wallet "${name}" deleted.`);
|
|
12351
|
+
} catch (err) {
|
|
12352
|
+
log12.error(`Failed to delete wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
12353
|
+
process.exitCode = 1;
|
|
12354
|
+
}
|
|
12355
|
+
});
|
|
12356
|
+
}
|
|
12357
|
+
var log12;
|
|
12358
|
+
var init_wallet2 = __esm({
|
|
12359
|
+
"src/cli/commands/wallet.ts"() {
|
|
12360
|
+
"use strict";
|
|
12361
|
+
init_wallet_manager();
|
|
12362
|
+
init_logger();
|
|
12363
|
+
log12 = createLogger("cli-wallet");
|
|
12364
|
+
}
|
|
12365
|
+
});
|
|
12366
|
+
|
|
12367
|
+
// src/data/sources/binance-ws.ts
|
|
12368
|
+
import { EventEmitter } from "events";
|
|
12369
|
+
var log13, BASE_URL7, BinanceWebSocket;
|
|
12370
|
+
var init_binance_ws = __esm({
|
|
12371
|
+
"src/data/sources/binance-ws.ts"() {
|
|
12372
|
+
"use strict";
|
|
12373
|
+
init_logger();
|
|
12374
|
+
log13 = createLogger("binance-ws");
|
|
12375
|
+
BASE_URL7 = "wss://stream.binance.com:9443/ws";
|
|
12376
|
+
BinanceWebSocket = class extends EventEmitter {
|
|
12377
|
+
ws = null;
|
|
12378
|
+
streams = [];
|
|
12379
|
+
reconnectDelay = 1e3;
|
|
12380
|
+
maxReconnectDelay = 3e4;
|
|
12381
|
+
heartbeatTimer = null;
|
|
12382
|
+
reconnecting = false;
|
|
12383
|
+
closed = false;
|
|
12384
|
+
constructor(streams) {
|
|
12385
|
+
super();
|
|
12386
|
+
this.streams = streams;
|
|
12387
|
+
}
|
|
12388
|
+
connect() {
|
|
12389
|
+
if (this.closed) return;
|
|
12390
|
+
const url = this.streams.length === 1 ? `${BASE_URL7}/${this.streams[0]}` : `${BASE_URL7}/${this.streams.join("/")}`;
|
|
12391
|
+
log13.debug(`Connecting to ${url}`);
|
|
12392
|
+
this.ws = new WebSocket(url);
|
|
12393
|
+
this.ws.onopen = () => {
|
|
12394
|
+
log13.info(`WebSocket connected (${this.streams.length} streams)`);
|
|
12395
|
+
this.reconnectDelay = 1e3;
|
|
12396
|
+
this.reconnecting = false;
|
|
12397
|
+
this.startHeartbeat();
|
|
12398
|
+
this.emit("connected");
|
|
12399
|
+
};
|
|
12400
|
+
this.ws.onmessage = (event) => {
|
|
12401
|
+
try {
|
|
12402
|
+
const data = JSON.parse(String(event.data));
|
|
12403
|
+
this.handleMessage(data);
|
|
12404
|
+
} catch {
|
|
12405
|
+
}
|
|
12406
|
+
};
|
|
12407
|
+
this.ws.onclose = () => {
|
|
12408
|
+
this.stopHeartbeat();
|
|
12409
|
+
if (!this.closed) {
|
|
12410
|
+
this.scheduleReconnect();
|
|
12411
|
+
}
|
|
12412
|
+
};
|
|
12413
|
+
this.ws.onerror = () => {
|
|
12414
|
+
log13.debug("WebSocket error");
|
|
12415
|
+
};
|
|
12416
|
+
}
|
|
12417
|
+
close() {
|
|
12418
|
+
this.closed = true;
|
|
12419
|
+
this.stopHeartbeat();
|
|
12420
|
+
if (this.ws) {
|
|
12421
|
+
this.ws.close();
|
|
12422
|
+
this.ws = null;
|
|
12423
|
+
}
|
|
12424
|
+
}
|
|
12425
|
+
handleMessage(data) {
|
|
12426
|
+
const eventType = data["e"];
|
|
12427
|
+
if (eventType === "trade") {
|
|
12428
|
+
const trade = {
|
|
12429
|
+
symbol: String(data["s"]),
|
|
12430
|
+
price: Number(data["p"]),
|
|
12431
|
+
quantity: Number(data["q"]),
|
|
12432
|
+
time: Number(data["T"]),
|
|
12433
|
+
isBuyerMaker: Boolean(data["m"])
|
|
12434
|
+
};
|
|
12435
|
+
this.emit("trade", trade);
|
|
12436
|
+
} else if (eventType === "kline") {
|
|
12437
|
+
const k = data["k"];
|
|
12438
|
+
const kline = {
|
|
12439
|
+
symbol: String(data["s"]),
|
|
12440
|
+
interval: String(k["i"]),
|
|
12441
|
+
open: Number(k["o"]),
|
|
12442
|
+
high: Number(k["h"]),
|
|
12443
|
+
low: Number(k["l"]),
|
|
12444
|
+
close: Number(k["c"]),
|
|
12445
|
+
volume: Number(k["v"]),
|
|
12446
|
+
openTime: Number(k["t"]),
|
|
12447
|
+
closeTime: Number(k["T"]),
|
|
12448
|
+
isClosed: Boolean(k["x"])
|
|
12449
|
+
};
|
|
12450
|
+
this.emit("kline", kline);
|
|
12451
|
+
} else if (eventType === "24hrTicker") {
|
|
12452
|
+
const ticker = {
|
|
12453
|
+
symbol: String(data["s"]),
|
|
12454
|
+
price: Number(data["c"]),
|
|
12455
|
+
priceChange: Number(data["p"]),
|
|
12456
|
+
priceChangePct: Number(data["P"]),
|
|
12457
|
+
volume: Number(data["v"]),
|
|
12458
|
+
quoteVolume: Number(data["q"])
|
|
12459
|
+
};
|
|
12460
|
+
this.emit("ticker", ticker);
|
|
12461
|
+
}
|
|
12462
|
+
}
|
|
12463
|
+
scheduleReconnect() {
|
|
12464
|
+
if (this.reconnecting || this.closed) return;
|
|
12465
|
+
this.reconnecting = true;
|
|
12466
|
+
log13.info(`Reconnecting in ${this.reconnectDelay}ms...`);
|
|
12467
|
+
setTimeout(() => {
|
|
12468
|
+
this.connect();
|
|
12469
|
+
}, this.reconnectDelay);
|
|
12470
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
12471
|
+
}
|
|
12472
|
+
startHeartbeat() {
|
|
12473
|
+
this.heartbeatTimer = setInterval(() => {
|
|
12474
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
12475
|
+
this.ws.send(JSON.stringify({ method: "PING" }));
|
|
12476
|
+
}
|
|
12477
|
+
}, 3e4);
|
|
12478
|
+
}
|
|
12479
|
+
stopHeartbeat() {
|
|
12480
|
+
if (this.heartbeatTimer) {
|
|
12481
|
+
clearInterval(this.heartbeatTimer);
|
|
12482
|
+
this.heartbeatTimer = null;
|
|
12483
|
+
}
|
|
12484
|
+
}
|
|
12485
|
+
};
|
|
12486
|
+
}
|
|
12487
|
+
});
|
|
12488
|
+
|
|
12489
|
+
// src/data/sources/ws-manager.ts
|
|
12490
|
+
var ws_manager_exports = {};
|
|
12491
|
+
__export(ws_manager_exports, {
|
|
12492
|
+
WSManager: () => WSManager,
|
|
12493
|
+
getWSManager: () => getWSManager,
|
|
12494
|
+
initWSManager: () => initWSManager
|
|
12495
|
+
});
|
|
12496
|
+
function getWSManager() {
|
|
12497
|
+
return instance2;
|
|
12498
|
+
}
|
|
12499
|
+
function initWSManager() {
|
|
12500
|
+
if (!instance2) {
|
|
12501
|
+
instance2 = new WSManager();
|
|
12502
|
+
}
|
|
12503
|
+
return instance2;
|
|
12504
|
+
}
|
|
12505
|
+
var log14, MAX_STREAMS_PER_CONN, MAX_CONNECTIONS, WSManager, instance2;
|
|
12506
|
+
var init_ws_manager = __esm({
|
|
12507
|
+
"src/data/sources/ws-manager.ts"() {
|
|
12508
|
+
"use strict";
|
|
12509
|
+
init_binance_ws();
|
|
12510
|
+
init_logger();
|
|
12511
|
+
log14 = createLogger("ws-manager");
|
|
12512
|
+
MAX_STREAMS_PER_CONN = 1024;
|
|
12513
|
+
MAX_CONNECTIONS = 5;
|
|
12514
|
+
WSManager = class {
|
|
12515
|
+
connections = [];
|
|
12516
|
+
priceCache = /* @__PURE__ */ new Map();
|
|
12517
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
12518
|
+
// symbol → stream types
|
|
12519
|
+
tradeCallbacks = /* @__PURE__ */ new Map();
|
|
12520
|
+
klineCallbacks = /* @__PURE__ */ new Map();
|
|
12521
|
+
started = false;
|
|
12522
|
+
subscribe(symbol, streams = ["trade", "ticker"]) {
|
|
12523
|
+
const existing = this.subscriptions.get(symbol) ?? /* @__PURE__ */ new Set();
|
|
12524
|
+
for (const s of streams) {
|
|
12525
|
+
existing.add(s);
|
|
12526
|
+
}
|
|
12527
|
+
this.subscriptions.set(symbol, existing);
|
|
12528
|
+
}
|
|
12529
|
+
unsubscribe(symbol) {
|
|
12530
|
+
this.subscriptions.delete(symbol);
|
|
12531
|
+
this.priceCache.delete(symbol.toUpperCase());
|
|
12532
|
+
}
|
|
12533
|
+
start() {
|
|
12534
|
+
if (this.started) return;
|
|
12535
|
+
this.started = true;
|
|
12536
|
+
const allStreams = [];
|
|
12537
|
+
for (const [symbol, types] of this.subscriptions) {
|
|
12538
|
+
const lower = symbol.toLowerCase();
|
|
12539
|
+
for (const type of types) {
|
|
12540
|
+
if (type === "trade") allStreams.push(`${lower}@trade`);
|
|
12541
|
+
else if (type === "kline_1m") allStreams.push(`${lower}@kline_1m`);
|
|
12542
|
+
else if (type === "ticker") allStreams.push(`${lower}@ticker`);
|
|
12543
|
+
}
|
|
12544
|
+
}
|
|
12545
|
+
if (allStreams.length === 0) {
|
|
12546
|
+
log14.info("No subscriptions \u2014 WebSocket manager idle");
|
|
12547
|
+
return;
|
|
12548
|
+
}
|
|
12549
|
+
for (let i = 0; i < allStreams.length; i += MAX_STREAMS_PER_CONN) {
|
|
12550
|
+
if (this.connections.length >= MAX_CONNECTIONS) break;
|
|
12551
|
+
const chunk = allStreams.slice(i, i + MAX_STREAMS_PER_CONN);
|
|
12552
|
+
const ws = new BinanceWebSocket(chunk);
|
|
12553
|
+
ws.on("trade", (trade) => {
|
|
12554
|
+
this.priceCache.set(trade.symbol, { price: trade.price, time: trade.time });
|
|
12555
|
+
const callbacks = this.tradeCallbacks.get(trade.symbol);
|
|
12556
|
+
if (callbacks) {
|
|
12557
|
+
for (const cb of callbacks) cb(trade);
|
|
12558
|
+
}
|
|
12559
|
+
});
|
|
12560
|
+
ws.on("ticker", (ticker) => {
|
|
12561
|
+
this.priceCache.set(ticker.symbol, { price: ticker.price, time: Date.now() });
|
|
12562
|
+
});
|
|
12563
|
+
ws.on("kline", (kline) => {
|
|
12564
|
+
const key = `${kline.symbol}:${kline.interval}`;
|
|
12565
|
+
const callbacks = this.klineCallbacks.get(key);
|
|
12566
|
+
if (callbacks) {
|
|
12567
|
+
for (const cb of callbacks) cb(kline);
|
|
12568
|
+
}
|
|
12569
|
+
});
|
|
12570
|
+
ws.connect();
|
|
12571
|
+
this.connections.push(ws);
|
|
12572
|
+
}
|
|
12573
|
+
log14.info(
|
|
12574
|
+
`WebSocket manager started: ${this.connections.length} connections, ${allStreams.length} streams`
|
|
12575
|
+
);
|
|
12576
|
+
}
|
|
12577
|
+
stop() {
|
|
12578
|
+
for (const ws of this.connections) {
|
|
12579
|
+
ws.close();
|
|
12580
|
+
}
|
|
12581
|
+
this.connections = [];
|
|
12582
|
+
this.started = false;
|
|
12583
|
+
log14.info("WebSocket manager stopped");
|
|
12584
|
+
}
|
|
12585
|
+
getLatestPrice(symbol) {
|
|
12586
|
+
const upper = symbol.toUpperCase().replace("/", "");
|
|
12587
|
+
const cached = this.priceCache.get(upper);
|
|
12588
|
+
if (!cached) return null;
|
|
12589
|
+
if (Date.now() - cached.time > 6e4) return null;
|
|
12590
|
+
return cached.price;
|
|
12591
|
+
}
|
|
12592
|
+
onTrade(symbol, callback) {
|
|
12593
|
+
const upper = symbol.toUpperCase();
|
|
12594
|
+
const set = this.tradeCallbacks.get(upper) ?? /* @__PURE__ */ new Set();
|
|
12595
|
+
set.add(callback);
|
|
12596
|
+
this.tradeCallbacks.set(upper, set);
|
|
12597
|
+
}
|
|
12598
|
+
onKline(symbol, interval, callback) {
|
|
12599
|
+
const key = `${symbol.toUpperCase()}:${interval}`;
|
|
12600
|
+
const set = this.klineCallbacks.get(key) ?? /* @__PURE__ */ new Set();
|
|
12601
|
+
set.add(callback);
|
|
12602
|
+
this.klineCallbacks.set(key, set);
|
|
12603
|
+
}
|
|
12604
|
+
};
|
|
12605
|
+
instance2 = null;
|
|
12606
|
+
}
|
|
12607
|
+
});
|
|
12608
|
+
|
|
10960
12609
|
// src/tui/components/status-bar.tsx
|
|
10961
12610
|
import React, { useState, useEffect } from "react";
|
|
10962
12611
|
import { Box, Text, Spacer } from "ink";
|
|
@@ -11081,7 +12730,7 @@ function WelcomeBanner() {
|
|
|
11081
12730
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
11082
12731
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: " " }),
|
|
11083
12732
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "blue", children: "vizzor" }),
|
|
11084
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: " v0.
|
|
12733
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: " v0.11.0" }),
|
|
11085
12734
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " \u2014 AI-powered crypto chronovisor" })
|
|
11086
12735
|
] }),
|
|
11087
12736
|
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " ML models: LSTM + Random Forest + Isolation Forest + GBM Rug + Wallet LSTM + DistilBERT NLP" }) }),
|
|
@@ -11769,6 +13418,8 @@ async function executeCommand(name, args2) {
|
|
|
11769
13418
|
return handleConfig(args2);
|
|
11770
13419
|
case "agent":
|
|
11771
13420
|
return handleAgent(args2);
|
|
13421
|
+
case "backtest":
|
|
13422
|
+
return handleBacktest(args2);
|
|
11772
13423
|
case "clear":
|
|
11773
13424
|
return { blocks: [], text: "" };
|
|
11774
13425
|
case "exit":
|
|
@@ -12118,6 +13769,7 @@ function handleHelp() {
|
|
|
12118
13769
|
" /trends Live trending tokens + top gainers/losers",
|
|
12119
13770
|
" /audit <contract> [--chain <chain>] Audit a smart contract (bytecode scanning)",
|
|
12120
13771
|
" /agent <sub> Manage autonomous trading agents",
|
|
13772
|
+
" /backtest -s <strat> --pair <P> --from <D> --to <D> Run a historical backtest",
|
|
12121
13773
|
" /provider [list|<name>] Show/switch AI provider",
|
|
12122
13774
|
" /config [set <key> <value>] Show or update configuration",
|
|
12123
13775
|
" /clear Clear message history",
|
|
@@ -12130,6 +13782,62 @@ function handleHelp() {
|
|
|
12130
13782
|
].join("\n");
|
|
12131
13783
|
return { blocks: [], text };
|
|
12132
13784
|
}
|
|
13785
|
+
async function handleBacktest(args2) {
|
|
13786
|
+
const stratIdx = args2.indexOf("-s") !== -1 ? args2.indexOf("-s") : args2.indexOf("--strategy");
|
|
13787
|
+
const pairIdx = args2.indexOf("--pair");
|
|
13788
|
+
const fromIdx = args2.indexOf("--from");
|
|
13789
|
+
const toIdx = args2.indexOf("--to");
|
|
13790
|
+
const tfIdx = args2.indexOf("--timeframe");
|
|
13791
|
+
const strategy = stratIdx !== -1 && stratIdx + 1 < args2.length ? args2[stratIdx + 1] : "";
|
|
13792
|
+
const pair = pairIdx !== -1 && pairIdx + 1 < args2.length ? args2[pairIdx + 1] : "";
|
|
13793
|
+
const from = fromIdx !== -1 && fromIdx + 1 < args2.length ? args2[fromIdx + 1] : "";
|
|
13794
|
+
const to = toIdx !== -1 && toIdx + 1 < args2.length ? args2[toIdx + 1] : "";
|
|
13795
|
+
const timeframe = tfIdx !== -1 && tfIdx + 1 < args2.length ? args2[tfIdx + 1] : "4h";
|
|
13796
|
+
if (!strategy || !pair || !from || !to) {
|
|
13797
|
+
return {
|
|
13798
|
+
blocks: [],
|
|
13799
|
+
text: "Usage: /backtest -s <strategy> --pair <PAIR> --from <YYYY-MM-DD> --to <YYYY-MM-DD> [--timeframe <tf>]"
|
|
13800
|
+
};
|
|
13801
|
+
}
|
|
13802
|
+
try {
|
|
13803
|
+
const { BacktestEngine: BacktestEngine2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
|
|
13804
|
+
const engine = new BacktestEngine2({
|
|
13805
|
+
strategy,
|
|
13806
|
+
pair,
|
|
13807
|
+
from,
|
|
13808
|
+
to,
|
|
13809
|
+
initialCapital: 1e4,
|
|
13810
|
+
timeframe,
|
|
13811
|
+
slippageBps: 10,
|
|
13812
|
+
commissionPct: 0.1
|
|
13813
|
+
});
|
|
13814
|
+
const result = await engine.run();
|
|
13815
|
+
const lines = [
|
|
13816
|
+
`Backtest: ${result.config.strategy} on ${result.config.pair} (${result.config.from} \u2192 ${result.config.to})`,
|
|
13817
|
+
`Initial Capital: $${result.config.initialCapital.toLocaleString()}`,
|
|
13818
|
+
"",
|
|
13819
|
+
`Total Return: $${result.metrics.totalReturn.toFixed(2)} (${result.metrics.totalReturnPct.toFixed(2)}%)`,
|
|
13820
|
+
`Win Rate: ${(result.metrics.winRate * 100).toFixed(1)}%`,
|
|
13821
|
+
`Total Trades: ${result.metrics.totalTrades}`,
|
|
13822
|
+
`Profit Factor: ${result.metrics.profitFactor === Infinity ? "\u221E" : result.metrics.profitFactor.toFixed(2)}`,
|
|
13823
|
+
`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(2)}`,
|
|
13824
|
+
`Max Drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`
|
|
13825
|
+
];
|
|
13826
|
+
if (result.trades.length > 0) {
|
|
13827
|
+
lines.push("", "Last 5 Trades:");
|
|
13828
|
+
for (const t of result.trades.slice(-5)) {
|
|
13829
|
+
const dir = t.pnl >= 0 ? "+" : "";
|
|
13830
|
+
lines.push(
|
|
13831
|
+
` ${new Date(t.entryTime).toISOString().split("T")[0]} \u2192 ${new Date(t.exitTime).toISOString().split("T")[0]} | ${t.side} | $${t.entryPrice.toFixed(2)} \u2192 $${t.exitPrice.toFixed(2)} | ${dir}$${t.pnl.toFixed(2)} (${dir}${t.pnlPct.toFixed(2)}%)`
|
|
13832
|
+
);
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
return { blocks: [], text: lines.join("\n") };
|
|
13836
|
+
} catch (err) {
|
|
13837
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13838
|
+
return { blocks: [], text: `Backtest failed: ${message}` };
|
|
13839
|
+
}
|
|
13840
|
+
}
|
|
12133
13841
|
async function handleConfig(args2) {
|
|
12134
13842
|
if (args2[0] === "set") {
|
|
12135
13843
|
const key = args2[1];
|
|
@@ -12711,12 +14419,12 @@ var init_app = __esm({
|
|
|
12711
14419
|
// src/index.ts
|
|
12712
14420
|
init_loader();
|
|
12713
14421
|
import { Command } from "commander";
|
|
12714
|
-
import { readFileSync as
|
|
14422
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
12715
14423
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12716
14424
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
12717
14425
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
12718
14426
|
var __dirname2 = dirname2(__filename2);
|
|
12719
|
-
var pkg = JSON.parse(
|
|
14427
|
+
var pkg = JSON.parse(readFileSync4(resolve3(__dirname2, "..", "package.json"), "utf-8"));
|
|
12720
14428
|
var program = new Command().name("vizzor").description("Crypto chronovisor \u2014 AI-powered on-chain intelligence").version(pkg.version);
|
|
12721
14429
|
program.hook("preAction", async () => {
|
|
12722
14430
|
await loadConfig();
|
|
@@ -12791,9 +14499,30 @@ apiKeyCmd.command("revoke <id>").description("Revoke an API key").action(async (
|
|
|
12791
14499
|
const { handleApiKeyRevoke: handleApiKeyRevoke2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
12792
14500
|
handleApiKeyRevoke2(id);
|
|
12793
14501
|
});
|
|
14502
|
+
apiCmd.command("start").description("Start the REST API server").option("--port <port>", "Server port", (v) => parseInt(v, 10), 3e3).option("--host <host>", "Server host", "0.0.0.0").option("--auth", "Enable API key authentication", false).action(async (options) => {
|
|
14503
|
+
const { handleServe: handleServe2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
|
|
14504
|
+
await handleServe2(options);
|
|
14505
|
+
});
|
|
14506
|
+
var { registerBacktestCommand: registerBacktestCommand2 } = await Promise.resolve().then(() => (init_backtest2(), backtest_exports));
|
|
14507
|
+
registerBacktestCommand2(program);
|
|
14508
|
+
var { registerWalletCommand: registerWalletCommand2 } = await Promise.resolve().then(() => (init_wallet2(), wallet_exports));
|
|
14509
|
+
registerWalletCommand2(program);
|
|
12794
14510
|
var args = process.argv.slice(2);
|
|
12795
14511
|
if (args.length === 0) {
|
|
12796
14512
|
await loadConfig();
|
|
14513
|
+
const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
|
|
14514
|
+
try {
|
|
14515
|
+
const config2 = getConfig2();
|
|
14516
|
+
if (config2.realtime?.enabled) {
|
|
14517
|
+
const { initWSManager: initWSManager2 } = await Promise.resolve().then(() => (init_ws_manager(), ws_manager_exports));
|
|
14518
|
+
const wsManager = initWSManager2();
|
|
14519
|
+
for (const symbol of config2.realtime.symbols) {
|
|
14520
|
+
wsManager.subscribe(symbol);
|
|
14521
|
+
}
|
|
14522
|
+
wsManager.start();
|
|
14523
|
+
}
|
|
14524
|
+
} catch {
|
|
14525
|
+
}
|
|
12797
14526
|
const { startTUI: startTUI2 } = await Promise.resolve().then(() => (init_app(), app_exports));
|
|
12798
14527
|
startTUI2();
|
|
12799
14528
|
} else {
|