diff --git a/src/admin/client.ts b/src/admin/client.ts index af48bf356..0a363715f 100644 --- a/src/admin/client.ts +++ b/src/admin/client.ts @@ -84,10 +84,10 @@ var uiCtrl = ($scope : MainWindowScope, $log : ng.ILogService, subscriberFactory : Shared.SubscriberFactory, fireFactory : Shared.FireFactory) => { - + var cancelAllFirer = fireFactory.getFire(Messaging.Topics.CancelAllOrders); $scope.cancelAllOrders = () => cancelAllFirer.fire(new Models.CancelAllOrdersRequest()); - + $scope.order = new DisplayOrder(fireFactory, $log); $scope.pair = null; @@ -130,7 +130,7 @@ var requires = ['ui.bootstrap', Trades.tradeListDirective, MarketQuoting.marketQuotingDirective, MarketTrades.marketTradeDirective, - Messages.messagesDirective, + Messages.messagesDirective, Position.positionDirective, Tbp.targetBasePositionDirective, TradeSafety.tradeSafetyDirective, diff --git a/src/admin/orderlist.ts b/src/admin/orderlist.ts index 4c51b5f03..5b3b0eba7 100644 --- a/src/admin/orderlist.ts +++ b/src/admin/orderlist.ts @@ -96,7 +96,7 @@ var OrderListController = ($scope: OrderListScope, rowHeight: 20, headerRowHeight: 20, columnDefs: [ - { width: 120, field: 'time', displayName: 'time', cellFilter: "momentFullDate", + { width: 120, field: 'time', displayName: 'time', cellFilter: "momentFullDate", sortingAlgorithm: (a: moment.Moment, b: moment.Moment) => a.diff(b), sort: { direction: uiGridConstants.DESC, priority: 1} }, { width: 90, field: 'orderId', displayName: 'id' }, diff --git a/src/admin/pair.ts b/src/admin/pair.ts index 156fd3bd4..b93bf8f3e 100644 --- a/src/admin/pair.ts +++ b/src/admin/pair.ts @@ -21,11 +21,11 @@ class FormViewModel { private _submitConverter: (disp: T) => T = null) { if (this._submitConverter === null) this._submitConverter = d => d; - + _sub.registerConnectHandler(() => this.connected = true) .registerDisconnectedHandler(() => this.connected = false) .registerSubscriber(this.update, us => us.forEach(this.update)); - + this.connected = _sub.connected; this.master = angular.copy(defaultParameter); this.display = angular.copy(defaultParameter); diff --git a/src/admin/shared_directives.ts b/src/admin/shared_directives.ts index 64779770b..4d81f9c96 100644 --- a/src/admin/shared_directives.ts +++ b/src/admin/shared_directives.ts @@ -84,7 +84,7 @@ export class EvalAsyncSubscriber implements Messaging.ISubscribe { }; public disconnect = () => this._wrapped.disconnect(); - + public get connected() { return this._wrapped.connected; } } diff --git a/src/admin/trades.ts b/src/admin/trades.ts index 646459f3d..6f012f483 100644 --- a/src/admin/trades.ts +++ b/src/admin/trades.ts @@ -31,7 +31,7 @@ class DisplayTrade { this.price = trade.price; this.quantity = trade.quantity; this.value = trade.value; - + if (trade.liquidity === 0 || trade.liquidity === 1) { this.liquidity = Models.Liquidity[trade.liquidity].charAt(0); } diff --git a/src/common/messaging.ts b/src/common/messaging.ts index 18ba95e00..e4de40363 100644 --- a/src/common/messaging.ts +++ b/src/common/messaging.ts @@ -16,7 +16,7 @@ export interface IPublish { export class Publisher implements IPublish { private _snapshot : () => T[] = null; - constructor(private topic : string, + constructor(private topic : string, private _io : SocketIO.Server, snapshot : () => T[], private _log : (...args: any[]) => void) { @@ -28,7 +28,7 @@ export class Publisher implements IPublish { s.on("disconnect", () => { this._log("socket", s.id, "disconnected for Publisher", topic); }); - + s.on(Prefixes.SUBSCRIBE + "-" + topic, () => { if (this._snapshot !== null) { var snapshot = this._snapshot(); @@ -39,7 +39,7 @@ export class Publisher implements IPublish { }; this._io.on("connection", onConnection); - + Object.keys(this._io.sockets.connected).forEach(s => { onConnection(this._io.sockets.connected[s]); }); @@ -79,22 +79,22 @@ export class Subscriber implements ISubscribe { private _connectHandler : () => void = null; private _socket : SocketIOClient.Socket; - constructor(private topic : string, + constructor(private topic : string, io : SocketIOClient.Socket, private _log : (...args: any[]) => void) { this._socket = io; - + this._log("creating subscriber to", this.topic, "; connected?", this.connected); - - if (this.connected) + + if (this.connected) this.onConnect(); - + this._socket.on("connect", this.onConnect) .on("disconnect", this.onDisconnect) .on(Prefixes.MESSAGE + "-" + topic, this.onIncremental) .on(Prefixes.SNAPSHOT + "-" + topic, this.onSnapshot); } - + public get connected() : boolean { return this._socket.connected; } @@ -214,7 +214,7 @@ export class Receiver implements IReceive { _log("error in Receiver", e.stack, e.message); }); }; - + io.on("connection", onConnection); Object.keys(io.sockets.connected).forEach(s => { onConnection(io.sockets.connected[s]); diff --git a/src/service/backtest.ts b/src/service/backtest.ts index 65123474d..202fc78a7 100644 --- a/src/service/backtest.ts +++ b/src/service/backtest.ts @@ -24,49 +24,49 @@ enum TimedType { class Timed { constructor( - public action : () => void, - public time : moment.Moment, + public action : () => void, + public time : moment.Moment, public type : TimedType, public interval : moment.Duration) {} } export class BacktestTimeProvider implements Utils.IBacktestingTimeProvider { constructor(private _internalTime : moment.Moment, private _endTime : moment.Moment) { } - + utcNow = () => this._internalTime; - + private _immediates = new Array<() => void>(); setImmediate = (action: () => void) => this._immediates.push(action); - + private _timeouts : Timed[] = []; setTimeout = (action: () => void, time: moment.Duration) => { this.setAction(action, time, TimedType.Timeout); }; - + setInterval = (action: () => void, time: moment.Duration) => { this.setAction(action, time, TimedType.Interval); }; - + private setAction = (action: () => void, time: moment.Duration, type : TimedType) => { var dueTime = this._internalTime.clone().add(time); - + if (Utils.fastDiff(dueTime, this.utcNow()) < 0) { return; } - + this._timeouts.push(new Timed(action, dueTime, type, time)); this._timeouts.sort((a, b) => Utils.fastDiff(a.time, b.time)); }; - + scrollTimeTo = (time : moment.Moment) => { if (Utils.fastDiff(time, this.utcNow()) < 0) { throw new Error("Cannot reverse time!"); } - + while (this._immediates.length > 0) { this._immediates.pop()(); } - + while (this._timeouts.length > 0 && Utils.fastDiff(_.first(this._timeouts).time, time) < 0) { var evt : Timed = this._timeouts.shift(); this._internalTime = evt.time; @@ -75,28 +75,28 @@ export class BacktestTimeProvider implements Utils.IBacktestingTimeProvider { this.setAction(evt.action, evt.interval, evt.type); } } - + this._internalTime = time; }; } export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces.IOrderEntryGateway, Interfaces.IMarketDataGateway { ConnectChanged = new Utils.Evt(); - + MarketData = new Utils.Evt(); MarketTrade = new Utils.Evt(); - + OrderUpdate = new Utils.Evt(); - + supportsCancelAllOpenOrders = () : boolean => { return false; }; cancelAllOpenOrders = () : Q.Promise => { return Q(0); }; - + generateClientOrderId = () => { return "BACKTEST-" + shortId.generate(); } public cancelsByClientOrderId = true; - + private _openBidOrders : {[orderId: string]: Models.BrokeredOrder} = {}; private _openAskOrders : {[orderId: string]: Models.BrokeredOrder} = {}; @@ -112,10 +112,10 @@ export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces. this._baseHeld += order.quantity; this._baseAmount -= order.quantity; } - + this.OrderUpdate.trigger({ orderId: order.orderId, orderStatus: Models.OrderStatus.Working }); }, moment.duration(3)); - + return new Models.OrderGatewayActionReport(this.timeProvider.utcNow()); }; @@ -141,10 +141,10 @@ export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces. this._baseAmount += existing.quantity; delete this._openAskOrders[cancel.clientOrderId]; } - + this.OrderUpdate.trigger({ orderId: cancel.clientOrderId, orderStatus: Models.OrderStatus.Cancelled }); }, moment.duration(3)); - + return new Models.OrderGatewayActionReport(this.timeProvider.utcNow()); }; @@ -152,26 +152,26 @@ export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces. this.cancelOrder(new Models.BrokeredCancel(replace.origOrderId, replace.orderId, replace.side, replace.exchangeId)); return this.sendOrder(replace); }; - + private onMarketData = (market : Models.Market) => { this._openAskOrders = this.tryToMatch(_.values(this._openAskOrders), market.bids, Models.Side.Ask); this._openBidOrders = this.tryToMatch(_.values(this._openBidOrders), market.asks, Models.Side.Bid); - + this.MarketData.trigger(market); }; - + private tryToMatch = (orders: Models.BrokeredOrder[], marketSides: Models.MarketSide[], side: Models.Side) => { - if (orders.length === 0 || marketSides.length === 0) + if (orders.length === 0 || marketSides.length === 0) return _.indexBy(orders, k => k.orderId); - + var cmp = side === Models.Side.Ask ? (m, o) => o < m : (m, o) => o > m; _.forEach(orders, order => { _.forEach(marketSides, mkt => { if ((cmp(mkt.price, order.price) || order.type === Models.OrderType.Market) && order.quantity > 0) { - + var px = order.price; if (order.type === Models.OrderType.Market) px = mkt.price; - + var update : Models.OrderStatusReport = { orderId: order.orderId, lastPrice: px }; if (mkt.size >= order.quantity) { update.orderStatus = Models.OrderStatus.Complete; @@ -183,7 +183,7 @@ export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces. update.lastQuantity = mkt.size; } this.OrderUpdate.trigger(update); - + if (side === Models.Side.Bid) { this._baseAmount += update.lastQuantity; this._quoteHeld -= (update.lastQuantity*px); @@ -192,66 +192,66 @@ export class BacktestGateway implements Interfaces.IPositionGateway, Interfaces. this._baseHeld -= update.lastQuantity; this._quoteAmount += (update.lastQuantity*px); } - + order.quantity = order.quantity - update.lastQuantity; }; }); }); - + var liveOrders = _.filter(orders, o => o.quantity > 0); - + if (liveOrders.length > 5) console.warn("more than 5 outstanding " + Models.Side[side] + " orders open"); - + return _.indexBy(liveOrders, k => k.orderId); }; - + private onMarketTrade = (trade : Models.MarketTrade) => { this._openAskOrders = this.tryToMatch(_.values(this._openAskOrders), [trade], Models.Side.Ask); this._openBidOrders = this.tryToMatch(_.values(this._openBidOrders), [trade], Models.Side.Bid); - + this.MarketTrade.trigger(new Models.GatewayMarketTrade(trade.price, trade.size, trade.time, false, trade.make_side)); }; - + PositionUpdate = new Utils.Evt(); private recomputePosition = () => { this.PositionUpdate.trigger(new Models.CurrencyPosition(this._baseAmount, this._baseHeld, Models.Currency.BTC)); this.PositionUpdate.trigger(new Models.CurrencyPosition(this._quoteAmount, this._quoteHeld, Models.Currency.USD)); }; - + private _baseHeld = 0; private _quoteHeld = 0; - + constructor( private _inputData: Array, private _baseAmount : number, private _quoteAmount : number, private timeProvider: Utils.IBacktestingTimeProvider) {} - + public run = () => { this.ConnectChanged.trigger(Models.ConnectivityStatus.Connected); - + var hasProcessedMktData = false; - + this.timeProvider.setInterval(() => this.recomputePosition(), moment.duration(15, "seconds")); - + _(this._inputData).forEach(i => { this.timeProvider.scrollTimeTo(i.time); - + if (typeof i["make_side"] !== "undefined") { this.onMarketTrade(i); } else if (typeof i["bids"] !== "undefined" || typeof i["asks"] !== "undefined") { this.onMarketData(i); - + if (!hasProcessedMktData) { this.recomputePosition(); hasProcessedMktData = true; } } }); - - this.recomputePosition(); + + this.recomputePosition(); }; } @@ -275,7 +275,7 @@ class BacktestGatewayDetails implements Interfaces.IExchangeDetailsGateway { exchange(): Models.Exchange { return Models.Exchange.Null; } - + private static AllPairs = [ new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.USD) ]; @@ -293,10 +293,10 @@ export class BacktestParameters { export class BacktestPersister implements Persister.ILoadAll, Persister.ILoadLatest { public load = (exchange: Models.Exchange, pair: Models.CurrencyPair, limit?: number): Q.Promise => { - return this.loadAll(limit); + return this.loadAll(limit); }; - - public loadAll = (limit?: number): Q.Promise => { + + public loadAll = (limit?: number): Q.Promise => { if (this.initialData) { if (limit) { return Q(_.takeRight(this.initialData, limit)); @@ -307,14 +307,14 @@ export class BacktestPersister implements Persister.ILoadAll, Persister.IL } return Q([]); }; - + public persist = (report: T) => { }; - + public loadLatest = (): Q.Promise => { if (this.initialData) return Q(_.last(this.initialData)); }; - + constructor(private initialData?: T[]) { this.initialData = initialData || null; } @@ -324,7 +324,7 @@ export class BacktestExchange extends Interfaces.CombinedGateway { constructor(private gw: BacktestGateway) { super(gw, gw, gw, new BacktestGatewayDetails()); } - + public run = () => this.gw.run(); }; @@ -337,16 +337,16 @@ var backtestServer = () => { ["uncaughtException", "exit", "SIGINT", "SIGTERM"].forEach(reason => { process.on(reason, (e?) => { console.log(util.format("Terminating!", reason, e, (typeof e !== "undefined" ? e.stack : undefined))); - + process.exit(1); }); }); - + var mdFile = process.env['MD_FILE']; var paramFile = process.env['PARAM_FILE']; var savedProgressFile = process.env["PROGRESS_FILE"] || "nextParameters_saved.txt"; var backtestResultFile = process.env["RESULT_FILE"] || 'backtestResults.txt'; - + var rawParams = fs.readFileSync(paramFile, 'utf8'); var parameters : BacktestParameters[] = JSON.parse(rawParams); if (fs.existsSync(savedProgressFile)) { @@ -356,20 +356,20 @@ var backtestServer = () => { else if (fs.existsSync(backtestResultFile)) { fs.unlinkSync(backtestResultFile); } - + console.log("loaded input data..."); - + var app = express(); app.use(require('body-parser').json({limit: '200mb'})); app.use(require("compression")()); - + var server = app.listen(5001, () => { var host = server.address().address; var port = server.address().port; - + console.log('Backtest server listening at http://%s:%s', host, port); }); - + app.get("/inputData", (req, res) => { console.log("Starting inputData download for", req.ip); res.sendFile(mdFile, (err) => { @@ -377,18 +377,18 @@ var backtestServer = () => { else console.log("Ending inputData download for", req.ip); }); }); - + app.get("/nextParameters", (req, res) => { if (_.some(parameters)) { var id = parameters.length; var served = parameters.shift(); - if (typeof served["id"] === "undefined") + if (typeof served["id"] === "undefined") served.id = id.toString(); - + console.log("Serving parameters id =", served.id, " to", req.ip); res.json(served); fs.writeFileSync(savedProgressFile, parameters.length, {encoding: 'utf8'}); - + if (!_.some(parameters)) { console.log("Done serving parameters"); } @@ -399,10 +399,10 @@ var backtestServer = () => { fs.unlinkSync(savedProgressFile); } }); - + app.post("/result", (req, res) => { var params = req.body; - console.log("Accept backtest results, volume =", params[2].volume.toFixed(2), "val =", + console.log("Accept backtest results, volume =", params[2].volume.toFixed(2), "val =", params[1].value.toFixed(2), "qVal =", params[1].quoteValue.toFixed(2)); fs.appendFileSync(backtestResultFile, JSON.stringify(params)+"\n"); }); diff --git a/src/service/broker.ts b/src/service/broker.ts index ac13b3c62..fd2ea9cc3 100644 --- a/src/service/broker.ts +++ b/src/service/broker.ts @@ -56,7 +56,7 @@ export class OrderBroker implements Interfaces.IOrderBroker { if (this._oeGateway.supportsCancelAllOpenOrders()) { return this._oeGateway.cancelAllOpenOrders(); } - + var deferred = Q.defer(); var lateCancels : {[id: string] : boolean} = {}; @@ -100,7 +100,7 @@ export class OrderBroker implements Interfaces.IOrderBroker { sendOrder = (order : Models.SubmitNewOrder) : Models.SentOrder => { var orderId = this._oeGateway.generateClientOrderId(); var exch = this._baseBroker.exchange(); - var brokeredOrder = new Models.BrokeredOrder(orderId, order.side, order.quantity, order.type, + var brokeredOrder = new Models.BrokeredOrder(orderId, order.side, order.quantity, order.type, order.price, order.timeInForce, exch, order.preferPostOnly); var sent = this._oeGateway.sendOrder(brokeredOrder); @@ -126,7 +126,7 @@ export class OrderBroker implements Interfaces.IOrderBroker { replaceOrder = (replace : Models.CancelReplaceOrder) : Models.SentOrder => { var rpt = _.last(this._orderCache.allOrders[replace.origOrderId]); - var br = new Models.BrokeredReplace(replace.origOrderId, replace.origOrderId, rpt.side, replace.quantity, + var br = new Models.BrokeredReplace(replace.origOrderId, replace.origOrderId, rpt.side, replace.quantity, rpt.type, replace.price, rpt.timeInForce, rpt.exchange, rpt.exchangeId, rpt.preferPostOnly); var sent = this._oeGateway.replaceOrder(br); @@ -265,7 +265,7 @@ export class OrderBroker implements Interfaces.IOrderBroker { value = value * (1 + sign * feeCharged); } - const trade = new Models.Trade(o.orderId+"."+o.version, o.time, o.exchange, o.pair, + const trade = new Models.Trade(o.orderId+"."+o.version, o.time, o.exchange, o.pair, o.lastPrice, o.lastQuantity, o.side, value, o.liquidity, feeCharged); this.Trade.trigger(trade); this._tradePublisher.publish(trade); @@ -313,20 +313,20 @@ export class OrderBroker implements Interfaces.IOrderBroker { this._log.error(e, "unhandled exception while submitting order", o); } }); - + _cancelOrderReciever.registerReceiver(o => { this._log.info("got new cancel req", o); try { - this.cancelOrder(new Models.OrderCancel(o.orderId, o.exchange, _timeProvider.utcNow())); + this.cancelOrder(new Models.OrderCancel(o.orderId, o.exchange, _timeProvider.utcNow())); } catch (e) { this._log.error(e, "unhandled exception while submitting order", o); } }); - + _cancelAllOrdersReciever.registerReceiver(o => { this._log.info("handling cancel all orders request"); this.cancelOpenOrders() - .then(x => this._log.info("cancelled all ", x, " open orders"), + .then(x => this._log.info("cancelled all ", x, " open orders"), e => this._log.error(e, "error when cancelling all orders!")); }); @@ -379,8 +379,8 @@ export class PositionBroker implements Interfaces.IPositionBroker { var positionReport = new Models.PositionReport(baseAmount, quoteAmount, basePosition.heldAmount, quotePosition.heldAmount, baseValue, quoteValue, this._base.pair, this._base.exchange(), this._timeProvider.utcNow()); - if (this._report !== null && - Math.abs(positionReport.value - this._report.value) < 2e-2 && + if (this._report !== null && + Math.abs(positionReport.value - this._report.value) < 2e-2 && Math.abs(baseAmount - this._report.baseAmount) < 2e-2 && Math.abs(positionReport.baseHeldAmount - this._report.baseHeldAmount) < 2e-2 && Math.abs(positionReport.quoteHeldAmount - this._report.quoteHeldAmount) < 2e-2) @@ -426,7 +426,7 @@ export class ExchangeBroker implements Interfaces.IBroker { public get pair() { return this._pair; } - + public get supportedCurrencyPairs() : Models.CurrencyPair[] { return this._baseGateway.supportedCurrencyPairs; } diff --git a/src/service/config.ts b/src/service/config.ts index b3a61e2d2..28793bfc6 100644 --- a/src/service/config.ts +++ b/src/service/config.ts @@ -7,7 +7,7 @@ import fs = require("fs"); export interface IConfigProvider { GetString(configKey: string): string; GetNumber(configKey: string): number; - + inBacktestMode: boolean; } @@ -17,7 +17,7 @@ export class ConfigProvider implements IConfigProvider { constructor() { this.inBacktestMode = (process.env["TRIBECA_BACKTEST_MODE"] || "false") === "true"; - + var configFile = process.env["TRIBECA_CONFIG_FILE"] || "tribeca.json"; if (fs.existsSync(configFile)) { this._config = JSON.parse(fs.readFileSync(configFile, "utf-8")); @@ -33,16 +33,16 @@ export class ConfigProvider implements IConfigProvider { ConfigProvider.Log.info("%s = %s", configKey, value); return value; }; - + private Fetch = (configKey: string): string => { if (process.env.hasOwnProperty(configKey)) return process.env[configKey]; - + if (this._config.hasOwnProperty(configKey)) return this._config[configKey]; throw Error("Config does not have property " + configKey); }; - + inBacktestMode: boolean = false; } \ No newline at end of file diff --git a/src/service/gateways/bitfinex.ts b/src/service/gateways/bitfinex.ts index a99f25329..10fbbbcc1 100644 --- a/src/service/gateways/bitfinex.ts +++ b/src/service/gateways/bitfinex.ts @@ -330,7 +330,7 @@ class BitfinexOrderEntryGateway implements Interfaces.IOrderEntryGateway { class RateLimitMonitor { private _log = Utils.log("tribeca:gateway:rlm"); - + private _queue = Deque(); private _durationMs: number; @@ -369,7 +369,7 @@ class BitfinexHttp { return this.doRequest(opts, url); }; - + // Bitfinex seems to have a race condition where nonces are processed out of order when rapidly placing orders // Retry here - look to mitigate in the future by batching orders? post = (actionUrl: string, msg: TRequest): Q.Promise> => { diff --git a/src/service/gateways/coinbase-api.ts b/src/service/gateways/coinbase-api.ts index f2773d4d1..96c7f6a43 100644 --- a/src/service/gateways/coinbase-api.ts +++ b/src/service/gateways/coinbase-api.ts @@ -217,7 +217,7 @@ _.assign(AuthenticatedClient.prototype, new function() { var self = this; return prototype.delete.call(self, ['orders', orderID], callback); }; - + prototype.cancelAllOrders = function(callback) { var self = this; return prototype.delete.call(self, ['orders'], callback); diff --git a/src/service/gateways/coinbase.ts b/src/service/gateways/coinbase.ts index b979d52ab..e5c2ce2e9 100644 --- a/src/service/gateways/coinbase.ts +++ b/src/service/gateways/coinbase.ts @@ -475,7 +475,7 @@ class CoinbaseOrderEntryGateway implements Interfaces.IOrderEntryGateway { leavesQuantity: 0 }); } - + d.resolve(resp.length); }; }); @@ -580,23 +580,23 @@ class CoinbaseOrderEntryGateway implements Interfaces.IOrderEntryGateway { size: order.quantity.toString(), product_id: this._symbolProvider.symbol }; - + if (order.type === Models.OrderType.Limit) { o.price = order.price.toString(); - + if (order.preferPostOnly) o.post_only = true; - + switch (order.timeInForce) { - case Models.TimeInForce.GTC: + case Models.TimeInForce.GTC: break; - case Models.TimeInForce.FOK: + case Models.TimeInForce.FOK: o.time_in_force = "FOK"; break; - case Models.TimeInForce.IOC: + case Models.TimeInForce.IOC: o.time_in_force = "IOC"; break; - default: + default: throw new Error("Cannot map " + Models.TimeInForce[order.timeInForce] + " to a coinbase TIF"); } } diff --git a/src/service/gateways/hitbtc.ts b/src/service/gateways/hitbtc.ts index 56cfa9786..a7a7f5ee9 100644 --- a/src/service/gateways/hitbtc.ts +++ b/src/service/gateways/hitbtc.ts @@ -211,7 +211,7 @@ class HitBtcMarketDataGateway implements Interfaces.IMarketDataGateway { this.ConnectChanged.trigger(Models.ConnectivityStatus.Disconnected); } }; - + private onTrade = (t: MarketTrade) => { var side : Models.Side = Models.Side.Unknown; if (this._lastAsks.any() && this._lastBids.any()) { @@ -220,7 +220,7 @@ class HitBtcMarketDataGateway implements Interfaces.IMarketDataGateway { if (distance_from_bid < distance_from_ask) side = Models.Side.Bid; if (distance_from_bid > distance_from_ask) side = Models.Side.Ask; } - + this.MarketTrade.trigger(new Models.GatewayMarketTrade(t.price, t.amount, Utils.date(), false, side)); }; @@ -272,7 +272,7 @@ class HitBtcOrderEntryGateway implements Interfaces.IOrderEntryGateway { _orderEntryWs : WebSocket; public cancelsByClientOrderId = true; - + supportsCancelAllOpenOrders = () : boolean => { return false; }; cancelAllOpenOrders = () : Q.Promise => { return Q(0); }; @@ -567,11 +567,11 @@ class HitBtcBaseGateway implements Interfaces.IExchangeDetailsGateway { name() : string { return "HitBtc"; } - + private static AllPairs = [ new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.USD), new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.EUR), - + // don't use these yet. //new Models.CurrencyPair(Models.Currency.LTC, Models.Currency.BTC), //new Models.CurrencyPair(Models.Currency.LTC, Models.Currency.USD), @@ -608,7 +608,7 @@ function GetCurrencySymbol(c: Models.Currency) : string { class HitBtcSymbolProvider { public symbol : string; - + constructor(pair: Models.CurrencyPair) { this.symbol = GetCurrencySymbol(pair.base) + GetCurrencySymbol(pair.quote); } diff --git a/src/service/gateways/nullgw.ts b/src/service/gateways/nullgw.ts index db04fab5c..2e02c48dc 100644 --- a/src/service/gateways/nullgw.ts +++ b/src/service/gateways/nullgw.ts @@ -10,7 +10,7 @@ var uuid = require('node-uuid'); export class NullOrderGateway implements Interfaces.IOrderEntryGateway { OrderUpdate = new Utils.Evt(); ConnectChanged = new Utils.Evt(); - + supportsCancelAllOpenOrders = () : boolean => { return false; }; cancelAllOpenOrders = () : Q.Promise => { return Q(0); }; @@ -123,7 +123,7 @@ class NullGatewayDetails implements Interfaces.IExchangeDetailsGateway { exchange(): Models.Exchange { return Models.Exchange.Null; } - + private static AllPairs = [ new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.USD), new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.EUR), diff --git a/src/service/gateways/okcoin.ts b/src/service/gateways/okcoin.ts index 311043d7e..cb73fb500 100644 --- a/src/service/gateways/okcoin.ts +++ b/src/service/gateways/okcoin.ts @@ -76,13 +76,13 @@ interface SubscriptionRequest extends SignedMessage { } class OkCoinWebsocket { send = (channel : string, parameters: any) => { var subsReq : any = {event: 'addChannel', channel: channel}; - - if (parameters !== null) + + if (parameters !== null) subsReq.parameters = parameters; - + this._ws.send(JSON.stringify(subsReq)); } - + setHandler = (channel : string, handler: (newMsg : Models.Timestamped) => void) => { this._handlers[channel] = handler; } @@ -150,10 +150,10 @@ class OkCoinMarketDataGateway implements Interfaces.IMarketDataGateway { }; MarketData = new Utils.Evt(); - - private static GetLevel = (n: [number, number]) : Models.MarketSide => + + private static GetLevel = (n: [number, number]) : Models.MarketSide => new Models.MarketSide(n[0], n[1]); - + private onDepth = (depth : Models.Timestamped) => { var msg = depth.data; @@ -168,13 +168,13 @@ class OkCoinMarketDataGateway implements Interfaces.IMarketDataGateway { constructor(socket : OkCoinWebsocket, symbolProvider: OkCoinSymbolProvider) { var depthChannel = "ok_" + symbolProvider.symbolWithoutUnderscore + "_depth"; var tradesChannel = "ok_" + symbolProvider.symbolWithoutUnderscore + "_trades_v1"; - + socket.setHandler(depthChannel, this.onDepth); socket.setHandler(tradesChannel, this.onTrade); - + socket.ConnectChanged.on(cs => { this.ConnectChanged.trigger(cs); - + if (cs == Models.ConnectivityStatus.Connected) { socket.send(depthChannel, {}); socket.send(tradesChannel, {}); @@ -188,12 +188,12 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { ConnectChanged = new Utils.Evt(); generateClientOrderId = () => shortId.generate(); - + supportsCancelAllOpenOrders = () : boolean => { return false; }; cancelAllOpenOrders = () : Q.Promise => { return Q(0); }; public cancelsByClientOrderId = false; - + private static GetOrderType(side: Models.Side, type: Models.OrderType) : string { if (side === Models.Side.Bid) { if (type === Models.OrderType.Limit) return "buy"; @@ -205,7 +205,7 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { } throw new Error("unable to convert " + Models.Side[side] + " and " + Models.OrderType[type]); } - + // let's really hope there's no race conditions on their end -- we're assuming here that orders sent first // will be acked first, so we can match up orders and their acks private _ordersWaitingForAckQueue = []; @@ -216,30 +216,30 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { type: OkCoinOrderEntryGateway.GetOrderType(order.side, order.type), price: order.price.toString(), amount: order.quantity.toString()}; - + this._ordersWaitingForAckQueue.push(order.orderId); - + this._socket.send("ok_spotusd_trade", this._signer.signMessage(o)); return new Models.OrderGatewayActionReport(Utils.date()); }; - + private onOrderAck = (ts: Models.Timestamped) => { var orderId = this._ordersWaitingForAckQueue.shift(); if (typeof orderId === "undefined") { this._log.error("got an order ack when there was no order queued!", util.format(ts.data)); return; } - + var osr : Models.OrderStatusReport = { orderId: orderId, time: ts.time }; - + if (ts.data.result === "true") { osr.exchangeId = ts.data.order_id.toString(); osr.orderStatus = Models.OrderStatus.Working; - } + } else { osr.orderStatus = Models.OrderStatus.Rejected; } - + this.OrderUpdate.trigger(osr); }; @@ -248,10 +248,10 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { this._socket.send("ok_spotusd_cancel_order", this._signer.signMessage(c)); return new Models.OrderGatewayActionReport(Utils.date()); }; - + private onCancel = (ts: Models.Timestamped) => { var osr : Models.OrderStatusReport = { exchangeId: ts.data.order_id.toString(), time: ts.time }; - + if (ts.data.result === "true") { osr.orderStatus = Models.OrderStatus.Cancelled; } @@ -259,7 +259,7 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { osr.orderStatus = Models.OrderStatus.Rejected; osr.cancelRejected = true; } - + this.OrderUpdate.trigger(osr); }; @@ -267,7 +267,7 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { this.cancelOrder(new Models.BrokeredCancel(replace.origOrderId, replace.orderId, replace.side, replace.exchangeId)); return this.sendOrder(replace); }; - + private static getStatus(status: number) : Models.OrderStatus { // status: -1: cancelled, 0: pending, 1: partially filled, 2: fully filled, 4: cancel request in process switch (status) { @@ -283,7 +283,7 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { private onTrade = (tsMsg : Models.Timestamped) => { var t = tsMsg.time; var msg : OkCoinTradeRecord = tsMsg.data; - + var avgPx = parseFloat(msg.averagePrice); var lastQty = parseFloat(msg.sigTradeAmount); var lastPx = parseFloat(msg.sigTradePrice); @@ -304,16 +304,16 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { private _log = Utils.log("tribeca:gateway:OkCoinOE"); constructor( - private _socket : OkCoinWebsocket, + private _socket : OkCoinWebsocket, private _signer: OkCoinMessageSigner, private _symbolProvider: OkCoinSymbolProvider) { _socket.setHandler("ok_usd_realtrades", this.onTrade); _socket.setHandler("ok_spotusd_trade", this.onOrderAck); _socket.setHandler("ok_spotusd_cancel_order", this.onCancel); - + _socket.ConnectChanged.on(cs => { this.ConnectChanged.trigger(cs); - + if (cs === Models.ConnectivityStatus.Connected) { _socket.send("ok_usd_realtrades", _signer.signMessage({})); } @@ -324,10 +324,10 @@ class OkCoinOrderEntryGateway implements Interfaces.IOrderEntryGateway { class OkCoinMessageSigner { private _secretKey : string; private _api_key : string; - + public signMessage = (m : SignedMessage) : SignedMessage => { var els : string[] = []; - + if (!m.hasOwnProperty("api_key")) m.api_key = this._api_key; @@ -348,7 +348,7 @@ class OkCoinMessageSigner { m.sign = crypto.createHash('md5').update(sig).digest("hex").toString().toUpperCase(); return m; }; - + constructor(config : Config.IConfigProvider) { this._api_key = config.GetString("OkCoinApiKey"); this._secretKey = config.GetString("OkCoinSecretKey"); @@ -378,7 +378,7 @@ class OkCoinHttp { } } }); - + return d.promise; }; @@ -444,7 +444,7 @@ class OkCoinBaseGateway implements Interfaces.IExchangeDetailsGateway { exchange() : Models.Exchange { return Models.Exchange.OkCoin; } - + private static AllPairs = [ new Models.CurrencyPair(Models.Currency.BTC, Models.Currency.USD), //new Models.CurrencyPair(Models.Currency.LTC, Models.Currency.USD), @@ -475,7 +475,7 @@ function GetCurrencySymbol(c: Models.Currency) : string { class OkCoinSymbolProvider { public symbol : string; public symbolWithoutUnderscore: string; - + constructor(pair: Models.CurrencyPair) { this.symbol = GetCurrencySymbol(pair.base) + "_" + GetCurrencySymbol(pair.quote); this.symbolWithoutUnderscore = GetCurrencySymbol(pair.base) + GetCurrencySymbol(pair.quote); diff --git a/src/service/interfaces.ts b/src/service/interfaces.ts index 4ee92b8e0..c19d77bf1 100644 --- a/src/service/interfaces.ts +++ b/src/service/interfaces.ts @@ -35,12 +35,12 @@ export interface IOrderEntryGateway extends IGateway { sendOrder(order: Models.BrokeredOrder): Models.OrderGatewayActionReport; cancelOrder(cancel: Models.BrokeredCancel): Models.OrderGatewayActionReport; replaceOrder(replace: Models.BrokeredReplace): Models.OrderGatewayActionReport; - + OrderUpdate: Utils.Evt; - + cancelsByClientOrderId: boolean; generateClientOrderId(): string; - + supportsCancelAllOpenOrders() : boolean; cancelAllOpenOrders() : q.Promise; } diff --git a/src/service/main.ts b/src/service/main.ts index d74d0997a..01174490f 100644 --- a/src/service/main.ts +++ b/src/service/main.ts @@ -106,34 +106,34 @@ var messagingLog = Utils.log("tribeca:messaging"); function ParseCurrencyPair(raw: string) : Models.CurrencyPair { var split = raw.split("/"); - if (split.length !== 2) + if (split.length !== 2) throw new Error("Invalid currency pair! Must be in the format of BASE/QUOTE, eg BTC/USD"); - + return new Models.CurrencyPair(Models.Currency[split[0]], Models.Currency[split[1]]); } var pair = ParseCurrencyPair(config.GetString("TradedPair")); var defaultActive : Models.SerializedQuotesActive = new Models.SerializedQuotesActive(false, moment.unix(1)); -var defaultQuotingParameters : Models.QuotingParameters = new Models.QuotingParameters(.3, .05, Models.QuotingMode.Top, +var defaultQuotingParameters : Models.QuotingParameters = new Models.QuotingParameters(.3, .05, Models.QuotingMode.Top, Models.FairValueModel.BBO, 3, .8, false, Models.AutoPositionMode.Off, false, 2.5, 300, .095, 2*.095, .095, 3, .1); var backTestSimulationSetup = (inputData : Array, parameters : Backtest.BacktestParameters) => { var timeProvider : Utils.ITimeProvider = new Backtest.BacktestTimeProvider(_.first(inputData).time, _.last(inputData).time); var exchange = Models.Exchange.Null; var gw = new Backtest.BacktestGateway(inputData, parameters.startingBasePosition, parameters.startingQuotePosition, timeProvider); - + var getExch = (orderCache: Broker.OrderStateCache): Interfaces.CombinedGateway => new Backtest.BacktestExchange(gw); - - var getPublisher = (topic: string, persister?: Persister.ILoadAll): Messaging.IPublish => { + + var getPublisher = (topic: string, persister?: Persister.ILoadAll): Messaging.IPublish => { return new Messaging.NullPublisher(); }; - + var getReceiver = (topic: string) : Messaging.IReceive => new Messaging.NullReceiver(); - + var getPersister = (collectionName: string) : Persister.ILoadAll => new Backtest.BacktestPersister(); - + var getRepository = (defValue: T, collectionName: string) : Persister.ILoadLatest => new Backtest.BacktestPersister([defValue]); - + var startingActive : Models.SerializedQuotesActive = new Models.SerializedQuotesActive(true, timeProvider.utcNow()); var startingParameters : Models.QuotingParameters = parameters.quotingParameters; @@ -152,7 +152,7 @@ var backTestSimulationSetup = (inputData : Array { var timeProvider : Utils.ITimeProvider = new Utils.RealTimeProvider(); - + var app = express(); var http_server = http.createServer(app); var io = socket_io(http_server); @@ -167,10 +167,10 @@ var liveTradingSetup = () => { app.use(compression()); app.use(express.static(path.join(__dirname, "admin"))); - + var webport = config.GetNumber("WebClientListenPort"); http_server.listen(webport, () => mainLog.info('Listening to admins on *:', webport)); - + var getExchange = (): Models.Exchange => { var ex = config.GetString("EXCHANGE").toLowerCase(); switch (ex) { @@ -182,9 +182,9 @@ var liveTradingSetup = () => { default: throw new Error("unknown configuration env variable EXCHANGE " + ex); } }; - + var exchange = getExchange(); - + var getExch = (orderCache: Broker.OrderStateCache): Interfaces.CombinedGateway => { switch (exchange) { case Models.Exchange.HitBtc: return (new HitBtc.HitBtc(config, pair)); @@ -195,7 +195,7 @@ var liveTradingSetup = () => { default: throw new Error("no gateway provided for exchange " + exchange); } }; - + var getPublisher = (topic: string, persister?: Persister.ILoadAll): Messaging.IPublish => { var socketIoPublisher = new Messaging.Publisher(topic, io, null, messagingLog.info.bind(messagingLog)); if (persister) @@ -203,21 +203,21 @@ var liveTradingSetup = () => { else return socketIoPublisher; }; - - var getReceiver = (topic: string) : Messaging.IReceive => + + var getReceiver = (topic: string) : Messaging.IReceive => new Messaging.Receiver(topic, io, messagingLog.info.bind(messagingLog)); - + var db = Persister.loadDb(config); - + var loaderSaver = new Persister.LoaderSaver(exchange, pair); var mtLoaderSaver = new MarketTrades.MarketTradesLoaderSaver(loaderSaver); - + var getPersister = (collectionName: string) : Persister.ILoadAll => { var ls = collectionName === "mt" ? mtLoaderSaver : loaderSaver; return new Persister.Persister(db, collectionName, exchange, pair, ls.loader, ls.saver); }; - - var getRepository = (defValue: T, collectionName: string) : Persister.ILoadLatest => + + var getRepository = (defValue: T, collectionName: string) : Persister.ILoadLatest => new Persister.RepositoryPersister(db, defValue, collectionName, exchange, pair, loaderSaver.loader, loaderSaver.saver); return { @@ -257,13 +257,13 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { var tbpPersister = getPersister("tbp"); var tsvPersister = getPersister("tsv"); var marketDataPersister = getPersister(Messaging.Topics.MarketData); - + var activePersister = classes.getRepository(classes.startingActive, Messaging.Topics.ActiveChange); var paramsPersister = classes.getRepository(classes.startingParameters, Messaging.Topics.QuotingParametersChange); - + var exchange = classes.exchange; var completedSuccessfully = Q.defer(); - + Q.all([ orderPersister.loadAll(25000), tradesPersister.loadAll(10000), @@ -279,17 +279,17 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { initParams: Models.QuotingParameters, initActive: Models.SerializedQuotesActive, initRfv: Models.RegularFairValue[]) => { - + _.defaults(initParams, defaultQuotingParameters); _.defaults(initActive, defaultActive); - + var orderCache = new Broker.OrderStateCache(); var timeProvider = classes.timeProvider; var getPublisher = classes.getPublisher; - + var advert = new Models.ProductAdvertisement(exchange, pair, config.GetString("TRIBECA_MODE")); getPublisher(Messaging.Topics.ProductAdvertisement).registerSnapshot(() => [advert]).publish(advert); - + var quotePublisher = getPublisher(Messaging.Topics.Quote); var fvPublisher = getPublisher(Messaging.Topics.FairValue, fairValuePersister); var marketDataPublisher = getPublisher(Messaging.Topics.MarketData, marketDataPersister); @@ -304,47 +304,47 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { var tradeSafetyPublisher = getPublisher(Messaging.Topics.TradeSafetyValue, tsvPersister); var positionPublisher = getPublisher(Messaging.Topics.Position, positionPersister); var connectivity = getPublisher(Messaging.Topics.ExchangeConnectivity); - + var messages = new Messages.MessagesPubisher(timeProvider, messagesPersister, initMsgs, messagesPublisher); messages.publish("start up"); - + var getReceiver = classes.getReceiver; var activeReceiver = getReceiver(Messaging.Topics.ActiveChange); var quotingParametersReceiver = getReceiver(Messaging.Topics.QuotingParametersChange); var submitOrderReceiver = getReceiver(Messaging.Topics.SubmitNewOrder); var cancelOrderReceiver = getReceiver(Messaging.Topics.CancelOrder); var cancelAllOrdersReceiver = getReceiver(Messaging.Topics.CancelAllOrders); - + var gateway = classes.getExch(orderCache); - + if (!_.some(gateway.base.supportedCurrencyPairs, p => p.base === pair.base && p.quote === pair.quote)) throw new Error("Unsupported currency pair!. Please check that gateway " + gateway.base.name() + " supports the value specified in TradedPair config value"); - + var broker = new Broker.ExchangeBroker(pair, gateway.md, gateway.base, gateway.oe, connectivity); var orderBroker = new Broker.OrderBroker(timeProvider, broker, gateway.oe, orderPersister, tradesPersister, orderStatusPublisher, tradePublisher, submitOrderReceiver, cancelOrderReceiver, cancelAllOrdersReceiver, messages, orderCache, initOrders, initTrades); var marketDataBroker = new Broker.MarketDataBroker(gateway.md, marketDataPublisher, marketDataPersister, messages); var positionBroker = new Broker.PositionBroker(timeProvider, broker, gateway.pg, positionPublisher, positionPersister, marketDataBroker); - + var paramsRepo = new QuotingParameters.QuotingParametersRepository(quotingParametersPublisher, quotingParametersReceiver, initParams); paramsRepo.NewParameters.on(() => paramsPersister.persist(paramsRepo.latest)); - + var safetyCalculator = new Safety.SafetyCalculator(timeProvider, paramsRepo, orderBroker, paramsRepo, tradeSafetyPublisher, tsvPersister); - + var startQuoting = (timeProvider.utcNow().diff(initActive.time, 'minutes') < 3 && initActive.active); var active = new Active.ActiveRepository(startQuoting, broker, activePublisher, activeReceiver); - + var quoter = new Quoter.Quoter(orderBroker, broker); var filtration = new MarketFiltration.MarketFiltration(quoter, marketDataBroker); var fvEngine = new FairValue.FairValueEngine(timeProvider, filtration, paramsRepo, fvPublisher, fairValuePersister); var ewma = new Statistics.ObservableEWMACalculator(timeProvider, fvEngine, initParams.quotingEwma); - + var rfvValues = _.map(initRfv, (r: Models.RegularFairValue) => r.value); var shortEwma = new Statistics.EwmaStatisticCalculator(initParams.shortEwma); shortEwma.initialize(rfvValues); var longEwma = new Statistics.EwmaStatisticCalculator(initParams.longEwma); longEwma.initialize(rfvValues); - + var registry = new QuotingStyleRegistry.QuotingStyleRegistry([ new MidMarket.MidMarketQuoteStyle(), new TopJoin.InverseJoinQuoteStyle(), @@ -353,16 +353,16 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { new TopJoin.TopOfTheMarketQuoteStyle(), new TopJoin.PingPongQuoteStyle(), ]); - + var positionMgr = new PositionManagement.PositionManager(timeProvider, rfvPersister, fvEngine, initRfv, shortEwma, longEwma); var tbp = new PositionManagement.TargetBasePositionManager(timeProvider, positionMgr, paramsRepo, positionBroker, targetBasePositionPublisher, tbpPersister); var quotingEngine = new QuotingEngine.QuotingEngine(registry, timeProvider, filtration, fvEngine, paramsRepo, quotePublisher, orderBroker, positionBroker, ewma, tbp, safetyCalculator); var quoteSender = new QuoteSender.QuoteSender(timeProvider, quotingEngine, quoteStatusPublisher, quoter, active, positionBroker, fvEngine, marketDataBroker, broker); - + var marketTradeBroker = new MarketTrades.MarketTradeBroker(gateway.md, marketTradePublisher, marketDataBroker, quotingEngine, broker, mktTradePersister, initMktTrades); - + if (config.inBacktestMode) { var t = Utils.date(); console.log("starting backtest"); @@ -374,21 +374,21 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { completedSuccessfully.reject(err); return completedSuccessfully.promise; } - + var results = [paramsRepo.latest, positionBroker.latestReport, { trades: orderBroker._trades.map(t => [t.time.valueOf(), t.price, t.quantity, t.side]), volume: orderBroker._trades.reduce((p, c) => p + c.quantity, 0) }]; console.log("sending back results, took: ", Utils.date().diff(t, "seconds")); - - request({url: serverUrl+"/result", - method: 'POST', + + request({url: serverUrl+"/result", + method: 'POST', json: results}, (err, resp, body) => { }); - + completedSuccessfully.resolve(true); return completedSuccessfully.promise; } - + exitingEvent = () => { var a = new Models.SerializedQuotesActive(active.savedQuotingMode, timeProvider.utcNow()); mainLog.info("persisting active to", a.active); @@ -406,7 +406,7 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { }, moment.duration(1000)); return completedSuccessfully.promise; }; - + // event looped blocked timer var start = process.hrtime(); var interval = 100; @@ -418,7 +418,7 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { mainLog.info("Event looped blocked for " + Utils.roundFloat(n) + "ms"); start = process.hrtime(); }, interval).unref(); - + }).done(); return completedSuccessfully.promise; @@ -427,59 +427,59 @@ var runTradingSystem = (classes: SimulationClasses) : Q.Promise => { var harness = () : Q.Promise => { if (config.inBacktestMode) { console.log("enter backtest mode"); - + var getFromBacktestServer = (ep: string) : Q.Promise => { var d = Q.defer(); - request.get(serverUrl+"/"+ep, (err, resp, body) => { + request.get(serverUrl+"/"+ep, (err, resp, body) => { if (err) d.reject(err); else d.resolve(body); }); return d.promise; }; - + var inputDataPromise = getFromBacktestServer("inputData").then(body => { var inp : Array = (typeof body ==="string") ? eval(body) : body; - + for (var i = 0; i < inp.length; i++) { var d = inp[i]; d.time = moment(d.time); } - + return inp; }); - + var nextParameters = () : Q.Promise => getFromBacktestServer("nextParameters").then(body => { var p = (typeof body ==="string") ? JSON.parse(body) : body; console.log("Recv'd parameters", util.inspect(p)); return (typeof p === "string") ? null : p; }); - + var promiseWhile = (body : () => Q.Promise) => { var done = Q.defer(); - + var loop = () => { body().then(possibleResult => { if (!possibleResult) return done.resolve(null); else Q.when(possibleResult, loop, done.reject); }); }; - + Q.nextTick(loop); return done.promise; }; - + var runLoop = (inputMarketData : Array) : Q.Promise => { var singleRun = () => { var runWithParameters = (p : Backtest.BacktestParameters) => { return p !== null ? runTradingSystem(backTestSimulationSetup(inputMarketData, p)) : false; }; - + return nextParameters().then(runWithParameters); }; - + return promiseWhile(singleRun); }; - + return inputDataPromise.then(runLoop); } else { diff --git a/src/service/markettrades.ts b/src/service/markettrades.ts index 8f9a6afb8..bfd184887 100644 --- a/src/service/markettrades.ts +++ b/src/service/markettrades.ts @@ -21,18 +21,18 @@ import QuotingEngine = require("./quoting-engine"); export class MarketTradesLoaderSaver { public loader = (x : Models.MarketTrade) => { this._wrapped.loader(x); - + if (typeof x.quote !== "undefined" && x.quote !== null) this._wrapped.loader(x.quote); }; - + public saver = (x : Models.MarketTrade) => { this._wrapped.saver(x); - + if (typeof x.quote !== "undefined" && x.quote !== null) this._wrapped.saver(x.quote); }; - + constructor(private _wrapped: P.LoaderSaver) {} } @@ -48,7 +48,7 @@ export class MarketTradeBroker implements Interfaces.IMarketTradeBroker { var qt = u.onStartup ? null : this._quoteEngine.latestQuote; var mkt = u.onStartup ? null : this._mdBroker.currentBook; - var t = new Models.MarketTrade(this._base.exchange(), this._base.pair, u.price, u.size, u.time, qt, + var t = new Models.MarketTrade(this._base.exchange(), this._base.pair, u.price, u.size, u.time, qt, mkt === null ? null : mkt.bids[0], mkt === null ? null : mkt.asks[0], u.make_side); if (u.onStartup) { @@ -79,7 +79,7 @@ export class MarketTradeBroker implements Interfaces.IMarketTradeBroker { private _base: Broker.ExchangeBroker, private _persister: P.IPersist, initMkTrades: Array) { - + initMkTrades.forEach(t => this.marketTrades.push(t)); this._log.info("loaded %d market trades", this.marketTrades.length); diff --git a/src/service/position-management.ts b/src/service/position-management.ts index 35b7d8bfc..bb6577a57 100644 --- a/src/service/position-management.ts +++ b/src/service/position-management.ts @@ -63,7 +63,7 @@ export class PositionManager { this.NewTargetPosition.trigger(); } - this._log.info("recalculated regular fair value, short:", Utils.roundFloat(newShort), "long:", + this._log.info("recalculated regular fair value, short:", Utils.roundFloat(newShort), "long:", Utils.roundFloat(newLong), "target:", Utils.roundFloat(this._latest), "currentFv:", Utils.roundFloat(fv.price)); this._data.push(rfv); diff --git a/src/service/quote-sender.ts b/src/service/quote-sender.ts index f4fbc66af..fa90ef511 100644 --- a/src/service/quote-sender.ts +++ b/src/service/quote-sender.ts @@ -91,7 +91,7 @@ export class QuoteSender { bidStatus = Models.QuoteStatus.Live; } } - + var askAction: Models.QuoteSent; if (askStatus === Models.QuoteStatus.Live) { askAction = this._quoter.updateQuote(new Models.Timestamped(quote.ask, t), Models.Side.Ask); diff --git a/src/service/quoting-engine.ts b/src/service/quoting-engine.ts index b7b03c455..484ba3155 100644 --- a/src/service/quoting-engine.ts +++ b/src/service/quoting-engine.ts @@ -67,14 +67,14 @@ export class QuotingEngine { _quotePublisher.registerSnapshot(() => this.latestQuote === null ? [] : [this.latestQuote]); _targetPosition.NewTargetPosition.on(recalcWithoutInputTime); _safeties.NewValue.on(recalcWithoutInputTime); - + _timeProvider.setInterval(recalcWithoutInputTime, moment.duration(1, "seconds")); } private computeQuote(filteredMkt: Models.Market, fv: Models.FairValue) { var params = this._qlParamRepo.latest; var unrounded = this._registry.Get(params.mode).GenerateQuote(filteredMkt, fv, params); - + if (unrounded === null) return null; @@ -94,37 +94,37 @@ export class QuotingEngine { return null; } var targetBasePosition = tbp.data; - + var latestPosition = this._positionBroker.latestReport; var totalBasePosition = latestPosition.baseAmount + latestPosition.baseHeldAmount; - + if (totalBasePosition < targetBasePosition - params.positionDivergence) { unrounded.askPx = null; unrounded.askSz = null; if (params.aggressivePositionRebalancing) unrounded.bidSz = Math.min(params.aprMultiplier*params.size, targetBasePosition - totalBasePosition); } - + if (totalBasePosition > targetBasePosition + params.positionDivergence) { unrounded.bidPx = null; unrounded.bidSz = null; if (params.aggressivePositionRebalancing) unrounded.askSz = Math.min(params.aprMultiplier*params.size, totalBasePosition - targetBasePosition); } - + var safety = this._safeties.latest; if (safety === null) { this._log.warn("cannot compute a quote since trade safety is not yet computed!"); return null; } - + if (params.mode === Models.QuotingMode.PingPong) { if (unrounded.askSz && safety.buyPing && unrounded.askPx < safety.buyPing + params.width) unrounded.askPx = safety.buyPing + params.width; if (unrounded.bidSz && safety.sellPong && unrounded.bidPx > safety.sellPong - params.width) unrounded.bidPx = safety.sellPong - params.width; } - + if (safety.sell > params.tradesPerMinute) { unrounded.askPx = null; unrounded.askSz = null; @@ -133,22 +133,22 @@ export class QuotingEngine { unrounded.bidPx = null; unrounded.bidSz = null; } - + if (unrounded.bidPx !== null) { unrounded.bidPx = Utils.roundFloat(unrounded.bidPx); unrounded.bidPx = Math.max(0, unrounded.bidPx); } - + if (unrounded.askPx !== null) { unrounded.askPx = Utils.roundFloat(unrounded.askPx); unrounded.askPx = Math.max(unrounded.bidPx + .01, unrounded.askPx); } - + if (unrounded.askSz !== null) { unrounded.askSz = Utils.roundFloat(unrounded.askSz); unrounded.askSz = Math.max(0.01, unrounded.askSz); } - + if (unrounded.bidSz !== null) { unrounded.bidSz = Utils.roundFloat(unrounded.bidSz); unrounded.bidSz = Math.max(0.01, unrounded.bidSz); diff --git a/src/service/quoting-parameters.ts b/src/service/quoting-parameters.ts index 95721c922..13a35e44e 100644 --- a/src/service/quoting-parameters.ts +++ b/src/service/quoting-parameters.ts @@ -22,7 +22,7 @@ class Repository implements Interfaces.IRepository { defaultParameter: T, private _rec: Messaging.IReceive, private _pub: Messaging.IPublish) { - + this._log.info("Starting parameter:", defaultParameter); _pub.registerSnapshot(() => [this.latest]); _rec.registerReceiver(this.updateParameters); diff --git a/src/service/quoting-styles/mid-market.ts b/src/service/quoting-styles/mid-market.ts index a9905a341..404776a7f 100644 --- a/src/service/quoting-styles/mid-market.ts +++ b/src/service/quoting-styles/mid-market.ts @@ -5,14 +5,14 @@ import Models = require("../../common/models"); export class MidMarketQuoteStyle implements StyleHelpers.QuoteStyle { Mode = Models.QuotingMode.Mid; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { var width = params.width; var size = params.size; - + var bidPx = Math.max(fv.price - width, 0); var askPx = fv.price + width; - + return new StyleHelpers.GeneratedQuote(bidPx, size, askPx, size); }; } \ No newline at end of file diff --git a/src/service/quoting-styles/style-registry.ts b/src/service/quoting-styles/style-registry.ts index 9a0af049c..d8ca8d4cc 100644 --- a/src/service/quoting-styles/style-registry.ts +++ b/src/service/quoting-styles/style-registry.ts @@ -6,7 +6,7 @@ import _ = require("lodash"); class NullQuoteGenerator implements StyleHelpers.QuoteStyle { Mode = null; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { return null; }; diff --git a/src/service/quoting-styles/top-join.ts b/src/service/quoting-styles/top-join.ts index 607413787..2e2fdb102 100644 --- a/src/service/quoting-styles/top-join.ts +++ b/src/service/quoting-styles/top-join.ts @@ -5,7 +5,7 @@ import Models = require("../../common/models"); export class TopOfTheMarketQuoteStyle implements StyleHelpers.QuoteStyle { Mode = Models.QuotingMode.Top; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { return computeTopJoinQuote(market, fv, params); }; @@ -13,7 +13,7 @@ export class TopOfTheMarketQuoteStyle implements StyleHelpers.QuoteStyle { export class InverseTopOfTheMarketQuoteStyle implements StyleHelpers.QuoteStyle { Mode = Models.QuotingMode.InverseTop; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { return computeInverseJoinQuote(market, fv, params); }; @@ -21,7 +21,7 @@ export class InverseTopOfTheMarketQuoteStyle implements StyleHelpers.QuoteStyle export class InverseJoinQuoteStyle implements StyleHelpers.QuoteStyle { Mode = Models.QuotingMode.InverseJoin; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { return computeInverseJoinQuote(market, fv, params); }; @@ -37,7 +37,7 @@ export class PingPongQuoteStyle implements StyleHelpers.QuoteStyle { export class JoinQuoteStyle implements StyleHelpers.QuoteStyle { Mode = Models.QuotingMode.Join; - + GenerateQuote = (market: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) : StyleHelpers.GeneratedQuote => { return computeTopJoinQuote(market, fv, params); }; @@ -103,7 +103,7 @@ function computeInverseJoinQuote(filteredMkt: Models.Market, fv: Models.FairValu return genQt; } -//computePingPongQuote is same as computeTopJoinQuote but need to use params.mode === Models.QuotingMode.PingPong +//computePingPongQuote is same as computeTopJoinQuote but need to use params.mode === Models.QuotingMode.PingPong function computePingPongQuote(filteredMkt: Models.Market, fv: Models.FairValue, params: Models.QuotingParameters) { var genQt = getQuoteAtTopOfMarket(filteredMkt, params); diff --git a/src/service/safety.ts b/src/service/safety.ts index a1f2c5e80..266e45a65 100644 --- a/src/service/safety.ts +++ b/src/service/safety.ts @@ -47,7 +47,7 @@ export class SafetyCalculator { _repo.NewParameters.on(_ => this.computeQtyLimit()); _qlParams.NewParameters.on(_ => this.computeQtyLimit()); _broker.Trade.on(this.onTrade); - + _timeProvider.setInterval(this.computeQtyLimit, moment.duration(1, "seconds")); } diff --git a/src/service/utils.ts b/src/service/utils.ts index 57f6a16ce..3ea06b1c1 100644 --- a/src/service/utils.ts +++ b/src/service/utils.ts @@ -51,11 +51,11 @@ export class Evt { public on = (handler: (data?: T) => void) => this._event.addListener("evt", handler); public trigger = (data?: T) => this._event.emit("evt", data); - + public once = (handler: (data?: T) => void) => this._event.once("evt", handler); - + public setMaxListeners = (max: number) => this._event.setMaxListeners(max); - + public removeAllListeners = () => this._event.removeAllListeners(); } @@ -76,12 +76,12 @@ export interface IBacktestingTimeProvider extends ITimeProvider { export class RealTimeProvider implements ITimeProvider { constructor() { } - + utcNow = () => moment.utc(); - + setTimeout = (action: () => void, time: moment.Duration) => setTimeout(action, time.asMilliseconds()); - + setImmediate = (action: () => void) => setImmediate(action); - + setInterval = (action: () => void, time: moment.Duration) => setInterval(action, time.asMilliseconds()); } \ No newline at end of file diff --git a/src/service/web.ts b/src/service/web.ts index d7ae7c208..90d8eb040 100644 --- a/src/service/web.ts +++ b/src/service/web.ts @@ -19,13 +19,13 @@ export class StandaloneHttpPublisher { private route: string, private _httpApp: express.Application, private _persister: Persister.ILoadAll) { - + _httpApp.get("/data/" + route, (req: express.Request, res: express.Response) => { var getParameter = (pName: string, cvt: (r: string) => T) => { var rawMax : string = req.param(pName, null); return (rawMax === null ? null : cvt(rawMax)); }; - + var max = getParameter("max", r => parseInt(r)); var startTime = getParameter("start_time", r => moment(r)); diff --git a/test/backtest.ts b/test/backtest.ts index 30be0d1d8..731aef947 100644 --- a/test/backtest.ts +++ b/test/backtest.ts @@ -12,82 +12,82 @@ import util = require("util"); describe("BacktestTests", () => { var timeProvider : Backtest.BacktestTimeProvider; - + beforeEach(() => { var t0 = Moment.unix(1); var t1 = Moment.unix(10); timeProvider = new Backtest.BacktestTimeProvider(t0, t1); }); - + it("Should increment time", () => { timeProvider.scrollTimeTo(Moment.unix(2)); assert.equal(timeProvider.utcNow().diff(Moment.unix(2)), 0); }); - + it("Should not allow rewinding time", () => { timeProvider.scrollTimeTo(Moment.unix(6)); assert.throws(() => timeProvider.scrollTimeTo(Moment.unix(2))); }); - + it("Should handle timeouts", () => { var triggered = false; timeProvider.setTimeout(() => triggered = true, Moment.duration(4, "seconds")); timeProvider.scrollTimeTo(Moment.unix(2)); assert.equal(triggered, false, "should not yet be triggered"); - + timeProvider.scrollTimeTo(Moment.unix(7)); assert.equal(triggered, true, "should be triggered"); }); - + it("Should handle timeouts in order", () => { var triggeredFirst = false; timeProvider.setTimeout(() => triggeredFirst = true, Moment.duration(4, "seconds")); - + var triggeredSecond = false; timeProvider.setTimeout(() => triggeredSecond = true, Moment.duration(7, "seconds")); timeProvider.scrollTimeTo(Moment.unix(2)); assert.equal(triggeredFirst, false, "1 should not yet be triggered"); assert.equal(triggeredSecond, false, "2 should not yet be triggered"); - + timeProvider.scrollTimeTo(Moment.unix(7)); assert.equal(triggeredFirst, true, "1 should be triggered"); assert.equal(triggeredSecond, false, "2 should not yet be triggered"); - + timeProvider.scrollTimeTo(Moment.unix(9)); assert.equal(triggeredFirst, true, "1 should be triggered"); assert.equal(triggeredSecond, true, "2 should be triggered"); }); - + it("Should handle intervals", () => { var nTimes = 0; timeProvider.setInterval(() => nTimes += 1, Moment.duration(2, "seconds")); - + timeProvider.scrollTimeTo(Moment.unix(9)); assert.equal(nTimes, 3); }); - + it("Should handle both intervals and timouts", () => { var nTimes = 0; timeProvider.setInterval(() => nTimes += 1, Moment.duration(2, "seconds")); - + var triggeredFirst = false; timeProvider.setTimeout(() => triggeredFirst = true, Moment.duration(4, "seconds")); - + var triggeredSecond = false; timeProvider.setTimeout(() => triggeredSecond = true, Moment.duration(7, "seconds")); - + timeProvider.scrollTimeTo(Moment.unix(2)); assert.equal(nTimes, 0); assert.equal(triggeredFirst, false, "1 should not yet be triggered"); assert.equal(triggeredSecond, false, "2 should not yet be triggered"); - + timeProvider.scrollTimeTo(Moment.unix(7)); assert.equal(nTimes, 2); assert.equal(triggeredFirst, true, "1 should be triggered"); assert.equal(triggeredSecond, false, "2 should not yet be triggered"); - + timeProvider.scrollTimeTo(Moment.unix(9)); assert.equal(nTimes, 3); assert.equal(triggeredFirst, true, "1 should be triggered"); @@ -101,14 +101,14 @@ describe("BacktestGatewayTests", () => { new Models.Market([new Models.MarketSide(10, 5)], [new Models.MarketSide(20, 5)], Moment.unix(1)), new Models.Market([new Models.MarketSide(15, 5)], [new Models.MarketSide(20, 5)], Moment.unix(10)), ]; - + var timeProvider = new Backtest.BacktestTimeProvider(Moment.unix(1), Moment.unix(40)); var gateway = new Backtest.BacktestGateway(inputData, 10, 5000, timeProvider); - + gateway.MarketData.once(m => { gateway.sendOrder(new Models.BrokeredOrder("A", Models.Side.Ask, 3, Models.OrderType.Limit, 12, Models.TimeInForce.GTC, Models.Exchange.Null)); }); - + var gotTrade = false; gateway.OrderUpdate.on(o => { if (o.orderStatus === Models.OrderStatus.Complete) { @@ -117,13 +117,13 @@ describe("BacktestGatewayTests", () => { assert.equal(3, o.lastQuantity); } }); - + /*gateway.PositionUpdate.on(p => { console.log(Models.Currency[p.currency], p.amount, p.heldAmount); });*/ - + gateway.run(); - + assert(gotTrade === true, "never got trade"); }); }); \ No newline at end of file