├── .gitignore ├── LICENSE.md ├── MatchEngine.js ├── TestMatchEngine.js ├── app.js ├── package.json ├── public ├── style.css └── stylesheets │ ├── style.css │ └── style.styl ├── routes └── index.js └── views ├── index.ejs └── match.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Dylan Lingelbach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MatchEngine.js: -------------------------------------------------------------------------------- 1 | var createEngine = function createEngine() { 2 | var util = require('util'), 3 | EventEmitter = require('events').EventEmitter; 4 | 5 | var getNewId = function getNewId() { 6 | var orderId = 0; 7 | return function() { 8 | orderId = orderId + 1; 9 | return orderId; 10 | } 11 | }(); 12 | 13 | var adjustQuantity = function(order, newQuantity) { 14 | order.quantity = Math.max(0, newQuantity); 15 | if (order.quantity === 0) { 16 | order.status = "Complete"; 17 | } 18 | }; 19 | 20 | var Book = function () { 21 | this.levels = []; 22 | }; 23 | 24 | Book.prototype.map = function(fun) { 25 | return this.levels.map(fun); 26 | }; 27 | 28 | Book.prototype.addOrder = function(order) { 29 | if (order.quantity <= 0) 30 | return; 31 | var levelIndex = 0; 32 | var level = this.levels[0]; 33 | 34 | var comp = order.isBuy ? 35 | function(price) { return order.price < price; } 36 | : 37 | function(price) { return order.price > price; } 38 | 39 | while (level && comp(level.price)) 40 | { 41 | levelIndex = levelIndex + 1; 42 | level = this.levels[levelIndex]; 43 | } 44 | 45 | if (!level || level.price !== order.price) 46 | { 47 | level = []; 48 | level.price = order.price; 49 | this.levels.splice(levelIndex, 0, level); 50 | } 51 | level.push(order); 52 | }; 53 | 54 | Book.prototype.removeOrder = function(order) { 55 | for (var l = 0; l < this.levels.length; l++) 56 | { 57 | var level = this.levels[l]; 58 | if (level.price === order.price) 59 | { 60 | for (var i = 0; i < level.length; i++) 61 | { 62 | if (level[i].id === order.id) 63 | { 64 | level.splice(i, 1); 65 | if (level.length === 0) 66 | { 67 | this.levels.splice(i, 1); 68 | } 69 | return true; 70 | } 71 | } 72 | } 73 | } 74 | 75 | return false; 76 | }; 77 | 78 | Book.prototype.findMatches = function(order) { 79 | var comp = order.isBuy ? 80 | function(price) { return order.price >= price; } 81 | : 82 | function(price) { return order.price <= price; } 83 | 84 | var level = this.levels[0]; 85 | var remainingQuantity = order.quantity; 86 | var matches = []; 87 | for (var i = 0; i < this.levels.length; i++) 88 | { 89 | var level = this.levels[i]; 90 | if (!comp(level.price)) 91 | { 92 | break; 93 | } 94 | 95 | for (var j = 0; j < level.length && remainingQuantity > 0; j++) 96 | { 97 | var restingOrder = level[j]; 98 | matches.push(restingOrder); 99 | remainingQuantity = remainingQuantity - restingOrder.quantity; 100 | } 101 | } 102 | 103 | return matches; 104 | }; 105 | 106 | var Engine = function(){ 107 | this.bids = new Book(); 108 | this.offers = new Book(); 109 | this.orders = {}; 110 | }; 111 | util.inherits(Engine, EventEmitter); 112 | 113 | Engine.prototype.submitOrder = function(order) { 114 | if (order.quantity === 0 ) { 115 | throw new Error("Order must have non-zero quantity"); 116 | } 117 | 118 | var isBuy = order.quantity > 0; 119 | var book = isBuy ? this.bids : this.offers; 120 | var otherBook = isBuy ? this.offers : this.bids; 121 | 122 | var aggressiveOrder = { 123 | id: getNewId(), 124 | price: order.price, 125 | quantity: Math.abs(order.quantity), 126 | status: "Working", 127 | isBuy: isBuy 128 | }; 129 | 130 | var matches = otherBook.findMatches(aggressiveOrder); 131 | 132 | this.orders[aggressiveOrder.id] = aggressiveOrder; 133 | 134 | for (var i = 0; i < matches.length; i++) 135 | { 136 | var restingOrder = matches[i]; 137 | var matchQuantity = Math.min(aggressiveOrder.quantity, restingOrder.quantity); 138 | adjustQuantity(restingOrder, restingOrder.quantity - matchQuantity); 139 | adjustQuantity(aggressiveOrder, aggressiveOrder.quantity - matchQuantity); 140 | 141 | this.emit('match', restingOrder, aggressiveOrder, restingOrder.price, matchQuantity); 142 | 143 | if (restingOrder.quantity === 0) 144 | { 145 | otherBook.removeOrder(restingOrder); 146 | } 147 | } 148 | 149 | if (aggressiveOrder.quantity > 0) 150 | { 151 | book.addOrder(aggressiveOrder); 152 | } 153 | 154 | return aggressiveOrder.id; 155 | }; 156 | 157 | Engine.prototype.getStatus = function(orderId) { 158 | var order = this.orders[orderId]; 159 | return order ? 160 | { 161 | status: order.status, 162 | workingQuantity: order.quantity 163 | } 164 | : undefined; 165 | }; 166 | 167 | Engine.prototype.cancelOrder = function(orderId) { 168 | var order = this.orders[orderId]; 169 | if (!order) 170 | { 171 | return false; 172 | } 173 | 174 | if (order.status !== "Working") 175 | { 176 | return false; 177 | } 178 | var book = order.isBuy ? this.bids : this.offers; 179 | book.removeOrder(order); 180 | order.status = "Cancelled"; 181 | 182 | return true; 183 | }; 184 | 185 | Engine.prototype.getMarketData = function() { 186 | var levelReduce = function(order1, order2) { 187 | return { 188 | quantity: order1.quantity + order2.quantity, 189 | price: order1.price 190 | } 191 | }; 192 | var levelConverter = function(level) { 193 | if (!level) return []; 194 | if (level.length == 1) { 195 | return { quantity: level[0].quantity, price: level[0].price }; 196 | } 197 | return level.reduce(levelReduce) 198 | }; 199 | var bids = this.bids.map(levelConverter); 200 | var offers = this.offers.map(levelConverter); 201 | 202 | return { 203 | bids: bids, 204 | offers: offers 205 | }; 206 | }; 207 | 208 | return new Engine(); 209 | }; 210 | 211 | exports.createEngine = createEngine; -------------------------------------------------------------------------------- /TestMatchEngine.js: -------------------------------------------------------------------------------- 1 | assert = require("assert") 2 | matchEngine = require("./MatchEngine.js") 3 | console = require("console") 4 | 5 | var tests = {}; 6 | tests.createEngine = function() { 7 | var engine = matchEngine.createEngine(); 8 | assert.ok(engine, "Engine created"); 9 | }; 10 | tests.submitBuyOrder = function() { 11 | var engine = matchEngine.createEngine(); 12 | var id = engine.submitOrder({quantity: "3", price: "2.5"}); 13 | assert.ok(id, "order id returned"); 14 | }; 15 | tests.submitMultipleBuyOrders = function() { 16 | var engine = matchEngine.createEngine(); 17 | var id1 = engine.submitOrder({quantity: "3", price: "2.5"}); 18 | var id2 = engine.submitOrder({quantity: "3", price: "2.5"}); 19 | assert.ok(id1, "order id returned"); 20 | assert.ok(id2, "order id returned"); 21 | assert.notEqual(id1, id2, "Order ids must be unique"); 22 | }; 23 | tests.zeroQuantityOrder = function() { 24 | var engine = matchEngine.createEngine(); 25 | assert.throws(function() { engine.submitOrder({quantity: 0, price: "2.5"})}, Error); 26 | } 27 | tests.canCheckOrderStatus = function() { 28 | var engine = matchEngine.createEngine(); 29 | var id = engine.submitOrder({quantity: "3", price: "2.5"}); 30 | var status = engine.getStatus(id); 31 | assert.ok(status, "order status returned"); 32 | assert.equal(status.status, "Working"); 33 | }; 34 | tests.checkUnknownOrderStatus = function() { 35 | var engine = matchEngine.createEngine(); 36 | var status = engine.getStatus("1"); 37 | assert.ok(!status, "No status should be returned for unknown order"); 38 | }; 39 | tests.canCancelOrder = function() { 40 | var engine = matchEngine.createEngine(); 41 | var id = engine.submitOrder({quantity: "3", price: "2.5"}); 42 | var result = engine.cancelOrder(id); 43 | assert.ok(result, "cancel should succeed"); 44 | var status = engine.getStatus(id); 45 | assert.equal(status.status, "Cancelled"); 46 | }; 47 | tests.cancelOrderTwice = function() { 48 | var engine = matchEngine.createEngine(); 49 | var id = engine.submitOrder({quantity: "3", price: "2.5"}); 50 | engine.cancelOrder(id); 51 | var result = engine.cancelOrder(id); 52 | assert.ok(!result, "cancel should return false when called for the same order"); 53 | }; 54 | tests.cancelNonExistentOrder = function() { 55 | var engine = matchEngine.createEngine(); 56 | var result = engine.cancelOrder("1"); 57 | assert.ok(!result, "cancel should not succeed"); 58 | }; 59 | tests.matchRaisesEvent = function() { 60 | var engine = matchEngine.createEngine(); 61 | var matched = false; 62 | var buyId = engine.submitOrder({quantity: "3", price: "2.5"}); 63 | engine.on("match", function(restingOrder, aggressiveOrder, price, quantity) { 64 | assert.equal(restingOrder.id, buyId, "Resting order should be buy order"); 65 | assert.equal(price, "2.5"); 66 | assert.equal(quantity, 3); 67 | 68 | matched = true; 69 | }); 70 | 71 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 72 | 73 | assert.ok(matched, "Match should have occurred."); 74 | 75 | var buyStatus = engine.getStatus(buyId); 76 | assert.equal(buyStatus.status, "Complete"); 77 | var sellStatus = engine.getStatus(sellId); 78 | assert.equal(sellStatus.status, "Complete"); 79 | }; 80 | tests.cancelPreventsMatch = function() { 81 | var engine = matchEngine.createEngine(); 82 | var buyId = engine.submitOrder({quantity: "3", price: "2.5"}); 83 | engine.on("match", function() { 84 | assert.fail("Should not match after a cancel.") 85 | }); 86 | 87 | engine.cancelOrder(buyId); 88 | 89 | engine.submitOrder({quantity: "-3", price: "2.5"}); 90 | }; 91 | tests.matchAfterCancelRaisesEvent = function() { 92 | var engine = matchEngine.createEngine(); 93 | var matched = false; 94 | var buyOrder = {quantity: "3", price: "2.5"}; 95 | var buyId = engine.submitOrder(buyOrder); 96 | engine.cancelOrder(buyId); 97 | buyId = engine.submitOrder(buyOrder); 98 | 99 | engine.on("match", function(restingOrder, aggressiveOrder, price, quantity) { 100 | assert.equal(restingOrder.id, buyId, "Resting order should be buy order"); 101 | assert.equal(price, "2.5"); 102 | assert.equal(quantity, 3); 103 | 104 | matched = true; 105 | }); 106 | 107 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 108 | 109 | assert.ok(matched, "Match should have occurred."); 110 | 111 | var buyStatus = engine.getStatus(buyId); 112 | assert.equal(buyStatus.status, "Complete"); 113 | var sellStatus = engine.getStatus(sellId); 114 | assert.equal(sellStatus.status, "Complete"); 115 | }; 116 | tests.sweepRaisesEvents = function() { 117 | var engine = matchEngine.createEngine(); 118 | var buyId1 = engine.submitOrder({quantity: "1", price: "2.7"}); 119 | var buyId2 = engine.submitOrder({quantity: "1", price: "2.6"}); 120 | var buyId3 = engine.submitOrder({quantity: "1", price: "2.5"}); 121 | var restingIds = [buyId1, buyId2, buyId3]; 122 | var prices = [2.7, 2.6, 2.5]; 123 | var quantities = [1, 1, 1]; 124 | var matchCount = 0; 125 | engine.on("match", function(restingOrder, aggressiveOrder, matchPrice, matchQuantity) { 126 | var id = restingIds[matchCount]; 127 | var price = prices[matchCount]; 128 | var quantity = quantities[matchCount]; 129 | assert.equal(restingOrder.id, id, "Resting order should match expected"); 130 | assert.equal(price, matchPrice, "Prices should match"); 131 | assert.equal(quantity, matchQuantity, "Quantities should match"); 132 | 133 | matchCount = matchCount + 1; 134 | }); 135 | 136 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 137 | 138 | assert.equal(matchCount, 3, "Three matches should have occurred."); 139 | 140 | var buyStatus = engine.getStatus(buyId1); 141 | assert.equal(buyStatus.status, "Complete"); 142 | buyStatus = engine.getStatus(buyId2); 143 | assert.equal(buyStatus.status, "Complete"); 144 | buyStatus = engine.getStatus(buyId3); 145 | assert.equal(buyStatus.status, "Complete"); 146 | var sellStatus = engine.getStatus(sellId); 147 | assert.equal(sellStatus.status, "Complete"); 148 | }; 149 | tests.sweepRaisesEventsWithAscendingBuys = function() { 150 | var engine = matchEngine.createEngine(); 151 | var buyId1 = engine.submitOrder({quantity: "1", price: "2.5"}); 152 | var buyId2 = engine.submitOrder({quantity: "1", price: "2.6"}); 153 | var buyId3 = engine.submitOrder({quantity: "1", price: "2.7"}); 154 | var restingIds = [buyId3, buyId2, buyId1]; 155 | var prices = [2.7, 2.6, 2.5]; 156 | var quantities = [1, 1, 1]; 157 | var matchCount = 0; 158 | engine.on("match", function(restingOrder, aggressiveOrder, matchPrice, matchQuantity) { 159 | var id = restingIds[matchCount]; 160 | var price = prices[matchCount]; 161 | var quantity = quantities[matchCount]; 162 | assert.equal(restingOrder.id, id, "Resting order should match expected"); 163 | assert.equal(price, matchPrice, "Prices should match"); 164 | assert.equal(quantity, matchQuantity, "Quantities should match"); 165 | 166 | matchCount = matchCount + 1; 167 | }); 168 | 169 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 170 | 171 | assert.equal(matchCount, 3, "Three matches should have occurred."); 172 | 173 | var buyStatus = engine.getStatus(buyId1); 174 | assert.equal(buyStatus.status, "Complete"); 175 | buyStatus = engine.getStatus(buyId2); 176 | assert.equal(buyStatus.status, "Complete"); 177 | buyStatus = engine.getStatus(buyId3); 178 | assert.equal(buyStatus.status, "Complete"); 179 | var sellStatus = engine.getStatus(sellId); 180 | assert.equal(sellStatus.status, "Complete"); 181 | }; 182 | tests.partialsRaiseEvents = function() { 183 | var engine = matchEngine.createEngine(); 184 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 185 | var restingIds = [sellId, sellId, sellId]; 186 | var prices = [2.5, 2.5, 2.5]; 187 | var quantities = [1, 1, 1]; 188 | var matchCount = 0; 189 | engine.on("match", function(restingOrder, aggressiveOrder, matchPrice, matchQuantity) { 190 | var id = restingIds[matchCount]; 191 | var price = prices[matchCount]; 192 | var quantity = quantities[matchCount]; 193 | assert.equal(restingOrder.id, id, "Resting order should match expected"); 194 | assert.equal(price, matchPrice, "Prices should match"); 195 | assert.equal(quantity, matchQuantity, "Quantities should match"); 196 | 197 | matchCount = matchCount + 1; 198 | }); 199 | 200 | var buyId1 = engine.submitOrder({quantity: "1", price: "2.7"}); 201 | var sellStatus = engine.getStatus(sellId); 202 | assert.equal(sellStatus.status, "Working"); 203 | assert.equal(sellStatus.workingQuantity, 2); 204 | 205 | var buyId2 = engine.submitOrder({quantity: "1", price: "2.6"}); 206 | sellStatus = engine.getStatus(sellId); 207 | assert.equal(sellStatus.status, "Working"); 208 | assert.equal(sellStatus.workingQuantity, 1); 209 | 210 | var buyId3 = engine.submitOrder({quantity: "1", price: "2.5"}); 211 | 212 | assert.equal(matchCount, 3, "Three matches should have occurred."); 213 | 214 | var buyStatus = engine.getStatus(buyId1); 215 | assert.equal(buyStatus.status, "Complete"); 216 | buyStatus = engine.getStatus(buyId2); 217 | assert.equal(buyStatus.status, "Complete"); 218 | buyStatus = engine.getStatus(buyId3); 219 | assert.equal(buyStatus.status, "Complete"); 220 | sellStatus = engine.getStatus(sellId); 221 | assert.equal(sellStatus.status, "Complete"); 222 | }; 223 | tests.cancelAfterPartialClearsBook = function() { 224 | var engine = matchEngine.createEngine(); 225 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 226 | 227 | engine.submitOrder({quantity: "1", price: "2.7"}); 228 | engine.submitOrder({quantity: "1", price: "2.6"}); 229 | 230 | engine.cancelOrder(sellId); 231 | 232 | engine.on("match", function(restingOrder, aggressiveOrder, price, quantity) { 233 | assert.fail("Should not match after cancel"); 234 | }); 235 | 236 | engine.submitOrder({quantity: "1", price: "2.5"}); 237 | }; 238 | tests.resubmitAfterMatchDoesNotMatch = function() { 239 | var engine = matchEngine.createEngine(); 240 | engine.submitOrder({quantity: "3", price: "2.5"}); 241 | engine.submitOrder({quantity: "-3", price: "2.5"}); 242 | 243 | engine.on("match", function(restingOrder, aggressiveOrder, price, quantity) { 244 | assert.fail("Should not match against empty book") 245 | }); 246 | 247 | var sellId = engine.submitOrder({quantity: "-3", price: "2.5"}); 248 | 249 | var sellStatus = engine.getStatus(sellId); 250 | assert.equal(sellStatus.status, "Working"); 251 | }; 252 | tests.getMarketData = function() { 253 | var engine = matchEngine.createEngine(); 254 | engine.submitOrder({quantity: "3", price: "2.5"}); 255 | engine.submitOrder({quantity: "3", price: "2.5"}); 256 | engine.submitOrder({quantity: "-3", price: "2.6"}); 257 | 258 | var marketData = engine.getMarketData(); 259 | var bids = marketData.bids; 260 | var offers = marketData.offers; 261 | 262 | assert.equal(1, bids.length); 263 | assert.equal(1, offers.length); 264 | 265 | assert.equal(6, bids[0].quantity); 266 | assert.equal(3, offers[0].quantity); 267 | 268 | assert.equal(2.5, bids[0].price); 269 | assert.equal(2.6, offers[0].price); 270 | }; 271 | tests.multipleLevelsOfMarketData = function() { 272 | var engine = matchEngine.createEngine(); 273 | engine.submitOrder({quantity: "3", price: "2.5"}); 274 | engine.submitOrder({quantity: "3", price: "2.5"}); 275 | engine.submitOrder({quantity: "3", price: "2.5"}); 276 | engine.submitOrder({quantity: "2", price: "2.4"}); 277 | engine.submitOrder({quantity: "-3", price: "2.6"}); 278 | 279 | var marketData = engine.getMarketData(); 280 | var bids = marketData.bids; 281 | var offers = marketData.offers; 282 | 283 | assert.equal(2, bids.length); 284 | assert.equal(1, offers.length); 285 | 286 | assert.equal(9, bids[0].quantity); 287 | assert.equal(2, bids[1].quantity); 288 | assert.equal(3, offers[0].quantity); 289 | 290 | assert.equal(2.5, bids[0].price); 291 | assert.equal(2.4, bids[1].price); 292 | assert.equal(2.6, offers[0].price); 293 | }; 294 | tests.marketDataStripsOrderInformation = function() { 295 | var engine = matchEngine.createEngine(); 296 | engine.submitOrder({quantity: "3", price: "2.5"}); 297 | 298 | var marketData = engine.getMarketData(); 299 | var bids = marketData.bids; 300 | 301 | assert.ok(!bids[0].id); 302 | }; 303 | 304 | for (var test in tests) { 305 | if (tests.hasOwnProperty(test) && (typeof tests[test] == 'function')) { 306 | try{ 307 | tests[test](); 308 | console.info("Passed: " + test); 309 | } catch (e) { 310 | console.error("Failed: " + test + " Error: " + e); 311 | } 312 | } 313 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , routes = require('./routes') 8 | , http = require('http') 9 | , path = require('path') 10 | , matchEngine = require('./MatchEngine.js'); 11 | 12 | var engineRegistry = (function() { 13 | var registry = {}; 14 | return { 15 | getEngine: function(contract) { 16 | var engine = registry[contract]; 17 | if (!engine) { 18 | registry[contract] = engine = matchEngine.createEngine(); 19 | } 20 | return engine; 21 | } 22 | }; 23 | })(); 24 | 25 | var app = express(); 26 | 27 | app.configure(function(){ 28 | app.set('port', process.env.PORT || 3000); 29 | app.set('views', __dirname + '/views'); 30 | app.set('view engine', 'ejs'); 31 | app.use(express.favicon()); 32 | app.use(express.logger('dev')); 33 | app.use(express.bodyParser()); 34 | app.use(express.methodOverride()); 35 | app.use(express.cookieParser('your secret here')); 36 | app.use(express.session()); 37 | app.use(app.router); 38 | app.use(require('stylus').middleware(__dirname + '/public')); 39 | app.use(express.static(path.join(__dirname, 'public'))); 40 | }); 41 | 42 | app.configure('development', function(){ 43 | app.use(express.errorHandler()); 44 | }); 45 | 46 | app.get('/', routes.index); 47 | app.get(/^\/match\/(\w+)$/, function(req, res) { 48 | var contract = req.params[0]; 49 | var engine = engineRegistry.getEngine(contract); 50 | var renderMarketData = function(level) { 51 | if (!level) 52 | return ''; 53 | return level.quantity + '\t\t @ \t\t' + new Number(level.price).toFixed(2); 54 | }; 55 | console.log(req.session); 56 | res.render('match', { title: contract + ' match engine', contract: contract, engine: engine, render: renderMarketData, orders: req.session.orders || []}); 57 | }); 58 | 59 | app.get(/^\/cancel\/(\w+)\/(\d+)$/, function(req, res) { 60 | var contract = req.params[0]; 61 | var id = req.params[1]; 62 | var engine = engineRegistry.getEngine(contract); 63 | engine.cancelOrder(id); 64 | res.redirect('/match/'+contract); 65 | }); 66 | 67 | app.post(/^\/match\/(\w+)$/, function(req, res) { 68 | var contract = req.params[0]; 69 | var engine = engineRegistry.getEngine(contract); 70 | if (!req.session.orders) 71 | req.session.orders = []; 72 | var id = engine.submitOrder(req.body); 73 | req.session.orders.push({id: id, quantity: req.body.quantity, price: req.body.price}); 74 | res.redirect('/match/'+contract); 75 | }); 76 | 77 | http.createServer(app).listen(app.get('port'), function(){ 78 | console.log("Express server listening on port " + app.get('port')); 79 | }); 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MatchEngine", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app" 7 | }, 8 | "dependencies": { 9 | "express": "3.0.5", 10 | "ejs": "*", 11 | "stylus": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanlingelbach/NodeMatchEngine/eab12ea0e56c395be4a8dbc3e28cb558a283f23e/public/style.css -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | a { 6 | color: #00b7ff; 7 | } 8 | -------------------------------------------------------------------------------- /public/stylesheets/style.styl: -------------------------------------------------------------------------------- 1 | body 2 | padding: 50px 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif 4 | a 5 | color: #00B7FF -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET home page. 4 | */ 5 | 6 | exports.index = function(req, res){ 7 | res.render('index', { title: 'Express' }); 8 | }; -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Welcome to <%= title %>
10 | 11 | -------------------------------------------------------------------------------- /views/match.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |Bids | 12 |13 | | Offers | 14 |
<%= render(marketData.bids[i]) %> | 20 |21 | | <%= render(marketData.offers[i]) %> | 22 |
ID | 35 |36 | | Price | 37 |38 | | Working Quantity | 39 |40 | | Original Quantity | 41 |42 | | Status | 43 |44 | | 45 | |
<%= orders[i].id %> | 50 |51 | | <%= new Number(orders[i].price).toFixed(2) %> | 52 |53 | | <%= status.workingQuantity %> | 54 |55 | | <%= orders[i].quantity %> | 56 |57 | | <%= status.status %> | 58 |59 | | Cancel | 60 |