diff --git a/orderbook.js b/orderbook.js new file mode 100644 index 0000000..4fcdb6e --- /dev/null +++ b/orderbook.js @@ -0,0 +1,158 @@ +describe('15', () => { + describe('reconcileOrder', () => { + it('adds an order to the book when the book is empty and thus cannot fulfill the order', () => { + const existingBook = [] + const incomingOrder = { type: 'sell', quantity: 10, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 10, price: 6150 }]) + }) + + it('adds an order to the book when the book has orders of the corresponding type (i.e. a sell with no buys)', () => { + const existingBook = [{ type: 'sell', quantity: 10, price: 6150 }] + const incomingOrder = { type: 'sell', quantity: 12, price: 6000 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([ + { type: 'sell', quantity: 10, price: 6150 }, + { type: 'sell', quantity: 12, price: 6000 } + ]) + }) + + it('adds an order to the book when the book has a corresponding order type but it does not match', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6000 }] + const incomingOrder = { type: 'sell', quantity: 12, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([ + { type: 'buy', quantity: 10, price: 6000 }, + { type: 'sell', quantity: 12, price: 6150 } + ]) + }) + + it('fulfills an order and removes the matching order when the book contains a matching order of the same quantity', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6150 }, { type: 'sell', quantity: 12, price: 6250 }] + const incomingOrder = { type: 'sell', quantity: 10, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 6250 }]) + }) + + it('fulfills an order and reduces the matching order when the book contains a matching order of a larger quantity', () => { + const existingBook = [{ type: 'buy', quantity: 15, price: 6150 }, { type: 'sell', quantity: 12, price: 6950 }] + const incomingOrder = { type: 'sell', quantity: 10, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 6950 }, { type: 'buy', quantity: 5, price: 6150 }]) + }) + + it('partially fulfills an order, removes the matching order and adds the remainder of the order to the book when the book contains a matching order of a smaller quantity', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6150 }, { type: 'sell', quantity: 12, price: 5950 }] + const incomingOrder = { type: 'sell', quantity: 15, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 5950 }, { type: 'sell', quantity: 5, price: 6150 }]) + }) + + it('uses two existing orders to completely fulfill an order, removing the matching orders from the book', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6150 }, { type: 'buy', quantity: 5, price: 6150 }, { type: 'sell', quantity: 12, price: 5950 }] + const incomingOrder = { type: 'sell', quantity: 15, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 5950 }]) + }) + + it('uses two existing orders to completely fulfill an order, removing the first matching order from the book and reducing the second', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6150 }, { type: 'buy', quantity: 10, price: 6150 }, { type: 'sell', quantity: 12, price: 6950 }] + const incomingOrder = { type: 'sell', quantity: 15, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 6950 }, { type: 'buy', quantity: 5, price: 6150 }]) + }) + + it('uses two existing orders to partially fulfill an order, removing the matching orders from the book and reducing the incoming order before adding it to the book', () => { + const existingBook = [{ type: 'buy', quantity: 10, price: 6150 }, { type: 'buy', quantity: 10, price: 6150 }, { type: 'sell', quantity: 12, price: 6950 }] + const incomingOrder = { type: 'sell', quantity: 25, price: 6150 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 6950 }, { type: 'sell', quantity: 5, price: 6150 }]) + }) + + it.skip('Extra Credit: it fulfills a mismatched order when both parties benefit', () => { + const existingBook = [{ type: 'buy', quantity: 15, price: 6000 }, { type: 'sell', quantity: 12, price: 6950 }] + const incomingOrder = { type: 'sell', quantity: 15, price: 5900 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([{ type: 'sell', quantity: 12, price: 6950 }]) + }) + + it.skip('Extra Credit: it does not fulfill a mismatched order when it does not benefit both parties', () => { + const existingBook = [{ type: 'buy', quantity: 15, price: 5900 }, { type: 'sell', quantity: 12, price: 6950 }] + const incomingOrder = { type: 'sell', quantity: 15, price: 6000 } + + const updatedBook = reconcileOrder(existingBook, incomingOrder) + + expect(updatedBook).to.deep.equal([ + { type: 'buy', quantity: 15, price: 5900 }, + { type: 'sell', quantity: 12, price: 6950 }, + { type: 'sell', quantity: 15, price: 6000 }, + ]) + }) + }) + }) + + function typeFilter(exisitingBook, type) { + return existingBook.filter(item => item.type === type); + } + function reconcileOrder(existingBook, incomingOrder) { + + + let match = false; + let amount; + let i = 0; + for (; i < existingBook.length && !match; i++) { + if (existingBook[i].type != incomingOrder.type) { + if (existingBook[i].price === incomingOrder.price) { + match = true; + amount = (existingBook[i].quantity - incomingOrder.quantity); + } + } + if (match) { + break; + } + } + + if (!match) { + existingBook.push(incomingOrder); + } else { + const matched = existingBook[i]; + console.log(matched) + matched.quantity -= amount; + console.log(matched) + + //remove the order from it's location + existingBook.splice(i, 1); + + if (matched.quantity > 0) { + existingBook.push(matched); + } + } + + + + + return existingBook; + } + + module.exports = reconcileOrder; + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 74a2b24..cd33cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,9 +116,9 @@ "dev": true }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, "brace-expansion": { @@ -365,22 +365,22 @@ "dev": true }, "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -794,9 +794,9 @@ "dev": true }, "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", "dev": true }, "is-date-object": { @@ -839,12 +839,12 @@ "dev": true }, "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "has": "^1.0.3" + "has-symbols": "^1.0.1" } }, "is-symbol": { @@ -911,9 +911,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "log-symbols": { @@ -958,9 +958,9 @@ } }, "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -1084,9 +1084,9 @@ "dev": true }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, "object-keys": { @@ -1402,28 +1402,6 @@ "es-abstract": "^1.17.5" } }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, "string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", diff --git a/package.json b/package.json index a3ac999..e0d146b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,6 @@ "devDependencies": { "chai": "^4.2.0", "eslint": "^6.8.0", - "mocha": "^7.1.2" + "mocha": "^7.2.0" } }