Implemented second-order forecasts for stocks

This commit is contained in:
danielyxie 2019-05-22 17:23:30 -07:00
parent 7035154454
commit 6effda29a9
4 changed files with 85 additions and 16 deletions

@ -221,6 +221,10 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.47.0
*
* Scripts now start/stop instantly
v0.47.0
* Stock Market changes:
** Implemented spread. Stock's now have bid and ask prices at which transactions occur

@ -100,6 +100,12 @@ export class Stock {
*/
otlkMag: number;
/**
* Forecast of outlook magnitude. Essentially a second-order forecast.
* Unlike 'otlkMag', this number is on an absolute scale from 0-100 (rather than 0-50)
*/
otlkMagForecast: number;
/**
* Average price of stocks that the player owns in the LONG position
*/
@ -173,6 +179,7 @@ export class Stock {
this.mv = toNumber(p.mv);
this.b = p.b;
this.otlkMag = p.otlkMag;
this.otlkMagForecast = this.getAbsoluteForecast();
this.cap = getRandomInt(this.price * 1e3, this.price * 25e3);
this.spreadPerc = toNumber(p.spreadPerc);
this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10);
@ -189,11 +196,62 @@ export class Stock {
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
}
/**
* Set the stock to a new price. Also updates the stock's previous price tracker
*/
changePrice(newPrice: number): void {
this.lastPrice = this.price;
this.price = newPrice;
}
/**
* Change the stock's forecast during a stock market 'tick'.
* The way a stock's forecast changes depends on various internal properties,
* but is ultimately determined by RNG
*/
cycleForecast(changeAmt: number=0.1): void {
const increaseChance = this.getForecastIncreaseChance();
if (Math.random() < increaseChance) {
// Forecast increases
if (this.b) {
this.otlkMag += changeAmt;
} else {
this.otlkMag -= changeAmt;
}
} else {
// Forecast decreases
if (this.b) {
this.otlkMag -= changeAmt;
} else {
this.otlkMag += changeAmt;
}
}
this.otlkMag = Math.min(this.otlkMag, 50);
if (this.otlkMag < 0) {
this.otlkMag *= -1;
this.b = !this.b;
}
}
/**
* "Flip" the stock's second-order forecast. This can occur during a
* stock market "cycle" (determined by RNG). It is used to simulate
* RL stock market cycles and introduce volatility
*/
flipForecastForecast(): void {
const diff = this.otlkMagForecast - 50;
this.otlkMagForecast = 50 + (-1 * diff);
}
/**
* Returns the stock's absolute forecast, which is a number between 0-100
*/
getAbsoluteForecast(): number {
return this.b ? 50 + this.otlkMag : 50 - this.otlkMag;
}
/**
* Return the price at which YOUR stock is bought (market ask price). Accounts for spread
*/
@ -208,6 +266,15 @@ export class Stock {
return this.price * (1 - (this.spreadPerc / 100));
}
/**
* Returns the chance (0-1 decimal) that a stock has of having its forecast increase
*/
getForecastIncreaseChance(): number {
const diff = this.otlkMagForecast - this.getAbsoluteForecast();
return (50 + Math.min(Math.max(diff, -45), 45)) / 100;
}
/**
* Serialize the Stock to a JSON save state.
*/

@ -200,9 +200,15 @@ export function stockMarketCycle() {
if (!(stock instanceof Stock)) { continue; }
let thresh = 0.6;
if (stock.b) { thresh = 0.4; }
if (Math.random() < thresh) {
stock.b = !stock.b;
if (stock.otlkMag < 5) { stock.otlkMag += 0.1; }
const roll = Math.random();
if (roll < 0.4) {
stock.flipForecastForecast();
} else if (roll < 0.6) {
stock.otlkMagForecast += 0.5;
stock.otlkMagForecast = Math.min(stock.otlkMagForecast * 1.02, 50);
} else if (roll < 0.8) {
stock.otlkMagForecast -= 0.5;
stock.otlkMagForecast = otlkMagForecast * (1 / 1.02);
}
}
}
@ -264,23 +270,14 @@ export function processStockPrices(numCycles=1) {
}
let otlkMagChange = stock.otlkMag * av;
if (stock.otlkMag <= 0.1) {
if (stock.otlkMag < 1) {
otlkMagChange = 1;
}
if (c < 0.5) {
stock.otlkMag += otlkMagChange;
} else {
stock.otlkMag -= otlkMagChange;
}
if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100
if (stock.otlkMag < 0) {
stock.otlkMag *= -1;
stock.b = !stock.b;
}
stock.cycleForecast(otlkMagChange);
// Shares required for price movement gradually approaches max over time
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement);
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement);
stock.shareTxUntilMovementUp = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement);
stock.shareTxUntilMovementDown = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement);
}
displayStockMarketContent();

@ -27,6 +27,7 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement {
let plusOrMinus = stock.b; // True for "+", false for "-"
if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus }
hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1);
hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
}
let styleMarkup = {