More rebalancing for stock market changes. Transactions now affect second-order forecast (very slightly)

This commit is contained in:
danielyxie 2019-06-09 15:12:33 -07:00
parent 00f8c0a51f
commit a15041da75
7 changed files with 134 additions and 21 deletions

@ -96,7 +96,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
/**
* Execute a Stop or Limit Order.
* @param {Order} order - Order being executed
* @param {IStockMarket} stockMarket - Reference to StockMarket object
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
*/
function executeOrder(order: Order, refs: IProcessOrderRefs) {
const stock = refs.symbolToStockMap[order.stockSymbol];

@ -6,6 +6,8 @@ import {
} from "../../utils/JSONReviver";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
export const StockForecastInfluenceLimit = 5;
export interface IConstructorParams {
b: boolean;
initPrice: number | IMinMaxRange;
@ -290,6 +292,32 @@ export class Stock {
return (50 + Math.min(Math.max(diff, -45), 45)) / 100;
}
/**
* Changes a stock's forecast. This is used when the stock is influenced
* by a transaction. The stock's forecast always goes towards 50, but the
* movement is capped by a certain threshold/limit
*/
influenceForecast(change: number): void {
if (this.otlkMag > StockForecastInfluenceLimit) {
this.otlkMag = Math.max(StockForecastInfluenceLimit, this.otlkMag - change);
}
}
/**
* Changes a stock's second-order forecast. This is used when the stock is
* influenced by a transaction. The stock's second-order forecast always
* goes towards 50.
*/
influenceForecastForecast(change: number): void {
if (this.otlkMagForecast > 50) {
this.otlkMagForecast -= change;
this.otlkMagForecast = Math.max(50, this.otlkMagForecast);
} else if (this.otlkMagForecast < 50) {
this.otlkMagForecast += change;
this.otlkMagForecast = Math.min(50, this.otlkMagForecast);
}
}
/**
* Serialize the Stock to a JSON save state.
*/

@ -187,12 +187,12 @@ export function stockMarketCycle() {
const roll = Math.random();
if (roll < 0.1) {
stock.flipForecastForecast();
StockMarket.ticksUntilCycle = 4 * TicksPerCycle;
} else if (roll < 0.55) {
stock.b = !stock.b;
stock.flipForecastForecast();
StockMarket.ticksUntilCycle = TicksPerCycle;
}
StockMarket.ticksUntilCycle = TicksPerCycle;
}
}
@ -262,8 +262,8 @@ export function processStockPrices(numCycles=1) {
}
let otlkMagChange = stock.otlkMag * av;
if (stock.otlkMag < 1) {
otlkMagChange = 1;
if (stock.otlkMag < 5) {
otlkMagChange *= 10;
}
stock.cycleForecast(otlkMagChange);
stock.cycleForecastForecast(otlkMagChange / 2);

@ -6,7 +6,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.01;
export const forecastChangePerPriceMovement = 0.006;
/**
* Calculate the total cost of a "buy" transaction. This accounts for spread and commission.
@ -60,7 +60,8 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
}
/**
* Processes a stock's change in forecast whenever it is transacted
* Processes a stock's change in forecast & second-order forecast
* whenever it is transacted
* @param {Stock} stock - Stock being sold
* @param {number} shares - Number of sharse being transacted
* @param {PositionTypes} posType - Long or short position
@ -78,7 +79,8 @@ export function processTransactionForecastMovement(stock: Stock, shares: number)
stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
stock.otlkMag -= (forecastChangePerPriceMovement);
stock.influenceForecast(forecastChangePerPriceMovement);
stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100));
}
return;
@ -95,13 +97,11 @@ export function processTransactionForecastMovement(stock: Stock, shares: number)
stock.shareTxUntilMovement = stock.shareTxForMovement;
}
// Forecast always decreases in magnitude
const forecastChange = forecastChangePerPriceMovement * (numIterations - 1);
const changeLimit = 6;
if (stock.otlkMag > changeLimit) {
stock.otlkMag = Math.max(changeLimit, stock.otlkMag - forecastChange);
}
const forecastForecastChange = forecastChange * (stock.mv / 100);
stock.influenceForecast(forecastChange);
stock.influenceForecastForecast(forecastForecastChange);
}
/**

@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6,
},
shareTxForMovement: {
max: 84e3,
min: 24e3,
max: 70e3,
min: 20e3,
},
symbol: StockSymbols["Sigma Cosmetics"],
},
@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6,
},
shareTxForMovement: {
max: 64e3,
min: 18e3,
max: 52e3,
min: 15e3,
},
symbol: StockSymbols["Joes Guns"],
},

@ -27,7 +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}`;
hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
}
let styleMarkup = {

@ -8,7 +8,7 @@ import {
} from "../src/StockMarket/BuyingAndSelling";
import { Order } from "../src/StockMarket/Order";
import { processOrders } from "../src/StockMarket/OrderProcessing";
import { Stock } from "../src/StockMarket/Stock";
import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock";
import {
deleteStockMarket,
initStockMarket,
@ -40,7 +40,7 @@ describe("Stock Market Tests", function() {
b: true,
initPrice: 10e3,
marketCap: 5e9,
mv: 1,
mv: 2,
name: "MockStock",
otlkMag: 20,
spreadPerc: 1,
@ -309,6 +309,57 @@ describe("Stock Market Tests", function() {
expect(stock.getForecastIncreaseChance()).to.equal(0.3);
});
});
describe("#influenceForecast()", function() {
beforeEach(function() {
stock.otlkMag = 10;
});
it("should change the forecast's value towards 50", function() {
stock.influenceForecast(2);
expect(stock.otlkMag).to.equal(8);
});
it("should not care about whether the stock is in bull or bear mode", function() {
stock.b = true;
stock.influenceForecast(1);
expect(stock.otlkMag).to.equal(9);
stock.b = false;
stock.influenceForecast(2);
expect(stock.otlkMag).to.equal(7);
});
it("should not influence the forecast beyond the limit", function() {
stock.influenceForecast(10);
expect(stock.otlkMag).to.equal(StockForecastInfluenceLimit);
stock.influenceForecast(10);
expect(stock.otlkMag).to.equal(StockForecastInfluenceLimit);
});
});
describe("#influenceForecastForecast()", function() {
it("should change the second-order forecast's value towards 50", function() {
stock.otlkMagForecast = 75;
stock.influenceForecastForecast(15);
expect(stock.otlkMagForecast).to.equal(60);
stock.otlkMagForecast = 25;
stock.influenceForecastForecast(15);
expect(stock.otlkMagForecast).to.equal(40);
});
it("should not change the second-order forecast past 50", function() {
stock.otlkMagForecast = 40;
stock.influenceForecastForecast(20);
expect(stock.otlkMagForecast).to.equal(50);
stock.otlkMagForecast = 60;
stock.influenceForecastForecast(20);
expect(stock.otlkMagForecast).to.equal(50);
});
});
});
describe("StockMarket object", function() {
@ -508,6 +559,16 @@ describe("Stock Market Tests", function() {
return origForecast - forecastChangePerPriceMovement * (n - 1);
}
function getNthForecastForecast(origForecastForecast, n) {
if (stock.otlkMagForecast > 50) {
const expected = origForecastForecast - (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100));
return expected < 50 ? 50 : expected;
} else if (stock.otlkMagForecast < 50) {
const expected = origForecastForecast + (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100));
return expected > 50 ? 50 : expected;
}
}
describe("processTransactionForecastMovement() for buy transactions", function() {
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
@ -547,69 +608,85 @@ describe("Stock Market Tests", function() {
it("should properly evaluate LONG transactions that triggers forecast movements", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that triggers forecast movements", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement);
processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement);
processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
});
@ -932,7 +1009,15 @@ describe("Stock Market Tests", function() {
});
describe("Order Processing", function() {
// TODO
before(function() {
expect(initStockMarket).to.not.throw();
expect(initSymbolToStockMap).to.not.throw();
});
describe()
describe("executeOrder()", function() {
});
});
describe("Player Influencing", function() {