2019-04-21 07:31:19 +02:00
|
|
|
import { Stock } from "./Stock";
|
|
|
|
import { PositionTypes } from "./data/PositionTypes";
|
|
|
|
import { CONSTANTS } from "../Constants";
|
|
|
|
|
2019-05-02 00:20:14 +02:00
|
|
|
// Amount by which a stock's forecast changes during each price movement
|
2019-06-10 00:12:33 +02:00
|
|
|
export const forecastChangePerPriceMovement = 0.006;
|
2019-05-02 00:20:14 +02:00
|
|
|
|
2019-04-28 10:23:11 +02:00
|
|
|
/**
|
2019-05-23 04:12:06 +02:00
|
|
|
* Calculate the total cost of a "buy" transaction. This accounts for spread and commission.
|
2019-04-21 07:31:19 +02:00
|
|
|
* @param {Stock} stock - Stock being purchased
|
|
|
|
* @param {number} shares - Number of shares being transacted
|
|
|
|
* @param {PositionTypes} posType - Long or short position
|
|
|
|
* @returns {number | null} Total transaction cost. Returns null for an invalid transaction
|
|
|
|
*/
|
2021-09-09 05:47:34 +02:00
|
|
|
export function getBuyTransactionCost(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
|
|
|
// hanging in the case when a really big number is passed in
|
|
|
|
shares = Math.min(shares, stock.maxShares);
|
|
|
|
|
|
|
|
const isLong = posType === PositionTypes.Long;
|
|
|
|
|
|
|
|
// If the number of shares doesn't trigger a price movement, its a simple calculation
|
|
|
|
if (isLong) {
|
|
|
|
return shares * stock.getAskPrice() + CONSTANTS.StockMarketCommission;
|
|
|
|
} else {
|
|
|
|
return shares * stock.getBidPrice() + CONSTANTS.StockMarketCommission;
|
|
|
|
}
|
2019-04-28 04:20:51 +02:00
|
|
|
}
|
|
|
|
|
2019-04-21 07:31:19 +02:00
|
|
|
/**
|
|
|
|
* Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts
|
2019-05-23 04:12:06 +02:00
|
|
|
* for spread and commission.
|
2019-04-21 07:31:19 +02:00
|
|
|
* @param {Stock} stock - Stock being sold
|
2022-10-09 07:25:31 +02:00
|
|
|
* @param {number} shares - Number of shares being transacted
|
2019-04-21 07:31:19 +02:00
|
|
|
* @param {PositionTypes} posType - Long or short position
|
|
|
|
* @returns {number | null} Amount of money gained from transaction. Returns null for an invalid transaction
|
|
|
|
*/
|
2021-09-09 05:47:34 +02:00
|
|
|
export function getSellTransactionGain(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
|
|
|
// hanging in the case when a really big number is passed in
|
|
|
|
shares = Math.min(shares, stock.maxShares);
|
|
|
|
|
|
|
|
const isLong = posType === PositionTypes.Long;
|
|
|
|
if (isLong) {
|
|
|
|
return shares * stock.getBidPrice() - CONSTANTS.StockMarketCommission;
|
|
|
|
} else {
|
|
|
|
// Calculating gains for a short position requires calculating the profit made
|
|
|
|
const origCost = shares * stock.playerAvgShortPx;
|
2021-09-09 05:47:34 +02:00
|
|
|
const profit = (stock.playerAvgShortPx - stock.getAskPrice()) * shares - CONSTANTS.StockMarketCommission;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
return origCost + profit;
|
|
|
|
}
|
2019-04-21 07:31:19 +02:00
|
|
|
}
|
2019-04-28 04:20:51 +02:00
|
|
|
|
|
|
|
/**
|
2019-06-10 00:12:33 +02:00
|
|
|
* Processes a stock's change in forecast & second-order forecast
|
|
|
|
* whenever it is transacted
|
2019-04-28 04:20:51 +02:00
|
|
|
* @param {Stock} stock - Stock being sold
|
2022-10-09 07:25:31 +02:00
|
|
|
* @param {number} shares - Number of shares being transacted
|
2019-04-28 04:20:51 +02:00
|
|
|
* @param {PositionTypes} posType - Long or short position
|
|
|
|
*/
|
2021-09-09 05:47:34 +02:00
|
|
|
export function processTransactionForecastMovement(stock: Stock, shares: number): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
|
|
|
// hanging in the case when a really big number is passed in
|
|
|
|
shares = Math.min(shares, stock.maxShares);
|
|
|
|
|
|
|
|
// If there's only going to be one iteration at most
|
|
|
|
const firstShares = stock.shareTxUntilMovement;
|
|
|
|
if (shares <= firstShares) {
|
|
|
|
stock.shareTxUntilMovement -= shares;
|
|
|
|
if (stock.shareTxUntilMovement <= 0) {
|
|
|
|
stock.shareTxUntilMovement = stock.shareTxForMovement;
|
|
|
|
stock.influenceForecast(forecastChangePerPriceMovement);
|
2021-09-09 05:47:34 +02:00
|
|
|
stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100));
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
2019-05-17 08:55:21 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate how many iterations of price changes we need to account for
|
|
|
|
const remainingShares = shares - firstShares;
|
|
|
|
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
|
|
|
|
2022-10-09 07:25:31 +02:00
|
|
|
// If on the off chance we end up perfectly at the next price movement
|
2021-09-05 01:09:30 +02:00
|
|
|
stock.shareTxUntilMovement =
|
2021-09-09 05:47:34 +02:00
|
|
|
stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
|
|
|
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
|
2021-09-05 01:09:30 +02:00
|
|
|
++numIterations;
|
|
|
|
stock.shareTxUntilMovement = stock.shareTxForMovement;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forecast always decreases in magnitude
|
|
|
|
const forecastChange = forecastChangePerPriceMovement * (numIterations - 1);
|
|
|
|
const forecastForecastChange = forecastChange * (stock.mv / 100);
|
|
|
|
stock.influenceForecast(forecastChange);
|
|
|
|
stock.influenceForecastForecast(forecastForecastChange);
|
2019-04-28 04:20:51 +02:00
|
|
|
}
|
2019-04-30 05:54:20 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the maximum number of shares of a stock that can be purchased.
|
|
|
|
* Handles mid-transaction price movements, both L and S positions, etc.
|
|
|
|
* Used for the "Buy Max" button in the UI
|
|
|
|
* @param {Stock} stock - Stock being purchased
|
|
|
|
* @param {PositionTypes} posType - Long or short position
|
|
|
|
* @param {number} money - Amount of money player has
|
|
|
|
* @returns maximum number of shares that the player can purchase
|
|
|
|
*/
|
2021-09-09 05:47:34 +02:00
|
|
|
export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, money: number): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (!(stock instanceof Stock)) {
|
|
|
|
return 0;
|
|
|
|
}
|
2019-04-30 05:54:20 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
const isLong = posType === PositionTypes.Long;
|
2019-04-30 05:54:20 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
const remainingMoney = money - CONSTANTS.StockMarketCommission;
|
|
|
|
const currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
2019-04-30 05:54:20 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return Math.floor(remainingMoney / currPrice);
|
2019-04-30 05:54:20 +02:00
|
|
|
}
|