Changed stock market price movements so that upward and downward movements use different trackers. Forecast can no longer be inverted due to price movements. Updated stock market unit tests

This commit is contained in:
danielyxie 2019-05-16 23:55:21 -07:00
parent bd02e724e5
commit 94175877d7
12 changed files with 216 additions and 129 deletions

@ -86,8 +86,11 @@ Sleeve memory dictates what a sleeve's synchronization will be when its reset by
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
switch BitNodes its synchronization will initially be set to 10, rather than 1.
Memory can only be increased by purchasing upgrades from The Covenant.
It is a persistent stat, meaning it never gets reset back to 1.
Memory can only be increased by purchasing upgrades from The Covenant. Just like
the ability to purchase additional sleeves, this is only available in BitNodes-10
and above, and is only available after defeating BitNode-10 at least once.
Memory is a persistent stat, meaning it never gets reset back to 1.
The maximum possible value for a sleeve's memory is 100.
Re-sleeving

@ -21,4 +21,13 @@ hackAnalyzeThreads() Netscript Function
If this function returns 50, this means that if your next `hack()` call
is run on a script with 50 threads, it will steal $1m from the `foodnstuff` server.
**Warning**: The value returned by this function isn't necessarily a whole number.
.. warning:: The value returned by this function isn't necessarily a whole number.
.. warning:: It is possible for this function to return :code:`Infinity` or :code:`NaN` in
certain uncommon scenarios. This is because in JavaScript:
* :code:`0 / 0 = NaN`
* :code:`N / 0 = Infinity` for 0 < N < Infinity.
For example, if a server has no money available and you want to hack some positive
amount from it, then the function would return :code:`Infinity` because
this would be impossible.

