qe-cli
Version:
qe-cli
585 lines (584 loc) • 24.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const PUBLIC_APPDATA_DIR = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
const updateNotifier = require('update-notifier');
const commander_1 = require("commander");
const fs = require("fs");
const https = require("https");
const axios_1 = require("axios");
const chalk = require("chalk");
const moment = require("moment");
const boxen = require("boxen");
const ws_client_1 = require("./util/ws_client");
const inquirer = require("inquirer");
const str_1 = require("./util/str");
const ps_1 = require("./util/ps");
const webpack = require("webpack");
let package_info;
let version = require(__dirname + "/package.json").version;
const pkg_info = require('./package.json');
const server = "https://api.quantengine.com/";
const ws_server = "wss://api.quantengine.com/websocket";
let quantengine_module_pkg_path = path.join(process.cwd(), "./node_modules/quantengine/package.json");
const program = new commander_1.Command();
const prompt = inquirer.createPromptModule();
const axiosInstance = axios_1.default.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
const type_str_len = 7;
let debug_start = false;
let debug_session = "";
let debug_id = null;
let all_version = "";
const compiler = webpack({
entry: './main.ts',
mode: 'production',
target: 'node',
output: {
filename: 'main_out.js',
},
});
(function () {
let version_list = [`CLI版本: ${chalk.cyanBright(version)}`];
let quantengine_pkg;
if (fs.existsSync(quantengine_module_pkg_path)) {
quantengine_pkg = require(quantengine_module_pkg_path);
}
version_list.push(`SDK版本: ${quantengine_pkg?.version ? chalk.cyanBright(quantengine_pkg.version) : chalk.gray("unknown")}`);
all_version = version_list.join("\n");
})();
process.on('SIGINT', function () {
try_stop().then(() => process.exit(0)).catch(() => {
console.log(chalk.redBright("用户强制结束调试"));
process.exit(1);
});
});
if (fs.existsSync(path.join(process.cwd(), "./package.json"))) {
package_info = require(path.join(process.cwd(), "./package.json"));
}
let key;
if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"))) {
key = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json")).toString()).key;
}
if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/config.json"))) {
let config = {};
try {
config = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/config.json")).toString());
}
catch (e) {
console.error("Parse Error Occurred:", e);
}
}
program
.name("qe-cli " + version)
.option('-l, --login', "使用开发者密钥登录qe-cli")
.option('-i, --init', "初始化一个新策略")
.option('-d, --debug ', '调试策略')
.option('-p, --publish [patch|minor|major]', "发布策略")
.option('-u, --update', "更新qe开发工具包")
.helpOption('-h, --help', "帮助信息")
.version(all_version, "-v, --version", "版本信息");
program.parse(process.argv);
(async () => {
await check_update();
const options = program.opts();
if (options.init) {
await init();
}
else if (options.login) {
await login();
}
else if (options.debug) {
await debug();
}
else if (options.publish) {
await publish();
}
else if (options.update) {
await update_sdk();
}
else {
program.help();
}
return 0;
})().catch(e => {
console.error("Error Occurred:", e);
process.exit(1);
}).then(() => {
process.exit(0);
});
async function check_update() {
let cli_notifier;
let sdk_notifier;
let quantengine_pkg;
let update_msg = [];
let update_msg_str;
if (pkg_info && (pkg_info?.name) && (pkg_info?.version)) {
cli_notifier = updateNotifier({
pkg: pkg_info,
updateCheckInterval: 300 * 1000
});
}
if (fs.existsSync(quantengine_module_pkg_path)) {
quantengine_pkg = require(quantengine_module_pkg_path);
}
if (quantengine_pkg && (quantengine_pkg?.name) && (quantengine_pkg?.version)) {
sdk_notifier = updateNotifier({
pkg: quantengine_pkg,
updateCheckInterval: 300 * 1000
});
}
if (cli_notifier?.update && (cli_notifier?.update?.current !== cli_notifier?.update?.latest)) {
update_msg.push(`CLI Update Available. ${chalk.gray(cli_notifier?.update?.current)} → ${chalk.greenBright(cli_notifier?.update?.latest)}`);
}
if (sdk_notifier?.update && (sdk_notifier?.update?.current !== sdk_notifier?.update?.latest)) {
update_msg.push(`SDK Update Available. ${chalk.gray(sdk_notifier?.update?.current)} → ${chalk.greenBright(sdk_notifier?.update?.latest)}`);
}
if (update_msg.length > 0) {
update_msg_str = update_msg.join("\n");
}
if (update_msg_str) {
console.log(boxen(`${update_msg_str}\nRun ${chalk.cyanBright('qe-cli -u')} to update.`, {
borderColor: "yellow",
padding: 1,
margin: 1,
borderStyle: 'round',
textAlignment: 'center'
}));
}
}
async function init() {
if (!(await try_login(key))) {
return;
}
let init_sdk = false;
if (package_info) {
console.log(chalk.redBright("此目录已包含package.json配置,请在空目录下执行 qe-cli -i 初始化。"));
return;
}
if (!package_info) {
package_info = {
name: "strategy_project",
version: "1.0.0",
description: "Strategy Project Workspace",
QuantEngine: {},
dependencies: {}
};
init_sdk = true;
}
package_info.QuantEngine = package_info.QuantEngine || {};
let project;
while (true) {
let result = await prompt([
{
type: 'input',
name: "project",
message: "策略名",
default: "strategy_" + str_1.str.randomStr(),
prefix: ">",
suffix: ":"
}
]);
if (package_info.QuantEngine[result.project]) {
console.log(chalk.redBright(" 该策略已存在"));
}
else {
project = result.project;
break;
}
}
process.stdout.write(chalk.blueBright("正在创建策略..."));
let response = await safe_api("strategy/create", key, { name: project, launcher_id: 2 });
if (response.code === 1) {
console.log(chalk.yellowBright(" 创建完成"));
package_info.name = project;
package_info.QuantEngine = response.data.id;
package_info.scripts = {
"debug": "qe-cli -d",
"publish": "qe-cli -p"
};
fs.writeFileSync("package.json", JSON.stringify(package_info, null, 2), { encoding: "utf-8" });
let ts_code_template = fs.readFileSync(path.join(__dirname, "./template/template_ts"), { encoding: "utf-8" });
fs.writeFileSync(path.join(process.cwd(), `./main.ts`), ts_code_template, { encoding: "utf-8" });
fs.writeFileSync(path.join(process.cwd(), `./config.json`), "[]", { encoding: "utf-8" });
fs.writeFileSync(path.join(process.cwd(), `./action.json`), "[]", { encoding: "utf-8" });
if (init_sdk) {
process.stdout.write(chalk.blueBright("正在安装策略SDK..."));
await ps_1.ps.exec("npm i qe-runtime@latest");
await ps_1.ps.exec("npm i -D typescript@4.3.5");
let tsconfig_template = fs.readFileSync(path.join(__dirname, "./template/tsconfig"), { encoding: "utf-8" });
fs.writeFileSync(path.join(process.cwd(), `./tsconfig.json`), tsconfig_template, { encoding: "utf-8" });
}
console.log(chalk.yellowBright(" 策略创建成功, 名称:" + project + " ID:" + response.data.id + " 开发者权限使用量:" + response.data.created + "/" + response.data.limit));
}
else {
console.log(chalk.redBright(" " + response.error));
}
}
async function login() {
let key;
if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"))) {
key = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json")).toString()).key;
}
while (true) {
let result = await prompt([
{
type: 'input', name: "token", message: "请输入开发者密钥", default: key, prefix: ">", suffix: ":"
}
]);
process.stdout.write(chalk.blueBright("正在校验开发者密钥..."));
key = result.token;
let response = await safe_api("user/check", key, {});
if (response.code === 1) {
if (!fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli"))) {
fs.mkdirSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli"));
}
fs.writeFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"), JSON.stringify({ key: key }));
console.log(chalk.yellowBright(" 校验通过"));
break;
}
else {
console.log(chalk.redBright(response.error));
}
}
}
async function debug() {
if (!(await try_login(key))) {
return;
}
let strategy_code;
let actions;
let config;
process.stdout.write(chalk.blueBright("正在编译策略代码..."));
let start = Date.now();
if (fs.existsSync(path.join(process.cwd(), "dist", "main_out.js"))) {
await fs.unlinkSync(path.join(process.cwd(), "dist", "main_out.js"));
}
await new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (stats?.hasErrors()) {
console.log(chalk.redBright("策略编译出错了..."));
reject(stats.compilation.errors);
}
compiler.close((closeErr) => {
if (closeErr) {
console.log(closeErr);
reject(closeErr);
}
return resolve(true);
});
});
});
console.log(chalk.yellowBright(" 编译完成,耗时:" + (Date.now() - start) + "毫秒"));
try {
strategy_code = fs.readFileSync(path.join(process.cwd(), "dist", "main_out.js")).toString("utf8");
}
catch (e) {
console.log(chalk.redBright("无法读取策略代码文件,请检查策略代码。"));
process.exit(0);
}
try {
actions = JSON.parse(fs.readFileSync(path.join(process.cwd(), "action.json")).toString("utf8"));
}
catch (e) {
console.log(chalk.redBright("无法读取策略动作文件["), chalk.blueBright(`action.json`), chalk.redBright("]。"));
process.exit(0);
}
try {
config = JSON.parse(fs.readFileSync(path.join(process.cwd(), "config.json")).toString("utf8"));
}
catch (e) {
console.log(chalk.redBright("无法读取策略配置文件["), chalk.blueBright(`config.json`), chalk.redBright("]。"));
process.exit(0);
}
if (!package_info.QuantEngine) {
return console.log(chalk.redBright("无法找到这个策略的ID。"));
}
process.stdout.write(chalk.blueBright("正在连接调试日志服务器..."));
let key_response = await safe_api("strategy/get_debug_info", key, {
strategy_id: package_info.QuantEngine
});
if (key_response.code !== 1) {
return console.log(chalk.redBright("CODE:" + key_response.code + " Get Debug Token Error:" + key_response.error || "Internal Error"));
}
await ConnectLogServer(key_response.data.user_strategy_id);
console.log(chalk.yellowBright(" 完成"));
process.stdout.write(chalk.blueBright("正在启动调试节点..."));
let response = await safe_api("strategy/debug", key, {
strategy_id: package_info.QuantEngine,
actions: actions,
config_template: config,
strategy_code_type: "js",
strategy_code: strategy_code,
bot_hook: false,
strategy_hook: false,
});
if (response.code !== 1) {
return console.log(chalk.redBright("CODE:" + response.code + " Debug Service Error:" + response.error));
}
else {
console.log(chalk.yellowBright(" 完成"));
console.log(chalk.cyanBright("======================================================"));
console.log(chalk.yellowBright("开始调试策略,节点区域:" + response.node_name + " 实盘会话:" + response.session));
console.log(chalk.yellowBright("WebHook状态: "), "Bot ->", response.bot_hook_enable ? chalk.blueBright("Enable") : chalk.gray("Disable"), " Strategy ->", response?.strategy_hook_enable ? chalk.blueBright("Enable") : chalk.gray("Disable"));
console.log(chalk.yellowBright("实盘监控地址: " + response.shout_url));
if (response.hook_enable) {
try {
let hook_address_response = await safe_api("user_strategy/get_hook_address", key, {
user_strategy_id: response.debug_id_enc
});
if (hook_address_response.code === 1) {
console.log(chalk.greenBright("BotHookURL:"), hook_address_response?.data?.result || "");
}
else {
console.log(chalk.gray("Get BotHook URL Error ,CODE:"), chalk.gray(hook_address_response.code));
}
}
catch (e) {
console.log(chalk.gray("Get Bot WebHook URL Error ,You Can Restart Debug If Necessary."), chalk.gray(e.toString()));
}
}
if (response.strategy_hook_enable) {
try {
let hook_address_response = await safe_api("cli/strategy/get_hook_address", key, {
uuid: package_info.QuantEngine
});
if (hook_address_response.code === 1) {
console.log(chalk.greenBright("StrategyHookURL:"), hook_address_response?.data?.result || "");
}
else {
console.log(chalk.gray("Get StrategyHook URL Error ,CODE:"), chalk.gray(hook_address_response.code));
}
}
catch (e) {
console.log(chalk.gray("Get Strategy WebHook URL Error ,You Can Restart Debug If Necessary."), chalk.gray(e.toString()));
}
}
console.log(chalk.cyanBright("======================================================"));
debug_id = response.debug_id_enc;
debug_start = true;
debug_session = response.session;
let offline_count = 0;
await ps_1.ps.sleep(1000);
while (true) {
let result = await safe_api("bot/get_user_strategy", key, {
session: response.session,
});
if (result.code === 1 && !result.data.running) {
if (offline_count % 3 === 0 && offline_count > 0) {
console.log(chalk.gray("未能获取到策略运行状态..."));
}
if (offline_count >= 20) {
console.log(chalk.blueBright("由于长时间未能获取到策略运行状态,本次调试结束。"));
process.exit(0);
}
offline_count++;
}
else if (result.code !== 1) {
offline_count++;
}
if (result.code === 1 && result.data.running) {
offline_count = 0;
}
await ps_1.ps.sleep(1000);
}
}
}
async function publish() {
if (!(await try_login(key))) {
return;
}
let publish_type = process.argv[3] || "patch";
process.stdout.write(chalk.blueBright("正在编译策略代码..."));
let start = Date.now();
if (fs.existsSync(path.join(process.cwd(), "dist", "main_out.js"))) {
await fs.unlinkSync(path.join(process.cwd(), "dist", "main_out.js"));
}
await new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (stats?.hasErrors()) {
console.log(chalk.redBright("策略编译出错了..."));
reject(stats.compilation.errors);
}
compiler.close((closeErr) => {
if (closeErr) {
console.log(closeErr);
reject(closeErr);
}
return resolve(true);
});
});
});
console.log(chalk.yellowBright(" 编译完成,耗时:" + (Date.now() - start) + "毫秒"));
let strategy_code;
let actions;
let config;
try {
strategy_code = fs.readFileSync(path.join(process.cwd(), "dist", "main_out.js")).toString("utf8");
}
catch (e) {
console.log(chalk.redBright("无法读取策略代码文件,请检查策略代码。"));
process.exit(0);
}
try {
actions = JSON.parse(fs.readFileSync(path.join(process.cwd(), "action.json")).toString("utf8"));
}
catch (e) {
console.log(chalk.redBright("无法读取策略动作文件["), chalk.blueBright(`action.json`), chalk.redBright("]。"));
process.exit(0);
}
try {
config = JSON.parse(fs.readFileSync(path.join(process.cwd(), "config.json")).toString("utf8"));
}
catch (e) {
console.log(chalk.redBright("无法读取策略配置文件["), chalk.blueBright(`config.json`), chalk.redBright("]。"));
process.exit(0);
}
process.stdout.write(chalk.blueBright("正在发布策略..."));
let response = await safe_api("strategy/publish", key, {
strategy_id: package_info.QuantEngine,
strategy_code: strategy_code,
actions: actions,
publish_type: publish_type,
strategy_code_type: "js",
strategy_code_src: strategy_code
});
if (response.code !== 1) {
return console.log(" " + chalk.redBright(response.error));
}
else {
console.log(chalk.yellowBright(" 发布成功,当前版本ID:" + response.data.id + " 版本号: " + response.data.old_version + " => " + response.data.current_version));
}
}
async function update_sdk() {
const qe_runtime_old_version = package_info?.dependencies?.["qe-runtime"]?.replace("^", "") || null;
if (qe_runtime_old_version) {
process.stdout.write(chalk.blueBright("正在更新qe-runtime..."));
let remote_version = await axios_1.default.get("https://registry.npmjs.org/qe-runtime/latest");
if (qe_runtime_old_version == remote_version?.data?.version) {
console.log(chalk.yellowBright(" " + qe_runtime_old_version + " == " + remote_version?.data?.version));
}
else {
await ps_1.ps.exec("npm i qe-runtime@latest");
console.log(chalk.yellowBright(" " + qe_runtime_old_version + " => " + package_info?.dependencies?.["qe-runtime"]?.replace("^", "")));
}
}
let updated_cli_info = JSON.parse(fs.readFileSync(path.join(__dirname, "./package.json"), { encoding: "utf-8" }));
const cli_old_version = updated_cli_info?.version || null;
process.stdout.write(chalk.blueBright("正在更新qe-cli..."));
let remote_version = await axios_1.default.get("https://registry.npmjs.org/qe-cli/latest");
if (cli_old_version == remote_version?.data?.version) {
console.log(chalk.yellowBright(" " + cli_old_version + " == " + remote_version?.data?.version));
}
else {
await ps_1.ps.exec("npm i -g qe-cli@latest");
updated_cli_info = JSON.parse(fs.readFileSync(path.join(__dirname, "./package.json"), { encoding: "utf-8" }));
console.log(chalk.yellowBright(" " + cli_old_version + " => " + updated_cli_info?.version));
}
process.exit(0);
}
async function try_login(key) {
process.stdout.write(chalk.blueBright("正在校验开发者密钥..."));
let response = await safe_api("user/check", key, {});
if (response.code === 1) {
console.log(chalk.yellowBright(" 校验通过"));
return true;
}
else {
console.log(chalk.redBright(" " + response.error));
return false;
}
}
async function try_stop() {
try {
if (debug_id) {
let response = await safe_api("strategy/stop", key, {
user_strategy_id: debug_id,
});
}
}
catch (e) {
}
}
async function safe_api(api, token, data) {
try {
if (!token || token.length != 32) {
return { code: -997, error: `Token Length Error.` };
}
let response = await axiosInstance.post(server + api, {
x: str_1.str.aes_encrypt(JSON.stringify(data), token, token.substring(0, 16))
}, {
headers: {
token: token,
"user-agent": "qe-cli/" + version
},
timeout: 10 * 1000,
});
if (response.status == 200 && response.data.x) {
return JSON.parse(str_1.str.aes_decrypt(response.data.x, token, token.substring(0, 16)));
}
else {
return {
code: -998,
error: "Not Safe Api"
};
}
}
catch (e) {
return {
code: -999,
error: e.toString()
};
}
}
async function ConnectLogServer(key, connect_info = true) {
return new Promise(function (resolve, reject) {
const client = new ws_client_1.ws_client({
url: ws_server,
debug: false
});
client.on("connect", (id) => {
client.send({ event: "robot", data: [key] });
return resolve(true);
});
client.on("disconnect", (id) => {
console.log(chalk.redBright("日志服务器已断开,正在重连..."));
});
client.on("onmessage", (event, args, cb) => {
if (args?.log?.type) {
switch (args?.log?.type) {
case "info":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.gray(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
case "success":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.green(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
case "warning":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.yellow(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
case "error":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.red(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
case "debug":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.yellow(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
case "system":
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.cyan(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
break;
default:
console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.gray(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message);
}
if (args?.log.stop_sig === true) {
console.log(chalk.cyanBright("======================================================"));
console.log(chalk.blueBright("策略运行结束,调试完毕。"));
process.exit(0);
}
}
});
client.connect().then();
});
}