diff --git a/config/development.json b/config/development.json index 145c363..051bd4f 100644 --- a/config/development.json +++ b/config/development.json @@ -6,7 +6,7 @@ "username": "root", "timezone": "+09:00", "operatorsAliases": false, - "logging": true + "logging": false }, "account": { "id": "xxx", @@ -22,16 +22,16 @@ "strategies": { "sniper": { "sell_k": 85, - "buy_k": 15 + "buy_k": 20 } }, "backtest": { - "test": true, + "test": false, "isLastDate": false, "date": "2017-11-01", - "interval": 1 + "interval": 3000 }, "ea": { - "interval": 60 + "interval": 30000 } } \ No newline at end of file diff --git a/config/test.json b/config/test.json index 2c36c27..dbacfae 100644 --- a/config/test.json +++ b/config/test.json @@ -22,16 +22,16 @@ "strategies": { "sniper": { "sell_k": 85, - "buy_k": 15 + "buy_k": 20 } }, "backtest": { "test": true, "isLastDate": false, "date": "2017-11-01", - "interval": 1 + "interval": 60000 }, "ea": { - "interval": 60 + "interval": 60000 } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 702880b..5ad954b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ns-expert-advisor", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2825,9 +2825,9 @@ } }, "ns-manager": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/ns-manager/-/ns-manager-0.0.18.tgz", - "integrity": "sha512-sedRU+lAh9DBl4LStGGsH6QUeKBGwdpQVJW4PjLk4D2IKLYh9dwu/coy1+5WpLo6lFoAIii3gA9v/ILu06q4aw==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/ns-manager/-/ns-manager-0.0.24.tgz", + "integrity": "sha512-IVS4ofi4YXtkfeysX6JtiKSOqRJLp7MjGFJJ0yWve1tBv/VYWsPdFpmY7Ts1QvRAUroQr+Xw6rKzRlGa6OMsDg==", "requires": { "ns-common": "https://registry.npmjs.org/ns-common/-/ns-common-0.0.11.tgz", "ns-store": "0.0.27", @@ -5109,4 +5109,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 965c336..bbf9edc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ns-expert-advisor", - "version": "0.0.1", + "version": "0.0.2", "description": "node-stock expert advisor app", "repository": { "type": "git", @@ -24,7 +24,7 @@ "mysql2": "^1.4.2", "ns-common": "0.0.11", "ns-findata": "0.0.20", - "ns-manager": "0.0.18", + "ns-manager": "0.0.24", "ns-store": "0.0.27", "ns-strategies": "0.0.5", "ns-trader": "0.0.8", diff --git a/src/index.ts b/src/index.ts index a1e944d..f3d3e80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,8 +12,9 @@ if (Util.isTradeTime()) { expertAdvisor.start(); } Log.system.info('注册定时EA服务程序[开始]'); -const eaTask = new Scheduler('0 9 * * *'); // */3 * * * * * +const eaTask = new Scheduler('0 9 * * *'); // */3 * * * * * // 0 9 * * * eaTask.invok((ea: ExpertAdvisor) => { + // eaTask.reminder.cancel() if (!Util.isTradeDate(new Date())) { Log.system.info('当前非交易日,不启动EA程序'); return; diff --git a/src/lib/expert-advisor.test.ts b/src/lib/expert-advisor.test.ts index 4f3c79a..4b30e77 100644 --- a/src/lib/expert-advisor.test.ts +++ b/src/lib/expert-advisor.test.ts @@ -26,8 +26,8 @@ const testGet5minData = async (done: () => void) => { } const testOnPretrade = async (done: () => void) => { - const hisData: types.Bar[] = await expertAdvisor._getTest5minData('6553'); - await expertAdvisor.updBalance() + const hisData: types.Bar[] = await expertAdvisor._getTest5minData('6664'); + await expertAdvisor.updAsset() for (let i = 0; i < hisData.length; i++) { await expertAdvisor.onPretrade(); } diff --git a/src/lib/expert-advisor.ts b/src/lib/expert-advisor.ts index a23042d..7d9dc7e 100644 --- a/src/lib/expert-advisor.ts +++ b/src/lib/expert-advisor.ts @@ -11,7 +11,7 @@ const Loki = require('lokijs'); export class ExpertAdvisor { symbol: string; - account: { id: string, balance: number }; + account: types.Model.Account; backtest: { test: boolean, isLastDate: string, @@ -41,7 +41,7 @@ export class ExpertAdvisor { this.interval = this.backtest.test ? this.backtest.interval : config.ea.interval; this.manager = new Manager(); this.trader = new Trader(config); - this.account = { id: config.account.userId, balance: 0 }; + this.account = { id: config.account.userId }; this.dataProvider = new DataProvider(config.store); } @@ -53,18 +53,21 @@ export class ExpertAdvisor { async start() { await this.dataProvider.init() - // 更新余额 - await this.updBalance(); + // 更新资产 + await this.updAsset(); if (this.account.balance === 0) { Log.system.warn(`账户:${this.account.id},可用余额:0,不执行EA程序!`); return; } - setInterval(this.onPretrade, this.interval); + setInterval(this.onPretrade.bind(this), this.interval); await this.trader.init(); } - async updBalance() { - this.account.balance = await this.manager.asset.getBalance(this.account.id); + async updAsset() { + const res = await this.manager.asset.get(this.account.id); + if (res) { + this.account = res; + } } async _getTest5minData(symbol: string): Promise { @@ -88,22 +91,32 @@ export class ExpertAdvisor { } async getTest5minData(symbol: string): Promise { - const hisData: types.Bar[] = await this._getTest5minData(symbol); - if (hisData.length === 0) { - throw new Error('回测环境未获取5分钟线数据!'); - } const loki = this.backtest.loki; - let coll = loki.getCollection(symbol); - if (!coll) { - coll = loki.addCollection(symbol); + const inCollName = 'i_' + symbol; + + let inColl = loki.getCollection(inCollName); + let hisData: types.Bar[] = []; + // 股票输入表为空时,通过接口获取数据 + if (!inColl) { + hisData = await this._getTest5minData(symbol); + if (hisData.length === 0) { + throw new Error('回测环境未获取5分钟线数据!'); + } + inColl = loki.addCollection(inCollName); + inColl.insert(JSON.parse(JSON.stringify(hisData))); + } else { + hisData = inColl.chain().find().data({ removeMeta: true }); + } + // 取出数据导入输出表 + let outColl = loki.getCollection(symbol); + if (!outColl) { + outColl = loki.addCollection(symbol); // 插入第一条数据 - // TODO coll.insert(hisData[0]); - coll.insert(hisData.slice(0, 15)); + outColl.insert(hisData[0]); } else { - // 插入下一条数据 - coll.insert(hisData[coll.find().length]); + outColl.insert(hisData[outColl.find().length]); } - return coll.chain().find().data({ removeMeta: true }); + return outColl.chain().find().data({ removeMeta: true }); } async get5minData(symbol: string): Promise { @@ -167,6 +180,7 @@ export class ExpertAdvisor { Log.system.info('处理信号[启动]'); const price: number = numeral(hisData[hisData.length - 1].close).value(); const time = moment.unix((hisData[hisData.length - 1].time) / 1000).format('YYYY-MM-DD HH:mm:ss'); + Log.system.info(`symbol:${symbol}, price:${price}, time:${time}`); // 买入信号 if (singal.side === types.OrderSide.Buy) { Log.system.info('买入信号'); @@ -187,8 +201,8 @@ export class ExpertAdvisor { await this.manager.trader.set(this.account, order); // 消除信号 await this.manager.signal.remove(singal.id); - // 更新余额 - await this.updBalance(); + // 更新资产 + await this.updAsset(); } else if (singal.price > price) { // 股价继续下跌 Log.system.info('更新买入信号股价', price); singal.price = price; @@ -201,25 +215,25 @@ export class ExpertAdvisor { } } else if (singal.side === types.OrderSide.Sell) { Log.system.info('卖出信号'); - const posiInput: { [Attr: string]: any } = { - symbol: symbol, - account_id: this.account.id, - side: types.OrderSide.Buy - }; - if (this.backtest.test) { - posiInput.backtest = '1'; - posiInput.mocktime = time; + // 查询是否有持仓 + let position: types.Model.Position | undefined; + if (this.account.positions) { + position = this.account.positions.find((posi) => { + return posi.symbol === String(symbol) && posi.side === types.OrderSide.Buy; + }) } - - // 获取持仓 - const position = await this.manager.position.get(posiInput); - if (!position || !position.price) { - throw new Error(`持仓:${JSON.stringify(position)},卖出信号出错`); + if (!position) { + Log.system.warn('未查询到持仓,不进行卖出!'); + return; } Log.system.info(`获取持仓:${JSON.stringify(position)}`); - Log.system.info(`信号股价(${singal.price}) > 当前股价(${price}) && 盈利超过1000(${price} - ${position.price} > 10)`); - // 信号出现时股价 > 当前股价(股价下跌) && 并且盈利超过1000 - if (singal.price > price && price - position.price > 10) { + if (!position.price) { + Log.system.error('持仓股价为空!'); + return; + } + Log.system.info(`信号股价(${singal.price}) > 当前股价(${price}) && 盈利超过700(${price} - ${position.price} > 7)`); + // 信号出现时股价 > 当前股价(股价下跌) && 并且盈利超过700 + if (singal.price > price && price - position.price > 7) { Log.system.info('卖出信号出现后,股价下跌,立即卖出', price); const order = Object.assign({}, this.trader.order, { side: types.OrderSide.Sell, @@ -235,8 +249,17 @@ export class ExpertAdvisor { await this.manager.trader.set(this.account, order); // 消除信号 await this.manager.signal.remove(singal.id); - // 更新余额 - await this.updBalance(); + // 更新资产 + await this.updAsset(); + } else if (singal.price < price) { // 股价继续上涨 + Log.system.info('更新卖出信号股价', price); + singal.price = price; + if (this.backtest.test) { + singal.backtest = '1'; + singal.mocktime = time; + } + // 记录当前股价 + await this.manager.signal.set(singal); } } Log.system.info('处理信号[终了]'); @@ -245,30 +268,34 @@ export class ExpertAdvisor { // 拉取信号 async pullSingal(symbol: string, hisData: types.Bar[]) { Log.system.info('拉取信号[启动]'); - // 订单价格 - const orderPrice = numeral(hisData[hisData.length - 1].close).value() * 100 + 500; - Log.system.info(`订单价格:${JSON.stringify(orderPrice)}`); - if (this.account.balance < orderPrice) { - const balance = numeral(this.account.balance).format('0,0'); - Log.system.warn(`可用余额:${balance} < 订单价格(${symbol}):${numeral(orderPrice).format('0,0')},不拉取信号!`); - return; - } // 没有信号时,执行策略取得信号 const singal: SniperSingal | null = SniperStrategy.execute(symbol, hisData); // 获得买卖信号 if (singal && singal.side) { Log.system.info(`获得买卖信号:${JSON.stringify(singal)}`); - if (singal.side === types.OrderSide.Sell) { - // 查询是否有持仓 - const position = await this.manager.position.get({ - symbol, account_id: this.account.id, - side: types.OrderSide.Buy - }); - if (!position) { - Log.system.warn('未查询到持仓,不保存卖出信号!'); + if (singal.side === types.OrderSide.Buy) { + // 订单价格 + const orderPrice = numeral(hisData[hisData.length - 1].close).value() * 100 + 500; + Log.system.info(`订单价格:${JSON.stringify(orderPrice)}`); + if (this.account.balance < orderPrice) { + const balance = numeral(this.account.balance).format('0,0'); + Log.system.warn(`可用余额:${balance} < 订单价格(${symbol}):${numeral(orderPrice).format('0,0')},不拉取信号!`); return; } + } else if (singal.side === types.OrderSide.Sell) { + + // 查询是否有持仓 + if (this.account.positions) { + const position = this.account.positions.find((posi) => { + return posi.symbol === String(symbol) && posi.side === types.OrderSide.Buy; + }); + + if (!position) { + Log.system.warn('未查询到持仓,不保存卖出信号!'); + return; + } + } } const price = hisData[hisData.length - 1].close; const modelSingal = Object.assign({