UNPKG

turingtrader.js

Version:
419 lines (335 loc) 15.4 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createSimulator = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _tradingCalendarUs = require("./trading-calendar-us"); var _data2 = require("../data"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } var createSimulator = function createSimulator(algo) { var _algo$calendar, _internalInterface; var data = { tradingCalendar: (_algo$calendar = algo.calendar) !== null && _algo$calendar !== void 0 ? _algo$calendar : (0, _tradingCalendarUs.createTradingCalendarUS)(), cache: {} }; var state = { tradingDays: null, tradingDayIndex: null, cash: 0.0, positions: {} }; //========== internal interface: methods available to algorithms var internalInterface = (_internalInterface = { //----- properties get startDate() { return data.tradingCalendar.startDate; }, set startDate(d) { data.tradingCalendar.startDate = d; }, get endDate() { return data.tradingCalendar.endDate; }, set endDate(d) { data.tradingCalendar.endDate = d; }, get tradingCalendar() { return data.tradingCalendar; }, get cache() { return data.cache; }, orderTypes: { // NOTE: the values correspond to the // sequence of order execution mktThisClose: 0, mktNextOpen: 1, limNextBar: 2, stpNextBar: 3 }, get nav() {}, get result() { return getProperty("result"); }, //----- methods info: function info() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return console.log("INFO: ", args); }, asset: function asset(name) { return (0, _data2.loadAsset)(internalInterface, name, algo.data); } }, (0, _defineProperty2["default"])(_internalInterface, "cache", function cache(id, fn) { var _data$cache$id; return (_data$cache$id = data.cache[id]) !== null && _data$cache$id !== void 0 ? _data$cache$id : data.cache[id] = fn(); }), (0, _defineProperty2["default"])(_internalInterface, "loop", function () { var _loop = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(fn) { var result, i, isLastTradingDay, orders, oi, o, ticker, _loop2, ti; return _regenerator["default"].wrap(function _callee$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: state.tradingDays = internalInterface.tradingCalendar.tradingDays; state.nextTradingDay = internalInterface.tradingCalendar.nextTradingDay; result = { t: [], // we populate the next bar's open on this bar's close // therefore, we never get to populate the first bar's open o: [state.cash], // we can't produce meaningful high and low values. // therefore, we just skip them. c: [], // these are the asset allocations on open and close // limit and stop orders are reflected in the close oAlloc: [{ ticker: [], weight: [] }], cAlloc: [], // this flag indicates rebalancing dates. mktNextOpen // orders are reflected on the previous close because // that's when they've been submitted fAlloc: [] }; _context2.t0 = _regenerator["default"].keys(state.tradingDays); case 4: if ((_context2.t1 = _context2.t0()).done) { _context2.next = 31; break; } i = _context2.t1.value; // NOTE: this loop is processed strictly // in order to avoid any issues w/ the // simulator's state state.tradingDayIndex = i; isLastTradingDay = Number(i) === state.tradingDays.length - 1; _context2.next = 10; return fn(); case 10: orders = _context2.sent; // set flag to indicate rebalancing dates result.fAlloc.push((orders === null || orders === void 0 ? void 0 : orders.length) > 0 ? 1 : 0); // make sure we have a position for each // asset affected by the order basket _context2.t2 = _regenerator["default"].keys(orders); case 13: if ((_context2.t3 = _context2.t2()).done) { _context2.next = 22; break; } oi = _context2.t3.value; o = orders[oi]; _context2.next = 18; return o.ticker; case 18: ticker = _context2.sent; if (!(ticker in state.positions)) { state.positions[ticker] = { qty: 0.0, data: o.data }; } _context2.next = 13; break; case 22: _loop2 = /*#__PURE__*/_regenerator["default"].mark(function _loop2(ti) { var prices, _ticker, nav, _oi, _o, _ticker2, qtyCurrent, qtyNew, cashFlow, alloc, _ticker3, _alloc, _ticker4; return _regenerator["default"].wrap(function _loop2$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: // find the relevant asset prices prices = {}; _context.t0 = _regenerator["default"].keys(state.positions); case 2: if ((_context.t1 = _context.t0()).done) { _context.next = 16; break; } _ticker = _context.t1.value; if (!(internalInterface.orderTypes[ti] === internalInterface.orderTypes.mktThisClose)) { _context.next = 10; break; } _context.next = 7; return state.positions[_ticker].data.close.t(0); case 7: _context.t2 = _context.sent; _context.next = 13; break; case 10: _context.next = 12; return state.positions[_ticker].data.open.t(-1); case 12: _context.t2 = _context.sent; case 13: prices[_ticker] = _context.t2; _context.next = 2; break; case 16: // calculate NAV nav = Object.keys(state.positions).reduce(function (acc, ticker) { return acc + state.positions[ticker].qty * prices[ticker]; }, state.cash); // process orders _context.t3 = _regenerator["default"].keys(orders); case 18: if ((_context.t4 = _context.t3()).done) { _context.next = 33; break; } _oi = _context.t4.value; _o = _objectSpread({}, orders[_oi]); // on last bar, we execute mktNextOpen as mktThisClose if (isLastTradingDay === true && _o.type === internalInterface.orderTypes.mktNextOpen) { _o.type = internalInterface.orderTypes.mktThisClose; } // only execute orders of type we are currently processing if (!(_o.type !== internalInterface.orderTypes[ti])) { _context.next = 24; break; } return _context.abrupt("continue", 18); case 24: _context.next = 26; return _o.ticker; case 26: _ticker2 = _context.sent; qtyCurrent = state.positions[_ticker2].qty; qtyNew = _o.alloc * nav / prices[_ticker2]; cashFlow = (qtyNew - qtyCurrent) * prices[_ticker2]; // ignore orders smaller than 0.1% of NAV if (Math.abs(cashFlow / nav) > 1e-3) { // TODO: consider commissions here state.cash -= cashFlow; state.positions[_ticker2].qty = qtyNew; } _context.next = 18; break; case 33: // TODO: remove positions < 0.1% of NAV // save NAV & allocations if (internalInterface.orderTypes[ti] === internalInterface.orderTypes.mktThisClose) { result.t.push(internalInterface.t(0)); result.c.push(nav); alloc = { ticker: [], weight: [] }; for (_ticker3 in state.positions) { alloc.ticker.push(_ticker3); alloc.weight.push(state.positions[_ticker3].qty * prices[_ticker3] / nav); } result.cAlloc.push(alloc); } else if (isLastTradingDay !== true && internalInterface.orderTypes[ti] === internalInterface.orderTypes.mktNextOpen) { // simData.t added while processing mktThisClose result.o.push(nav); _alloc = { ticker: [], weight: [] }; for (_ticker4 in state.positions) { _alloc.ticker.push(_ticker4); _alloc.weight.push(state.positions[_ticker4].qty * prices[_ticker4] / nav); } result.oAlloc.push(_alloc); } //internalInterface.info(`${internalInterface.t(0)}: ${nav}`) case 34: case "end": return _context.stop(); } } }, _loop2); }); _context2.t4 = _regenerator["default"].keys(internalInterface.orderTypes); case 24: if ((_context2.t5 = _context2.t4()).done) { _context2.next = 29; break; } ti = _context2.t5.value; return _context2.delegateYield(_loop2(ti), "t6", 27); case 27: _context2.next = 24; break; case 29: _context2.next = 4; break; case 31: return _context2.abrupt("return", result); case 32: case "end": return _context2.stop(); } } }, _callee); })); function loop(_x) { return _loop.apply(this, arguments); } return loop; }()), (0, _defineProperty2["default"])(_internalInterface, "t", function t(offset) { // this is the raw index into the tradingDays array var rawIndex = state.tradingDayIndex - offset; // if the index is -1, we return the // next trading day *after* the sim range if (rawIndex === state.tradingDays.length) return state.nextTradingDay; var index = Math.min(state.tradingDays.length - 1, Math.max(0, rawIndex)); return state.tradingDays[index]; }), (0, _defineProperty2["default"])(_internalInterface, "deposit", function deposit(amount) { state.cash += amount; }), _internalInterface); //========== external interface: methods called on the simulator instance var externalInterface = { run: function () { var _run = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() { var result, key, ticker, _data; return _regenerator["default"].wrap(function _callee2$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _context3.next = 2; return algo.run(internalInterface); case 2: result = _context3.sent; _context3.t0 = _regenerator["default"].keys(algo.extra); case 4: if ((_context3.t1 = _context3.t0()).done) { _context3.next = 13; break; } key = _context3.t1.value; ticker = algo.extra[key]; _context3.next = 9; return (0, _data2.loadAsset)(internalInterface, ticker, algo.data).data; case 9: _data = _context3.sent; result[key] = { meta: { ticker: _data.meta.ticker }, c: _data.c }; _context3.next = 4; break; case 13: return _context3.abrupt("return", result); case 14: case "end": return _context3.stop(); } } }, _callee2); })); function run() { return _run.apply(this, arguments); } return run; }(), report: function report() { return algo.report(internalInterface); } }; return externalInterface; }; //============================================================================== // end of file exports.createSimulator = createSimulator;