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. * Execute a Stop or Limit Order.
* @param {Order} order - Order being executed * @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) { function executeOrder(order: Order, refs: IProcessOrderRefs) {
const stock = refs.symbolToStockMap[order.stockSymbol]; const stock = refs.symbolToStockMap[order.stockSymbol];

@ -6,6 +6,8 @@ import {
} from "../../utils/JSONReviver"; } from "../../utils/JSONReviver";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
export const StockForecastInfluenceLimit = 5;
export interface IConstructorParams { export interface IConstructorParams {
b: boolean; b: boolean;
initPrice: number | IMinMaxRange; initPrice: number | IMinMaxRange;
@ -290,6 +292,32 @@ export class Stock {
return (50 + Math.min(Math.max(diff, -45), 45)) / 100; 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. * Serialize the Stock to a JSON save state.
*/ */

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

@ -6,7 +6,7 @@ import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
// Amount by which a stock's forecast changes during each price movement // 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. * 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 {Stock} stock - Stock being sold
* @param {number} shares - Number of sharse being transacted * @param {number} shares - Number of sharse being transacted
* @param {PositionTypes} posType - Long or short position * @param {PositionTypes} posType - Long or short position
@ -78,7 +79,8 @@ export function processTransactionForecastMovement(stock: Stock, shares: number)
stock.shareTxUntilMovement -= shares; stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) { if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
stock.otlkMag -= (forecastChangePerPriceMovement); stock.influenceForecast(forecastChangePerPriceMovement);
stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100));
} }
return; return;
@ -95,13 +97,11 @@ export function processTransactionForecastMovement(stock: Stock, shares: number)
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
} }
// Forecast always decreases in magnitude // Forecast always decreases in magnitude
const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); const forecastChange = forecastChangePerPriceMovement * (numIterations - 1);
const changeLimit = 6; const forecastForecastChange = forecastChange * (stock.mv / 100);
if (stock.otlkMag > changeLimit) { stock.influenceForecast(forecastChange);
stock.otlkMag = Math.max(changeLimit, stock.otlkMag - forecastChange); stock.influenceForecastForecast(forecastForecastChange);
}
} }
/** /**

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

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

@ -8,7 +8,7 @@ import {
} from "../src/StockMarket/BuyingAndSelling"; } from "../src/StockMarket/BuyingAndSelling";
import { Order } from "../src/StockMarket/Order"; import { Order } from "../src/StockMarket/Order";
import { processOrders } from "../src/StockMarket/OrderProcessing"; import { processOrders } from "../src/StockMarket/OrderProcessing";
import { Stock } from "../src/StockMarket/Stock"; import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock";
import { import {
deleteStockMarket, deleteStockMarket,
initStockMarket, initStockMarket,
@ -40,7 +40,7 @@ describe("Stock Market Tests", function() {
b: true, b: true,
initPrice: 10e3, initPrice: 10e3,
marketCap: 5e9, marketCap: 5e9,
mv: 1, mv: 2,
name: "MockStock", name: "MockStock",
otlkMag: 20, otlkMag: 20,
spreadPerc: 1, spreadPerc: 1,
@ -309,6 +309,57 @@ describe("Stock Market Tests", function() {
expect(stock.getForecastIncreaseChance()).to.equal(0.3); 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() { describe("StockMarket object", function() {
@ -508,6 +559,16 @@ describe("Stock Market Tests", function() {
return origForecast - forecastChangePerPriceMovement * (n - 1); 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() { describe("processTransactionForecastMovement() for buy transactions", function() {
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; 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() { it("should properly evaluate LONG transactions that triggers forecast movements", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
}); });
it("should properly evaluate SHORT transactions that triggers forecast movements", function() { it("should properly evaluate SHORT transactions that triggers forecast movements", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
}); });
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement);
processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement);
processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldForecast = stock.otlkMag; const oldForecast = stock.otlkMag;
const oldForecastForecast = stock.otlkMagForecast;
processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
}); });
}); });
@ -932,7 +1009,15 @@ describe("Stock Market Tests", function() {
}); });
describe("Order Processing", 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() { describe("Player Influencing", function() {