@ -279,7 +279,7 @@ export class FactionRoot extends React.Component<IProps, IState> {
{
canPurchaseSleeves &&
<Option
buttonText={"Purchase Duplicate Sleeves"}
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases}
/>

@ -72,7 +72,7 @@ export function purchaseHacknet() {
if (!Player.canAfford(cost)) { return -1; }
Player.loseMoney(cost);
const server = Player.createHacknetServer();
Player.hashManager.updateCapacity(Player.hacknetNodes);
updateHashManagerCapacity();
return numOwned;
} else {
@ -372,6 +372,8 @@ export function purchaseCacheUpgrade(node, levels=1) {
Player.loseMoney(cost);
node.upgradeCache(sanitizedLevels);
return true;
}
// Create/Refresh Hacknet Nodes UI

@ -125,7 +125,7 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) {
// We only execute limit orders until the price fails to match the order condition
const isLong = (order.pos === PositionTypes.Long);
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
const firstShares = Math.min(order.shares, isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown);
// First transaction to trigger movement
let res = (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts));
@ -173,7 +173,7 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) {
if (totalShares === 0) {
return; // Player has no shares
}
const firstShares = Math.min(totalShares, stock.shareTxUntilMovement);
const firstShares = Math.min(totalShares, isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp);
// First transaction to trigger movement
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {

@ -138,8 +138,10 @@ export class Stock {
/**
* How many share transactions remaining until a price movement occurs
* (separately tracked for upward and downward movements)
*/
shareTxUntilMovement: number;
shareTxUntilMovementDown: number;
shareTxUntilMovementUp: number;
/**
* Spread percentage. The bid/ask prices for this stock are N% above or below
@ -160,22 +162,23 @@ export class Stock {
readonly totalShares: number;
constructor(p: IConstructorParams = defaultConstructorParams) {
this.name = p.name;
this.symbol = p.symbol;
this.price = toNumber(p.initPrice);
this.lastPrice = this.price;
this.playerShares = 0;
this.playerAvgPx = 0;
this.playerShortShares = 0;
this.playerAvgShortPx = 0;
this.mv = toNumber(p.mv);
this.b = p.b;
this.otlkMag = p.otlkMag;
this.cap = getRandomInt(this.price * 1e3, this.price * 25e3);
this.spreadPerc = toNumber(p.spreadPerc);
this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10);
this.shareTxForMovement = toNumber(p.shareTxForMovement);
this.shareTxUntilMovement = this.shareTxForMovement;
this.name = p.name;
this.symbol = p.symbol;
this.price = toNumber(p.initPrice);
this.lastPrice = this.price;
this.playerShares = 0;
this.playerAvgPx = 0;
this.playerShortShares = 0;
this.playerAvgShortPx = 0;
this.mv = toNumber(p.mv);
this.b = p.b;
this.otlkMag = p.otlkMag;
this.cap = getRandomInt(this.price * 1e3, this.price * 25e3);
this.spreadPerc = toNumber(p.spreadPerc);
this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10);
this.shareTxForMovement = toNumber(p.shareTxForMovement);
this.shareTxUntilMovementDown = this.shareTxForMovement;
this.shareTxUntilMovementUp = this.shareTxForMovement;
// Total shares is determined by market cap, and is rounded to nearest 100k
let totalSharesUnrounded: number = (p.marketCap / this.price);

@ -279,7 +279,8 @@ export function processStockPrices(numCycles=1) {
}
// Shares required for price movement gradually approaches max over time
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovement + 5, stock.shareTxForMovement);
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement);
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement);
}
displayStockMarketContent();

@ -3,7 +3,7 @@ import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants";
// Amount by which a stock's forecast changes during each price movement
export const forecastChangePerPriceMovement = 0.05;
export const forecastChangePerPriceMovement = 0.1;
/**
* Given a stock, calculates the amount by which the stock price is multiplied
@ -49,21 +49,24 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
const isLong = (posType === PositionTypes.Long);
// If the number of shares doesn't trigger a price movement, its a simple calculation
if (shares <= stock.shareTxUntilMovement) {
if (isLong) {
if (isLong) {
if (shares <= stock.shareTxUntilMovementUp) {
return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission;
} else {
}
} else {
if (shares <= stock.shareTxUntilMovementDown) {
return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission;
}
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
let remainingShares = shares - firstShares;
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);
let totalCost = (firstShares * currPrice);
const increasingMvmt = calculateIncreasingPriceMovement(stock)!;
const decreasingMvmt = calculateDecreasingPriceMovement(stock)!;
@ -111,42 +114,66 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
}
}
// No price/forecast movement
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
// If there's only going to be one iteration
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
if (shares <= firstShares) {
function triggerMovement() {
processPriceMovement();
stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement);
}
if (isLong) {
stock.shareTxUntilMovementUp -= shares;
if (stock.shareTxUntilMovementUp <= 0) {
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
triggerMovement();
}
} else {
stock.shareTxUntilMovementDown -= shares;
if (stock.shareTxUntilMovementDown <= 0) {
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
triggerMovement();
}
}
return;
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let remainingShares = shares - firstShares;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 1; i < numIterations; ++i) {
processPriceMovement();
}
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
// The shareTxUntilMovement ended up at 0 at the end of the "processing"
++numIterations;
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
// If on the offchance we end up perfectly at the next price movement
if (isLong) {
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
// The shareTxUntilMovementUp ended up at 0 at the end of the "processing"
++numIterations;
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
processPriceMovement();
}
} else {
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
// The shareTxUntilMovementDown ended up at 0 at the end of the "processing"
++numIterations;
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
processPriceMovement();
}
}
stock.changePrice(currPrice);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange;
if (stock.otlkMag < 0) {
stock.b = !stock.b;
stock.otlkMag = Math.abs(stock.otlkMag);
if (stock.otlkMag < 0.1) {
stock.otlkMag = 0.1;
}
}
@ -166,9 +193,10 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
shares = Math.min(shares, stock.maxShares);
const isLong = (posType === PositionTypes.Long);
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
// If the number of shares doesn't trigger a price mvoement, its a simple calculation
if (shares <= stock.shareTxUntilMovement) {
if (shares <= firstShares) {
if (isLong) {
return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission;
} else {
@ -181,7 +209,7 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let remainingShares = shares - firstShares;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
// Helper function to calculate gain for a single iteration
@ -198,7 +226,7 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
// The initial cost calculation takes care of the first "iteration"
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
let totalGain = calculateGain(currPrice, stock.shareTxUntilMovement);
let totalGain = calculateGain(currPrice, firstShares);
for (let i = 1; i < numIterations; ++i) {
// Price movement
if (isLong) {
@ -229,6 +257,7 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
shares = Math.min(shares, stock.maxShares);
const isLong = (posType === PositionTypes.Long);
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
let currPrice = stock.price;
function processPriceMovement() {
@ -239,41 +268,64 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
}
}
// No price/forecast movement
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
// If there's only going to be one iteration at most
if (shares <= firstShares) {
function triggerPriceMovement() {
processPriceMovement();
stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement);
}
if (isLong) {
stock.shareTxUntilMovementDown -= shares;
if (stock.shareTxUntilMovementDown <= 0) {
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
triggerPriceMovement();
}
} else {
stock.shareTxUntilMovementUp -= shares;
if (stock.shareTxUntilMovementUp <= 0) {
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
triggerPriceMovement();
}
}
return;
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let remainingShares = shares - firstShares;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 1; i < numIterations; ++i) {
processPriceMovement();
}
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
++numIterations;
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
// If on the offchance we end up perfectly at the next price movement
if (isLong) {
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
++numIterations;
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
processPriceMovement();
}
} else {
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
++numIterations;
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
processPriceMovement();
}
}
stock.changePrice(currPrice);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange;
if (stock.otlkMag < 0) {
stock.b = !stock.b;
stock.otlkMag = Math.abs(stock.otlkMag);
if (stock.otlkMag < 0.1) {
stock.otlkMag = 0.1;
}
}
@ -299,14 +351,15 @@ export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, mone
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
// No price movement
const firstIterationCost = stock.shareTxUntilMovement * currPrice;
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
const firstIterationCost = firstShares * currPrice;
if (remainingMoney < firstIterationCost) {
return Math.floor(remainingMoney / currPrice);
}
// We'll avoid any accidental infinite loops by having a hardcoded maximum number of
// iterations
let numShares = stock.shareTxUntilMovement;
let numShares = firstShares;
remainingMoney -= firstIterationCost;
for (let i = 0; i < 10e3; ++i) {
if (isLong) {

@ -48,6 +48,7 @@ type IProps = {
orders: Order[];
p: IPlayer;
placeOrder: placeOrderFn;
rerenderAllTickers: () => void;
sellStockLong: txFn;
sellStockShort: txFn;
stock: Stock;
@ -57,7 +58,6 @@ type IState = {
orderType: SelectorOrderType;
position: PositionTypes;
qty: string;
rerenderFlag: boolean;
}
export class StockTicker extends React.Component<IProps, IState> {
@ -68,7 +68,6 @@ export class StockTicker extends React.Component<IProps, IState> {
orderType: SelectorOrderType.Market,
position: PositionTypes.Long,
qty: "",
rerenderFlag: false,
}
this.getBuyTransactionCostText = this.getBuyTransactionCostText.bind(this);
@ -81,7 +80,6 @@ export class StockTicker extends React.Component<IProps, IState> {
this.handleQuantityChange = this.handleQuantityChange.bind(this);
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
this.rerender = this.rerender.bind(this);
}
createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void) {
@ -119,7 +117,8 @@ export class StockTicker extends React.Component<IProps, IState> {
let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` +
`will cost ${numeralWrapper.formatMoney(cost)}. `;
const causesMovement = qty > stock.shareTxUntilMovement;
const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
const causesMovement = qty > amtNeededForMovement;
if (causesMovement) {
costTxt += `WARNING: Purchasing this many shares will influence the stock price`;
}
@ -152,7 +151,8 @@ export class StockTicker extends React.Component<IProps, IState> {
let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` +
`will result in a gain of ${numeralWrapper.formatMoney(cost)}. `;
const causesMovement = qty > stock.shareTxUntilMovement;
const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
const causesMovement = qty > amtNeededForMovement;
if (causesMovement) {
costTxt += `WARNING: Selling this many shares will influence the stock price`;
}
@ -174,7 +174,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.buyStockLong(this.props.stock, shares);
}
this.rerender();
this.props.rerenderAllTickers();
break;
}
case SelectorOrderType.Limit: {
@ -216,7 +216,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.buyStockLong(stock, maxShares);
}
this.rerender();
this.props.rerenderAllTickers();
break;
}
default: {
@ -297,7 +297,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.sellStockLong(this.props.stock, shares);
}
this.rerender();
this.props.rerenderAllTickers();
break;
}
case SelectorOrderType.Limit: {
@ -335,7 +335,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.sellStockLong(stock, stock.playerShares);
}
this.rerender();
this.props.rerenderAllTickers();
break;
}
default: {
@ -355,20 +355,12 @@ export class StockTicker extends React.Component<IProps, IState> {
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2));
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
// Determine if the player's intended transaction will cause a price movement
let causesMovement: boolean = false;
const qty = this.getQuantity();
if (!isNaN(qty)) {
causesMovement = qty > this.props.stock.shareTxUntilMovement;
causesMovement = qty > this.props.stock.shareTxForMovement;
}
return (
@ -411,7 +403,7 @@ export class StockTicker extends React.Component<IProps, IState> {
{
causesMovement &&
<p className="stock-market-price-movement-warning">
WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares will affect
WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares may affect
the stock's price. This applies during the transaction itself as well. See Investopedia
for more details.
</p>

@ -53,6 +53,7 @@ export class StockTickers extends React.Component<IProps, IState> {
this.changeWatchlistFilter = this.changeWatchlistFilter.bind(this);
this.collapseAllTickers = this.collapseAllTickers.bind(this);
this.expandAllTickers = this.expandAllTickers.bind(this);
this.rerender = this.rerender.bind(this);
this.listRef = React.createRef();
}
@ -159,6 +160,7 @@ export class StockTickers extends React.Component<IProps, IState> {
orders={orders}
p={this.props.p}
placeOrder={this.props.placeOrder}
rerenderAllTickers={this.rerender}
sellStockLong={this.props.sellStockLong}
sellStockShort={this.props.sellStockShort}
stock={val}

@ -52,16 +52,17 @@ export function displayCharacterInfo(elem: HTMLElement, p: IPlayer) {
function convertMoneySourceTrackerToString(src: MoneySourceTracker): string {
let parts: string[] = [`Total: ${numeralWrapper.formatMoney(src.total)}`];
if (src.bladeburner) { parts.push(`Bladeburner: ${numeralWrapper.formatMoney(src.bladeburner)}`) };
if (src.codingcontract) { parts.push(`Coding Contracts: ${numeralWrapper.formatMoney(src.codingcontract)}`) };
if (src.work) { parts.push(`Company Work: ${numeralWrapper.formatMoney(src.work)}`) };
if (src.corporation) { parts.push(`Corporation: ${numeralWrapper.formatMoney(src.corporation)}`) };
if (src.crime) { parts.push(`Crimes: ${numeralWrapper.formatMoney(src.crime)}`) };
if (src.gang) { parts.push(`Gang: ${numeralWrapper.formatMoney(src.gang)}`) };
if (src.hacking) { parts.push(`Hacking: ${numeralWrapper.formatMoney(src.hacking)}`) };
if (src.hacknetnode) { parts.push(`Hacknet Nodes: ${numeralWrapper.formatMoney(src.hacknetnode)}`) };
if (src.infiltration) { parts.push(`Infiltration: ${numeralWrapper.formatMoney(src.infiltration)}`) };
if (src.stock) { parts.push(`Stock Market: ${numeralWrapper.formatMoney(src.stock)}`) };
if (src.bladeburner) { parts.push(`Bladeburner: ${numeralWrapper.formatMoney(src.bladeburner)}`) };
if (src.codingcontract) { parts.push(`Coding Contracts: ${numeralWrapper.formatMoney(src.codingcontract)}`) };
if (src.work) { parts.push(`Company Work: ${numeralWrapper.formatMoney(src.work)}`) };
if (src.corporation) { parts.push(`Corporation: ${numeralWrapper.formatMoney(src.corporation)}`) };
if (src.crime) { parts.push(`Crimes: ${numeralWrapper.formatMoney(src.crime)}`) };
if (src.gang) { parts.push(`Gang: ${numeralWrapper.formatMoney(src.gang)}`) };
if (src.hacking) { parts.push(`Hacking: ${numeralWrapper.formatMoney(src.hacking)}`) };
if (src.hacknetnode) { parts.push(`Hacknet Nodes: ${numeralWrapper.formatMoney(src.hacknetnode)}`) };
if (src.hospitalization) { parts.push(`Hospitalization: ${numeralWrapper.formatMoney(src.hospitalization)}`) };
if (src.infiltration) { parts.push(`Infiltration: ${numeralWrapper.formatMoney(src.infiltration)}`) };
if (src.stock) { parts.push(`Stock Market: ${numeralWrapper.formatMoney(src.stock)}`) };
return parts.join("<br>");
}

@ -79,7 +79,8 @@ describe("Stock Market Tests", function() {
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.shareTxUntilMovementUp).to.equal(ctorParams.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).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");
@ -436,24 +437,24 @@ describe("Stock Market Tests", function() {
it("should do nothing on invalid 'stock' argument", function() {
const oldPrice = stock.price;
const oldTracker = stock.shareTxUntilMovement;
const oldTracker = stock.shareTxUntilMovementUp;
processBuyTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
});
it("should do nothing on invalid 'shares' arg", function() {
const oldPrice = stock.price;
const oldTracker = stock.shareTxUntilMovement;
const oldTracker = stock.shareTxUntilMovementUp;
processBuyTransactionPriceMovement(stock, NaN, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
processBuyTransactionPriceMovement(stock, -1, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
});
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
@ -463,7 +464,8 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
@ -473,7 +475,8 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that trigger price movements", function() {
@ -483,7 +486,7 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that trigger price movements", function() {
@ -493,7 +496,7 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
@ -503,7 +506,8 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
@ -511,10 +515,11 @@ describe("Stock Market Tests", function() {
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovementUp, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
@ -524,7 +529,8 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
@ -534,7 +540,8 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
@ -542,10 +549,11 @@ describe("Stock Market Tests", function() {
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.shareTxUntilMovementDown).to.be.below(stock.shareTxForMovement);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovementDown, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
@ -555,7 +563,7 @@ describe("Stock Market Tests", function() {
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
});
@ -565,24 +573,24 @@ describe("Stock Market Tests", function() {
it("should do nothing on invalid 'stock' argument", function() {
const oldPrice = stock.price;
const oldTracker = stock.shareTxUntilMovement;
const oldTracker = stock.shareTxUntilMovementDown;
processSellTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
});
it("should do nothing on invalid 'shares' arg", function() {
const oldPrice = stock.price;
const oldTracker = stock.shareTxUntilMovement;
const oldTracker = stock.shareTxUntilMovementDown;
processSellTransactionPriceMovement(stock, NaN, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
processSellTransactionPriceMovement(stock, -1, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
});
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
@ -592,7 +600,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
@ -602,7 +611,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that trigger price movements", function() {
@ -612,7 +622,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that trigger price movements", function() {
@ -622,7 +633,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
@ -632,7 +644,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
@ -640,10 +653,12 @@ describe("Stock Market Tests", function() {
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
expect(stock.shareTxUntilMovementDown).to.be.below(stock.shareTxForMovement);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovementDown, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
@ -653,7 +668,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
@ -663,7 +679,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
@ -671,10 +688,13 @@ describe("Stock Market Tests", function() {
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.shareTxUntilMovementUp).to.be.below(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovementUp, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
@ -684,7 +704,8 @@ describe("Stock Market Tests", function() {
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
});
});
});