mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 15:43:49 +01:00
More rebalancing for stock market changes. Transactions now affect second-order forecast (very slightly)
This commit is contained in:
parent
00f8c0a51f
commit
a15041da75
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user