From 7301946236f6559f3eba9306825404a374b9440e Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 9 Jun 2019 21:23:48 -0700 Subject: [PATCH] Added and Updated Stock Market tests for the new changes --- package-lock.json | 14 +- package.json | 2 + src/PersonObjects/IPlayer.ts | 1 + src/StockMarket/OrderProcessing.ts | 7 +- src/StockMarket/PlayerInfluencing.ts | 4 +- src/StockMarket/StockMarket.tsx | 10 +- src/StockMarket/StockMarketConstants.ts | 2 +- ...tockMarketTests.js => StockMarketTests.ts} | 410 +++++++++++++++--- 8 files changed, 375 insertions(+), 75 deletions(-) rename test/{StockMarketTests.js => StockMarketTests.ts} (73%) diff --git a/package-lock.json b/package-lock.json index 31729a31c..33b20a464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitburner", - "version": "0.46.2", + "version": "0.47.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -361,6 +361,18 @@ "integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==", "dev": true }, + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, "@types/numeral": { "version": "0.0.25", "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz", diff --git a/package.json b/package.json index c200e5fd4..aff6ee979 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "devDependencies": { "@babel/core": "^7.3.4", "@babel/preset-react": "^7.0.0", + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", "babel-loader": "^8.0.5", "beautify-lint": "^1.0.3", "benchmark": "^2.1.1", diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 09146f514..86da75d01 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -151,6 +151,7 @@ export interface IPlayer { reapplyAllSourceFiles(): void; regenerateHp(amt: number): void; recordMoneySource(amt: number, source: string): void; + setMoney(amt: number): void; startBladeburner(p: object): void; startClass(costMult: number, expMult: number, className: string): void; startCorporation(corpName: string, additionalShares?: number): void; diff --git a/src/StockMarket/OrderProcessing.ts b/src/StockMarket/OrderProcessing.ts index adfe0b967..bd3d8a628 100644 --- a/src/StockMarket/OrderProcessing.ts +++ b/src/StockMarket/OrderProcessing.ts @@ -22,7 +22,7 @@ import { numeralWrapper } from "../ui/numeralFormat"; import { dialogBoxCreate } from "../../utils/DialogBox"; -interface IProcessOrderRefs { +export interface IProcessOrderRefs { rerenderFn: () => void; stockMarket: IStockMarket; symbolToStockMap: IMap; @@ -49,7 +49,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi } let stockOrders = orderBook[stock.symbol]; if (stockOrders == null || !(stockOrders.constructor === Array)) { - console.error(`Invalid Order book for ${stock.symbol} in processOrders()`); + console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`); stockOrders = []; return; } @@ -160,9 +160,6 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) { if (isBuy) { dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + `This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`); - } else { - dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + - `This may be a bug, please report to game developer with details.`); } } } diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index f73006f2a..9d810e0ef 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -9,10 +9,10 @@ import { Company } from "../Company/Company"; import { Server } from "../Server/Server"; // Change in second-order forecast due to hacks/grows -const forecastForecastChangeFromHack = 0.1; +export const forecastForecastChangeFromHack = 0.1; // Change in second-order forecast due to company work -const forecastForecastChangeFromCompanyWork = 0.001; +export const forecastForecastChangeFromCompanyWork = 0.001; /** * Potentially decreases a stock's second-order forecast when its corresponding diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index c8fa589ed..e08cf440d 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -33,7 +33,7 @@ import * as ReactDOM from "react-dom"; export let StockMarket: IStockMarket | IMap = {}; // Maps full stock name -> Stock object export let SymbolToStockMap: IMap = {}; // Maps symbol -> Stock object -export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null) { +export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null): boolean { const tixApi = (workerScript instanceof WorkerScript); if (!(stock instanceof Stock)) { if (tixApi) { @@ -85,7 +85,7 @@ interface ICancelOrderParams { stock?: Stock; type?: OrderTypes; } -export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null) { +export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null): boolean { var tixApi = (workerScript instanceof WorkerScript); if (StockMarket["Orders"] == null) {return false;} if (params.order && params.order instanceof Order) { @@ -263,7 +263,11 @@ export function processStockPrices(numCycles=1) { let otlkMagChange = stock.otlkMag * av; if (stock.otlkMag < 5) { - otlkMagChange *= 10; + if (stock.otlkMag <= 1) { + otlkMagChange = 1; + } else { + otlkMagChange *= 10; + } } stock.cycleForecast(otlkMagChange); stock.cycleForecastForecast(otlkMagChange / 2); diff --git a/src/StockMarket/StockMarketConstants.ts b/src/StockMarket/StockMarketConstants.ts index 789d17e82..9124e8e01 100644 --- a/src/StockMarket/StockMarketConstants.ts +++ b/src/StockMarket/StockMarketConstants.ts @@ -2,4 +2,4 @@ * How many stock market 'ticks' before a 'cycle' is triggered. * A 'tick' is whenver stock prices update */ -export const TicksPerCycle = 70; +export const TicksPerCycle = 75; diff --git a/test/StockMarketTests.js b/test/StockMarketTests.ts similarity index 73% rename from test/StockMarketTests.js rename to test/StockMarketTests.ts index fdf7f07ac..1034b754b 100644 --- a/test/StockMarketTests.js +++ b/test/StockMarketTests.ts @@ -1,19 +1,33 @@ import { CONSTANTS } from "../src/Constants"; import { Player } from "../src/Player"; +import { IMap } from "../src/types"; + +import { Company } from "../src/Company/Company"; +import { Server } from "../src/Server/Server"; + import { buyStock, sellStock, shortStock, sellShort, } from "../src/StockMarket/BuyingAndSelling"; +import { IStockMarket } from "../src/StockMarket/IStockMarket"; import { Order } from "../src/StockMarket/Order"; -import { processOrders } from "../src/StockMarket/OrderProcessing"; +import { + forecastForecastChangeFromCompanyWork, + forecastForecastChangeFromHack, + influenceStockThroughCompanyWork, + influenceStockThroughServerGrow, + influenceStockThroughServerHack, +} from "../src/StockMarket/PlayerInfluencing"; +import { processOrders, IProcessOrderRefs } from "../src/StockMarket/OrderProcessing"; import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock"; import { + cancelOrder, deleteStockMarket, initStockMarket, initSymbolToStockMap, - loadStockMarket, + placeOrder, processStockPrices, StockMarket, SymbolToStockMap, @@ -35,7 +49,7 @@ describe("Stock Market Tests", function() { const commission = CONSTANTS.StockMarketCommission; // Generic Stock object that can be used by each test - let stock; + let stock: Stock; const ctorParams = { b: true, initPrice: 10e3, @@ -59,13 +73,13 @@ describe("Stock Market Tests", function() { describe("Stock Class", function() { describe("constructor", function() { it("should have default parameters", function() { - let defaultStock; + let defaultStock: Stock; function construct() { defaultStock = new Stock(); } expect(construct).to.not.throw(); - expect(defaultStock.name).to.equal(""); + expect(defaultStock!.name).to.equal(""); }); it("should properly initialize props from parameters", function() { @@ -85,7 +99,7 @@ describe("Stock Market Tests", function() { }); it ("should properly initialize props from range-values", function() { - let stock; + let stock: Stock; const params = { b: true, initPrice: { @@ -117,10 +131,10 @@ describe("Stock Market Tests", function() { } 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); + 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() { @@ -365,7 +379,7 @@ describe("Stock Market Tests", function() { describe("StockMarket object", function() { describe("Initialization", function() { // Keeps track of initialized stocks. Contains their symbols - const stocks = []; + const stocks: string[]= []; before(function() { expect(initStockMarket).to.not.throw(); @@ -430,7 +444,7 @@ describe("Stock Market Tests", function() { it("should trigger a price update when it has enough cycles", function() { // Get the initial prices - const initialValues = {}; + const initialValues: IMap = {}; for (const stockName in StockMarket) { const stock = StockMarket[stockName]; if (!(stock instanceof Stock)) { continue; } @@ -451,7 +465,7 @@ describe("Stock Market Tests", function() { if (!(stock instanceof Stock)) { continue; } expect(initialValues[stock.symbol].price).to.not.equal(stock.price); // expect(initialValues[stock.symbol].otlkMag).to.not.equal(stock.otlkMag); - expect(initialValues[stock.symbol]).to.satisfy(function(initValue) { + expect(initialValues[stock.symbol]).to.satisfy(function(initValue: any) { if ((initValue.otlkMag !== stock.otlkMag) || (initValue.b !== stock.b)) { return true; } else { @@ -483,7 +497,7 @@ describe("Stock Market Tests", function() { describe("Transaction Cost Calculator Functions", function() { describe("getBuyTransactionCost()", function() { it("should fail on invalid 'stock' argument", function() { - const res = getBuyTransactionCost({}, 10, PositionTypes.Long); + const res = getBuyTransactionCost({} as Stock, 10, PositionTypes.Long); expect(res).to.equal(null); }); @@ -516,7 +530,7 @@ describe("Stock Market Tests", function() { describe("getSellTransactionGain()", function() { it("should fail on invalid 'stock' argument", function() { - const res = getSellTransactionGain({}, 10, PositionTypes.Long); + const res = getSellTransactionGain({} as Stock, 10, PositionTypes.Long); expect(res).to.equal(null); }); @@ -555,11 +569,11 @@ describe("Stock Market Tests", function() { describe("Forecast Movement Processor Function", function() { // N = 1 is the original forecast - function getNthForecast(origForecast, n) { + function getNthForecast(origForecast: number, n: number) { return origForecast - forecastChangePerPriceMovement * (n - 1); } - function getNthForecastForecast(origForecastForecast, n) { + function getNthForecastForecast(origForecastForecast: number, n: number) { if (stock.otlkMagForecast > 50) { const expected = origForecastForecast - (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); return expected < 50 ? 50 : expected; @@ -576,24 +590,24 @@ describe("Stock Market Tests", function() { it("should do nothing on invalid 'stock' argument", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement({}, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement({} as Stock, mvmtShares); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should do nothing on invalid 'shares' arg", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement(stock, NaN, PositionTypes.Long); + processTransactionForecastMovement(stock, NaN); expect(stock.shareTxUntilMovement).to.equal(oldTracker); - processTransactionForecastMovement(stock, -1, PositionTypes.Long); + processTransactionForecastMovement(stock, -1); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should properly evaluate a LONG transaction that doesn't trigger a forecast movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -601,7 +615,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate a SHORT transaction that doesn't trigger a forecast movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -610,7 +624,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); @@ -620,7 +634,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); @@ -630,7 +644,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -640,9 +654,9 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -652,7 +666,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -662,7 +676,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -672,9 +686,9 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -684,7 +698,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -698,24 +712,24 @@ describe("Stock Market Tests", function() { it("should do nothing on invalid 'stock' argument", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement({}, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement({} as Stock, mvmtShares); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should do nothing on invalid 'shares' arg", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement(stock, NaN, PositionTypes.Long); + processTransactionForecastMovement(stock, NaN); expect(stock.shareTxUntilMovement).to.equal(oldTracker); - processTransactionForecastMovement(stock, -1, PositionTypes.Long); + processTransactionForecastMovement(stock, -1); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -723,7 +737,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -731,7 +745,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that trigger price movements", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -739,7 +753,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that trigger price movements", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -747,7 +761,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -755,9 +769,9 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -765,7 +779,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -773,7 +787,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -781,9 +795,9 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -791,7 +805,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -803,7 +817,7 @@ describe("Stock Market Tests", function() { describe("buyStock()", function() { it("should fail for invalid arguments", function() { - expect(buyStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(buyStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -822,7 +836,11 @@ describe("Stock Market Tests", function() { it("should return true and properly update stock properties for successful transactions", function() { const shares = 1e3; const cost = getBuyTransactionCost(stock, shares, PositionTypes.Long); - Player.setMoney(cost); + if (cost == null) { + expect.fail(); + } + + Player.setMoney(cost!); expect(buyStock(stock, shares, null, suppressDialogOpt)).to.equal(true); expect(stock.playerShares).to.equal(shares); @@ -833,7 +851,7 @@ describe("Stock Market Tests", function() { describe("sellStock()", function() { it("should fail for invalid arguments", function() { - expect(sellStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(sellStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -893,7 +911,7 @@ describe("Stock Market Tests", function() { describe("shortStock()", function() { it("should fail for invalid arguments", function() { - expect(shortStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(shortStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -912,7 +930,11 @@ describe("Stock Market Tests", function() { it("should return true and properly update stock properties for successful transactions", function() { const shares = 1e3; const cost = getBuyTransactionCost(stock, shares, PositionTypes.Short); - Player.setMoney(cost); + if (cost == null) { + expect.fail(); + } + + Player.setMoney(cost!); expect(shortStock(stock, shares, null, suppressDialogOpt)).to.equal(true); expect(stock.playerShortShares).to.equal(shares); @@ -923,7 +945,7 @@ describe("Stock Market Tests", function() { describe("sellShort()", function() { it("should fail for invalid arguments", function() { - expect(sellShort({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(sellShort({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -985,13 +1007,13 @@ describe("Stock Market Tests", function() { describe("Order Class", function() { it("should throw on invalid arguments", function() { function invalid1() { - return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); + return new Order({} as string, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); } function invalid2() { - return new Order("FOO", "z", 0, OrderTypes.LimitBuy, PositionTypes.Short); + return new Order("FOO", "z" as any as number, 0, OrderTypes.LimitBuy, PositionTypes.Short); } function invalid3() { - return new Order("FOO", 1, {}, OrderTypes.LimitBuy, PositionTypes.Short); + return new Order("FOO", 1, {} as number, OrderTypes.LimitBuy, PositionTypes.Short); } function invalid4() { return new Order("FOO", 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short); @@ -1008,19 +1030,281 @@ describe("Stock Market Tests", function() { }); }); - describe("Order Processing", function() { - before(function() { + describe("Order Placing & Processing", function() { + beforeEach(function() { expect(initStockMarket).to.not.throw(); expect(initSymbolToStockMap).to.not.throw(); - }); - - describe() - describe("executeOrder()", function() { + // Create an order book for our mock stock + StockMarket["Orders"][stock.symbol] = []; + }); + + describe("placeOrder()", function() { + it("should return false when it's called with invalid arguments", function() { + const invalid1 = placeOrder({} as Stock, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); + const invalid2 = placeOrder(stock, "foo" as any as number, 2, OrderTypes.LimitBuy, PositionTypes.Long); + const invalid3 = placeOrder(stock, 1, "foo" as any as number, OrderTypes.LimitBuy, PositionTypes.Long); + + expect(invalid1).to.equal(false); + expect(invalid2).to.equal(false); + expect(invalid3).to.equal(false); + + expect(StockMarket["Orders"][stock.symbol]).to.be.empty; + }); + + it("should return true and update the order book for valid arguments", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + expect(res).to.equal(true); + + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + const order = StockMarket["Orders"][stock.symbol][0]; + expect(order).to.be.an.instanceof(Order); + expect(order.stockSymbol).to.equal(ctorParams.symbol); + expect(order.shares).to.equal(1e3); + expect(order.price).to.equal(9e3); + expect(order.type).to.equal(OrderTypes.LimitBuy); + expect(order.pos).to.equal(PositionTypes.Long); + }); + }); + + describe("cancelOrder()", function() { + beforeEach(function() { + StockMarket["Orders"][stock.symbol] = []; + + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + }); + + it("returns true & removes an Order from the order book", function() { + const order = StockMarket["Orders"][stock.symbol][0]; + const res = cancelOrder({ order }); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + }); + + it("should also work when passing in order parameters separately", function() { + const res = cancelOrder({ + stock, + shares: 1e3, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long + }); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + }); + + it("should return false and do nothing when the specified order doesn't exist", function() { + // Same parameters, but its a different object + const order = new Order(stock.symbol, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + const res = cancelOrder({ order }); + expect(res).to.equal(false); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + + const res2 = cancelOrder({ + stock, + shares: 999, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long + }); + expect(res2).to.equal(false); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + }); + }); + + describe("processOrders()", function() { + let processOrdersRefs: IProcessOrderRefs; + + beforeEach(function() { + expect(initStockMarket).to.not.throw(); + expect(initSymbolToStockMap).to.not.throw(); + + StockMarket[stock.name] = stock; + SymbolToStockMap[stock.symbol] = stock; + StockMarket["Orders"][stock.symbol] = []; + + stock.playerShares = 1e3; + stock.playerShortShares = 1e3; + Player.setMoney(100e9); + + processOrdersRefs = { + rerenderFn: () => {}, + stockMarket: StockMarket as IStockMarket, + symbolToStockMap: SymbolToStockMap, + }; + }); + + function checkThatOrderExists(placeOrderRes?: boolean) { + if (typeof placeOrderRes === "boolean") { + expect(placeOrderRes).to.equal(true); + } + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + } + + function checkThatOrderExecuted() { + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + } + + it("should execute LONG Limit Buy orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); + + it("should execute SHORT Limit Buy Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(2e3); + }); + + it("should execute LONG Limit Sell Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitSell, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(0); + }); + + it("should execute SHORT Limit Sell Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitSell, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(0); + }); + + it("should execute LONG Stop Buy Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopBuy, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); + + it("should execute SHORT Stop Buy Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopBuy, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(2e3); + }); + + it("should execute LONG Stop Sell Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopSell, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(0); + }); + + it("should execute SHORT Stop Sell Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopSell, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(0); + }); + + it("should execute immediately if their conditions are satisfied", function() { + placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Long); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); }); }); + // TODO describe("Player Influencing", function() { - // TODO + const server = new Server({ + hostname: "mockserver", + moneyAvailable: 1e6, + organizationName: "MockStock", + }); + + const company = new Company({ + name: "MockStock", + info: "", + companyPositions: {}, + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 1, + }) + + beforeEach(function() { + expect(initStockMarket).to.not.throw(); + expect(initSymbolToStockMap).to.not.throw(); + + StockMarket[stock.name] = stock; + }); + + describe("influenceStockThroughServerHack()", function() { + it("should decrease a stock's second-order forecast when all of its money is hacked", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, server.moneyMax); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast - forecastForecastChangeFromHack); + }); + + it("should not decrease the stock's second-order forecast when no money is stolen", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, 0); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughServerGrow()", function() { + it("should increase a stock's second-order forecast when all of its money is grown", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, server.moneyMax); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + forecastForecastChangeFromHack); + }); + + it("should not increase the stock's second-order forecast when no money is grown", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, 0); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughCompanyWork()", function() { + it("should increase the server's second order forecast", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 1, 1e3); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + forecastForecastChangeFromCompanyWork); + }); + + it("should be affected by performanceMult", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 4, 1e3); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + 4 * forecastForecastChangeFromCompanyWork); + }); + }); }); });