mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-18 13:43:49 +01:00
Added unit tests for Stock Market. Removed dependencies from package.json, since they're sourced directly in tests/index.html
This commit is contained in:
parent
87b4698d5b
commit
3a601a015d
121
package-lock.json
generated
121
package-lock.json
generated
@ -928,12 +928,6 @@
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||
"dev": true
|
||||
},
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"assign-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||
@ -1319,12 +1313,6 @@
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
|
||||
"dev": true
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
|
||||
"dev": true
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
@ -1626,29 +1614,6 @@
|
||||
"lazy-cache": "1.0.4"
|
||||
}
|
||||
},
|
||||
"chai": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
|
||||
"integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assertion-error": "1.1.0",
|
||||
"check-error": "1.0.2",
|
||||
"deep-eql": "3.0.1",
|
||||
"get-func-name": "2.0.0",
|
||||
"pathval": "1.1.0",
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"chai-as-promised": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
|
||||
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"check-error": "1.0.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
@ -1700,12 +1665,6 @@
|
||||
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
|
||||
"dev": true
|
||||
},
|
||||
"check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
|
||||
@ -2870,15 +2829,6 @@
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
||||
"dev": true
|
||||
},
|
||||
"deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
@ -4440,12 +4390,6 @@
|
||||
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
|
||||
"dev": true
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"dev": true
|
||||
},
|
||||
"get-stdin": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
|
||||
@ -4654,12 +4598,6 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
},
|
||||
"growl": {
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
|
||||
"dev": true
|
||||
},
|
||||
"handle-thing": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz",
|
||||
@ -6852,59 +6790,6 @@
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
|
||||
"integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-stdout": "1.3.1",
|
||||
"commander": "2.15.1",
|
||||
"debug": "3.1.0",
|
||||
"diff": "3.5.0",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"glob": "7.1.2",
|
||||
"growl": "1.10.5",
|
||||
"he": "1.1.1",
|
||||
"minimatch": "3.0.4",
|
||||
"mkdirp": "0.5.1",
|
||||
"supports-color": "5.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "1.0.0",
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mocha-lcov-reporter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz",
|
||||
"integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=",
|
||||
"dev": true
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@ -7734,12 +7619,6 @@
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
|
||||
"dev": true
|
||||
},
|
||||
"pathval": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
||||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
||||
"dev": true
|
||||
},
|
||||
"pbkdf2": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
|
||||
|
@ -48,8 +48,6 @@
|
||||
"beautify-lint": "^1.0.3",
|
||||
"benchmark": "^2.1.1",
|
||||
"bundle-loader": "~0.5.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"css-loader": "^0.28.11",
|
||||
"es6-promise-polyfill": "^1.1.1",
|
||||
"eslint": "^4.19.1",
|
||||
@ -65,8 +63,6 @@
|
||||
"lodash": "^4.17.10",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-lcov-reporter": "^1.0.0",
|
||||
"node-sass": "^4.10.0",
|
||||
"raw-loader": "~0.5.0",
|
||||
"sass-loader": "^7.0.3",
|
||||
|
@ -2,6 +2,33 @@ import { Stock } from "./Stock";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for an 'upward' price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateIncreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for a "downward" price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateDecreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the total cost of a "buy" transaction. This accounts for spread,
|
||||
* price movements, and commission.
|
||||
@ -32,20 +59,26 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
let totalCost = (stock.shareTxUntilMovement * currPrice);
|
||||
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalCost += (amt * currPrice);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return totalCost + CONSTANTS.StockMarketCommission;
|
||||
@ -76,20 +109,19 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
let currPrice = stock.price;
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
}
|
||||
|
||||
stock.price = currPrice;
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
||||
}
|
||||
@ -124,7 +156,7 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to accoutn for
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
@ -144,16 +176,16 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
let totalGain = calculateGain(currPrice, stock.shareTxUntilMovement);
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalGain += calculateGain(currPrice, amt);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return totalGain - CONSTANTS.StockMarketCommission;
|
||||
@ -183,17 +215,13 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
let currPrice = stock.price;
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
432
tests/StockMarketTests.js
Normal file
432
tests/StockMarketTests.js
Normal file
@ -0,0 +1,432 @@
|
||||
import { CONSTANTS } from "../src/Constants";
|
||||
import { Stock } from "../src/StockMarket/Stock";
|
||||
import {
|
||||
calculateIncreasingPriceMovement,
|
||||
calculateDecreasingPriceMovement,
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement,
|
||||
} from "../src/StockMarket/StockMarketHelpers";
|
||||
import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
|
||||
|
||||
const assert = chai.assert;
|
||||
const expect = chai.expect;
|
||||
|
||||
console.log("Beginning Stock Market Tests");
|
||||
|
||||
describe("Stock Market Tests", function() {
|
||||
const commission = CONSTANTS.StockMarketCommission;
|
||||
|
||||
// Generic Stock object that can be used by each test
|
||||
let stock;
|
||||
const ctorParams = {
|
||||
b: true,
|
||||
initPrice: 10e3,
|
||||
marketCap: 5e9,
|
||||
mv: 1,
|
||||
name: "MockStock",
|
||||
otlkMag: 10,
|
||||
spreadPerc: 1,
|
||||
shareTxForMovement: 5e3,
|
||||
symbol: "mock",
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
function construct() {
|
||||
stock = new Stock(ctorParams);
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
});
|
||||
|
||||
describe("Stock Class", function() {
|
||||
describe("constructor", function() {
|
||||
it("should have default parameters", function() {
|
||||
let defaultStock;
|
||||
function construct() {
|
||||
defaultStock = new Stock();
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
expect(defaultStock.name).to.equal("");
|
||||
});
|
||||
|
||||
it("should properly initialize props from parameters", function() {
|
||||
expect(stock.name).to.equal(ctorParams.name);
|
||||
expect(stock.symbol).to.equal(ctorParams.symbol);
|
||||
expect(stock.price).to.equal(ctorParams.initPrice);
|
||||
expect(stock.lastPrice).to.equal(ctorParams.initPrice);
|
||||
expect(stock.b).to.equal(ctorParams.b);
|
||||
expect(stock.mv).to.equal(ctorParams.mv);
|
||||
expect(stock.shareTxForMovement).to.equal(ctorParams.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovement).to.equal(ctorParams.shareTxForMovement);
|
||||
expect(stock.maxShares).to.be.below(stock.totalShares);
|
||||
expect(stock.spreadPerc).to.equal(ctorParams.spreadPerc);
|
||||
expect(stock.priceMovementPerc).to.be.a("number");
|
||||
expect(stock.priceMovementPerc).to.be.at.most(stock.spreadPerc);
|
||||
expect(stock.priceMovementPerc).to.be.at.least(0);
|
||||
});
|
||||
|
||||
it ("should properly initialize props from range-values", function() {
|
||||
let stock;
|
||||
const params = {
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 10e3,
|
||||
min: 1e3,
|
||||
},
|
||||
marketCap: 5e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 150,
|
||||
min: 50,
|
||||
},
|
||||
name: "MockStock",
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: "mock",
|
||||
};
|
||||
|
||||
function construct() {
|
||||
stock = new Stock(params);
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
expect(stock.price).to.be.within(params.initPrice.min, params.initPrice.max);
|
||||
expect(stock.mv).to.be.within(params.mv.min / params.mv.divisor, params.mv.max / params.mv.divisor);
|
||||
expect(stock.spreadPerc).to.be.within(params.spreadPerc.min / params.spreadPerc.divisor, params.spreadPerc.max / params.spreadPerc.divisor);
|
||||
expect(stock.shareTxForMovement).to.be.within(params.shareTxForMovement.min, params.shareTxForMovement.max);
|
||||
});
|
||||
|
||||
it("should round the 'totalShare' prop to the nearest 100k", function() {
|
||||
expect(stock.totalShares % 100e3).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#changePrice()", function() {
|
||||
it("should set both the last price and current price properties", function() {
|
||||
const newPrice = 20e3;
|
||||
stock.changePrice(newPrice);
|
||||
expect(stock.lastPrice).to.equal(ctorParams.initPrice);
|
||||
expect(stock.price).to.equal(newPrice);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getAskPrice()", function() {
|
||||
it("should return the price increased by spread percentage", function() {
|
||||
const perc = stock.spreadPerc / 100;
|
||||
expect(perc).to.be.at.most(1);
|
||||
expect(perc).to.be.at.least(0);
|
||||
|
||||
const expected = stock.price * (1 + perc);
|
||||
expect(stock.getAskPrice()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getBidPrice()", function() {
|
||||
it("should return the price decreased by spread percentage", function() {
|
||||
const perc = stock.spreadPerc / 100;
|
||||
expect(perc).to.be.at.most(1);
|
||||
expect(perc).to.be.at.least(0);
|
||||
|
||||
const expected = stock.price * (1 - perc);
|
||||
expect(stock.getBidPrice()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transaction Cost Calculator Functions", function() {
|
||||
describe("getBuyTransactionCost()", function() {
|
||||
it("should fail on invalid 'stock' argument", function() {
|
||||
const res = getBuyTransactionCost({}, 10, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should fail on invalid 'shares' arg", function() {
|
||||
let res = getBuyTransactionCost(stock, NaN, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
|
||||
res = getBuyTransactionCost(stock, -1, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
expect(res).to.equal(shares * stock.getAskPrice() + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
expect(res).to.equal(shares * stock.getBidPrice() + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
|
||||
// Calculate expected cost
|
||||
const secondPrice = stock.getAskPrice() * calculateIncreasingPriceMovement(stock);
|
||||
const thirdPrice = secondPrice * calculateIncreasingPriceMovement(stock);
|
||||
let expected = (sharesPerMvmt * stock.getAskPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
|
||||
// Calculate expected cost
|
||||
const secondPrice = stock.getBidPrice() * calculateDecreasingPriceMovement(stock);
|
||||
const thirdPrice = secondPrice * calculateDecreasingPriceMovement(stock);
|
||||
let expected = (sharesPerMvmt * stock.getBidPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected + commission);
|
||||
});
|
||||
|
||||
it("should cap the 'shares' argument at the stock's maximum number of shares", function() {
|
||||
const maxRes = getBuyTransactionCost(stock, stock.maxShares, PositionTypes.Long);
|
||||
const exceedRes = getBuyTransactionCost(stock, stock.maxShares * 10, PositionTypes.Long);
|
||||
expect(maxRes).to.equal(exceedRes);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSellTransactionGain()", function() {
|
||||
it("should fail on invalid 'stock' argument", function() {
|
||||
const res = getSellTransactionGain({}, 10, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should fail on invalid 'shares' arg", function() {
|
||||
let res = getSellTransactionGain(stock, NaN, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
|
||||
res = getSellTransactionGain(stock, -1, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
const expected = shares * stock.getBidPrice() - commission;
|
||||
expect(res).to.equal(expected);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
// We need to set this property in order to calculate gains from short position
|
||||
stock.playerAvgShortPx = stock.price * 2;
|
||||
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
const expected = (shares * stock.playerAvgShortPx) + (shares * (stock.playerAvgShortPx - stock.getAskPrice())) - commission;
|
||||
expect(res).to.equal(expected);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
|
||||
// Calculated expected gain
|
||||
const mvmt = calculateDecreasingPriceMovement(stock);
|
||||
const secondPrice = stock.getBidPrice() * mvmt;
|
||||
const thirdPrice = secondPrice * mvmt;
|
||||
const expected = (sharesPerMvmt * stock.getBidPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected - commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
// We need to set this property in order to calculate gains from short position
|
||||
stock.playerAvgShortPx = stock.price * 2;
|
||||
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
|
||||
// Calculate expected gain
|
||||
const mvmt = calculateIncreasingPriceMovement(stock);
|
||||
const secondPrice = stock.getAskPrice() * mvmt;
|
||||
const thirdPrice = secondPrice * mvmt;
|
||||
function getGainForPrice(thisPrice) {
|
||||
const origCost = sharesPerMvmt * stock.playerAvgShortPx;
|
||||
return origCost + ((stock.playerAvgShortPx - thisPrice) * sharesPerMvmt);
|
||||
}
|
||||
const expected = getGainForPrice(stock.getAskPrice()) + getGainForPrice(secondPrice) + getGainForPrice(thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected - commission);
|
||||
});
|
||||
|
||||
it("should cap the 'shares' argument at the stock's maximum number of shares", function() {
|
||||
const maxRes = getSellTransactionGain(stock, stock.maxShares, PositionTypes.Long);
|
||||
const exceedRes = getSellTransactionGain(stock, stock.maxShares * 10, PositionTypes.Long);
|
||||
expect(maxRes).to.equal(exceedRes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Price Movement Processor Functions", function() {
|
||||
describe("processBuyTransactionPriceMovement()", function() {
|
||||
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
||||
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
||||
|
||||
it("should do nothing on invalid 'stock' argument", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovement;
|
||||
|
||||
processBuyTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should do nothing on invalid 'shares' arg", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovement;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, NaN, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
|
||||
processBuyTransactionPriceMovement(stock, -1, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
function getNthPrice(n) {
|
||||
let price = oldPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateIncreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPrice(4));
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
function getNthPrice(n) {
|
||||
let price = oldPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateDecreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPrice(4));
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processSellTransactionPriceMovement()", function() {
|
||||
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
||||
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
||||
|
||||
it("should do nothing on invalid 'stock' argument", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovement;
|
||||
|
||||
processSellTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should do nothing on invalid 'shares' arg", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovement;
|
||||
|
||||
processSellTransactionPriceMovement(stock, NaN, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
|
||||
processSellTransactionPriceMovement(stock, -1, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
|
||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
|
||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
function getNthPrice(n) {
|
||||
let price = oldPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateDecreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPrice(4));
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
function getNthPrice(n) {
|
||||
let price = oldPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateIncreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPrice(4));
|
||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -3,17 +3,20 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mocha Tests</title>
|
||||
<link href="https://unpkg.com/mocha@4.0.1/mocha.css" rel="stylesheet" />
|
||||
<link href="https://unpkg.com/mocha@6.1.4/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script src="https://unpkg.com/mocha@4.0.1/mocha.js"></script>
|
||||
<script src="https://unpkg.com/chai/chai.js"></script>
|
||||
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
||||
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script type="module" src="tests.bundle.js"></script>
|
||||
<script type="module">
|
||||
<script class="mocha-init">
|
||||
mocha.setup('bdd');
|
||||
mocha.checkLeaks();
|
||||
</script>
|
||||
<script type="module" src="tests.bundle.js"></script>
|
||||
<script class="mocha-exec" type="module">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
@ -1,3 +1 @@
|
||||
//require("babel-core/register");
|
||||
//require("babel-polyfill");
|
||||
module.exports = require("./NetscriptJSTest.js");
|
||||
export * from "./StockMarketTests";
|
||||
|
Loading…
Reference in New Issue
Block a user