mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-10-23 18:23:15 +02:00
297 lines
12 KiB
TypeScript
297 lines
12 KiB
TypeScript
|
/**
|
||
|
* Functions for buying/selling stocks. There are four functions total, two for
|
||
|
* long positions and two for short positions.
|
||
|
*/
|
||
|
import { Stock } from "./Stock";
|
||
|
import {
|
||
|
getBuyTransactionCost,
|
||
|
getSellTransactionGain,
|
||
|
processBuyTransactionPriceMovement,
|
||
|
processSellTransactionPriceMovement
|
||
|
} from "./StockMarketHelpers";
|
||
|
|
||
|
import { PositionTypes } from "./data/PositionTypes";
|
||
|
|
||
|
import { CONSTANTS } from "../Constants";
|
||
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||
|
import { Player } from "../Player";
|
||
|
|
||
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||
|
|
||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||
|
|
||
|
/**
|
||
|
* Each function takes an optional config object as its last argument
|
||
|
*/
|
||
|
interface IOptions {
|
||
|
rerenderFn?: () => void;
|
||
|
suppressDialog?: boolean;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to buy a stock in the long position
|
||
|
* @param {Stock} stock - Stock to buy
|
||
|
* @param {number} shares - Number of shares to buy
|
||
|
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||
|
* @param opts - Optional configuration for this function's behavior. See top of file
|
||
|
* @returns {boolean} - true if successful, false otherwise
|
||
|
*/
|
||
|
export function buyStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||
|
const tixApi = (workerScript instanceof WorkerScript);
|
||
|
|
||
|
// Validate arguments
|
||
|
shares = Math.round(shares);
|
||
|
if (shares == 0 || shares < 0) { return false; }
|
||
|
if (stock == null || isNaN(shares)) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`ERROR: buyStock() failed due to invalid arguments`);
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Does player have enough money?
|
||
|
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||
|
if (totalPrice == null) { return false; }
|
||
|
if (Player.money.lt(totalPrice)) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`);
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Would this purchase exceed the maximum number of shares?
|
||
|
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const origTotal = stock.playerShares * stock.playerAvgPx;
|
||
|
Player.loseMoney(totalPrice);
|
||
|
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||
|
stock.playerShares = Math.round(stock.playerShares + shares);
|
||
|
stock.playerAvgPx = newTotal / stock.playerShares;
|
||
|
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||
|
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||
|
opts.rerenderFn();
|
||
|
}
|
||
|
|
||
|
const resultTxt = `Bought ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(totalPrice)}. ` +
|
||
|
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
|
||
|
if (tixApi) {
|
||
|
if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); }
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate(resultTxt);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to sell a stock in the long position
|
||
|
* @param {Stock} stock - Stock to sell
|
||
|
* @param {number} shares - Number of shares to sell
|
||
|
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||
|
* @param opts - Optional configuration for this function's behavior. See top of file
|
||
|
* returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise
|
||
|
*/
|
||
|
export function sellStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||
|
const tixApi = (workerScript instanceof WorkerScript);
|
||
|
|
||
|
// Sanitize/Validate arguments
|
||
|
if (stock == null || shares < 0 || isNaN(shares)) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`ERROR: sellStock() failed due to invalid arguments`);
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
shares = Math.round(shares);
|
||
|
if (shares > stock.playerShares) {shares = stock.playerShares;}
|
||
|
if (shares === 0) {return false;}
|
||
|
|
||
|
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||
|
if (gains == null) { return false; }
|
||
|
let netProfit = gains - (stock.playerAvgPx * shares);
|
||
|
if (isNaN(netProfit)) { netProfit = 0; }
|
||
|
Player.gainMoney(gains);
|
||
|
Player.recordMoneySource(netProfit, "stock");
|
||
|
if (tixApi) {
|
||
|
workerScript!.scriptRef.onlineMoneyMade += netProfit;
|
||
|
Player.scriptProdSinceLastAug += netProfit;
|
||
|
}
|
||
|
|
||
|
stock.playerShares = Math.round(stock.playerShares - shares);
|
||
|
if (stock.playerShares === 0) {
|
||
|
stock.playerAvgPx = 0;
|
||
|
}
|
||
|
|
||
|
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||
|
|
||
|
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||
|
opts.rerenderFn();
|
||
|
}
|
||
|
|
||
|
const resultTxt = `Sold ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
|
||
|
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
|
||
|
if (tixApi) {
|
||
|
if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); }
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate(resultTxt);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to buy a stock in the short position
|
||
|
* @param {Stock} stock - Stock to sell
|
||
|
* @param {number} shares - Number of shares to short
|
||
|
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||
|
* @param opts - Optional configuration for this function's behavior. See top of file
|
||
|
* @returns {boolean} - true if successful, false otherwise
|
||
|
*/
|
||
|
export function shortStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||
|
const tixApi = (workerScript instanceof WorkerScript);
|
||
|
|
||
|
// Validate arguments
|
||
|
shares = Math.round(shares);
|
||
|
if (shares === 0 || shares < 0) { return false; }
|
||
|
if (stock == null || isNaN(shares)) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log("ERROR: shortStock() failed because of invalid arguments.");
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " +
|
||
|
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Does the player have enough money?
|
||
|
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||
|
if (totalPrice == null) { return false; }
|
||
|
if (Player.money.lt(totalPrice)) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log("ERROR: shortStock() failed because you do not have enough " +
|
||
|
"money to purchase this short position. You need " +
|
||
|
numeralWrapper.formatMoney(totalPrice));
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
|
||
|
numeralWrapper.formatMoney(totalPrice));
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Would this purchase exceed the maximum number of shares?
|
||
|
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
|
||
|
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
|
||
|
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
|
||
|
Player.loseMoney(totalPrice);
|
||
|
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||
|
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||
|
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||
|
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||
|
|
||
|
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||
|
opts.rerenderFn();
|
||
|
}
|
||
|
|
||
|
const resultTxt = `Bought a short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} ` +
|
||
|
`for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} ` +
|
||
|
`in commission fees.`;
|
||
|
if (tixApi) {
|
||
|
if (workerScript!.shouldLog("shortStock")) { workerScript!.log(resultTxt); }
|
||
|
} else if (!opts.suppressDialog) {
|
||
|
dialogBoxCreate(resultTxt);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to sell a stock in the short position
|
||
|
* @param {Stock} stock - Stock to sell
|
||
|
* @param {number} shares - Number of shares to sell
|
||
|
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||
|
* @param opts - Optional configuration for this function's behavior. See top of file
|
||
|
* @returns {boolean} true if successfully sells given amount OR max owned, false otherwise
|
||
|
*/
|
||
|
export function sellShort(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||
|
const tixApi = (workerScript instanceof WorkerScript);
|
||
|
|
||
|
if (stock == null || isNaN(shares) || shares < 0) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log("ERROR: sellShort() failed because of invalid arguments.");
|
||
|
} else if (!opts.suppressDialog) {
|
||
|
dialogBoxCreate("Failed to sell a short position in a stock. This is probably " +
|
||
|
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
shares = Math.round(shares);
|
||
|
if (shares > stock.playerShortShares) {shares = stock.playerShortShares;}
|
||
|
if (shares === 0) {return false;}
|
||
|
|
||
|
const origCost = shares * stock.playerAvgShortPx;
|
||
|
const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||
|
if (totalGain == null || isNaN(totalGain) || origCost == null) {
|
||
|
if (tixApi) {
|
||
|
workerScript!.log(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
|
||
|
} else if (!opts.suppressDialog) {
|
||
|
dialogBoxCreate(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
let profit = totalGain - origCost;
|
||
|
if (isNaN(profit)) { profit = 0; }
|
||
|
Player.gainMoney(totalGain);
|
||
|
Player.recordMoneySource(profit, "stock");
|
||
|
if (tixApi) {
|
||
|
workerScript!.scriptRef.onlineMoneyMade += profit;
|
||
|
Player.scriptProdSinceLastAug += profit;
|
||
|
}
|
||
|
|
||
|
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
|
||
|
if (stock.playerShortShares === 0) {
|
||
|
stock.playerAvgShortPx = 0;
|
||
|
}
|
||
|
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||
|
|
||
|
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||
|
opts.rerenderFn();
|
||
|
}
|
||
|
|
||
|
const resultTxt = `Sold your short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
|
||
|
`After commissions, you gained a total of ${numeralWrapper.formatMoney(totalGain)}`;
|
||
|
if (tixApi) {
|
||
|
if (workerScript!.shouldLog("sellShort")) { workerScript!.log(resultTxt); }
|
||
|
} else if (!opts.suppressDialog) {
|
||
|
dialogBoxCreate(resultTxt);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|