mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-20 21:25:47 +01:00
Fixed issues relating to stock price movements
This commit is contained in:
parent
67632ced09
commit
87b4698d5b
@ -1,5 +1,11 @@
|
||||
import { Order } from "./Order";
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement
|
||||
} from "./StockMarketHelpers";
|
||||
import {
|
||||
getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost
|
||||
@ -226,8 +232,8 @@ export function buyStock(stock, shares, workerScript=null) {
|
||||
}
|
||||
|
||||
// Does player have enough money?
|
||||
const totalPrice = stock.price * shares;
|
||||
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
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 + CONSTANTS.StockMarketCommission)}`);
|
||||
} else {
|
||||
@ -249,25 +255,19 @@ export function buyStock(stock, shares, workerScript=null) {
|
||||
}
|
||||
|
||||
const origTotal = stock.playerShares * stock.playerAvgPx;
|
||||
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||
Player.loseMoney(totalPrice);
|
||||
const newTotal = origTotal + totalPrice;
|
||||
stock.playerShares = Math.round(stock.playerShares + shares);
|
||||
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
displayStockMarketContent();
|
||||
|
||||
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(
|
||||
"Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
if (workerScript.shouldLog("buyStock")) { workerScript.log(resultTxt); }
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -297,8 +297,8 @@ export function sellStock(stock, shares, workerScript=null) {
|
||||
if (shares > stock.playerShares) {shares = stock.playerShares;}
|
||||
if (shares === 0) {return false;}
|
||||
|
||||
const gains = stock.price * shares - CONSTANTS.StockMarketCommission;
|
||||
let netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission;
|
||||
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
let netProfit = gains - (stock.playerAvgPx * shares);
|
||||
if (isNaN(netProfit)) { netProfit = 0; }
|
||||
Player.gainMoney(gains);
|
||||
Player.recordMoneySource(netProfit, "stock");
|
||||
@ -311,21 +311,15 @@ export function sellStock(stock, shares, workerScript=null) {
|
||||
if (stock.playerShares === 0) {
|
||||
stock.playerAvgPx = 0;
|
||||
}
|
||||
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
displayStockMarketContent();
|
||||
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(
|
||||
"Sold " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(gains, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
if (workerScript.shouldLog("sellStock")) { workerScript.log(resultTxt); }
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Sold " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(gains, '($0.000a)') + "."
|
||||
);
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -355,7 +349,7 @@ export function shortStock(stock, shares, workerScript=null) {
|
||||
}
|
||||
|
||||
// Does the player have enough money?
|
||||
const totalPrice = stock.price * shares;
|
||||
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||
if (tixApi) {
|
||||
workerScript.log("ERROR: shortStock() failed because you do not have enough " +
|
||||
@ -385,21 +379,15 @@ export function shortStock(stock, shares, workerScript=null) {
|
||||
const newTotal = origTotal + totalPrice;
|
||||
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||||
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
displayStockMarketContent();
|
||||
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.disableLogs.ALL == null && workerScript.disableLogs.shortStock == null) {
|
||||
workerScript.log(
|
||||
"Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
if (workerScript.shouldLog("shortStock")) { workerScript.log(resultTxt); }
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -430,9 +418,19 @@ export function sellShort(stock, shares, workerScript=null) {
|
||||
if (shares === 0) {return false;}
|
||||
|
||||
const origCost = shares * stock.playerAvgShortPx;
|
||||
let profit = ((stock.playerAvgShortPx - stock.price) * shares) - CONSTANTS.StockMarketCommission;
|
||||
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 {
|
||||
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(origCost + profit);
|
||||
Player.gainMoney(totalGain);
|
||||
Player.recordMoneySource(profit, "stock");
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.onlineMoneyMade += profit;
|
||||
@ -443,21 +441,14 @@ export function sellShort(stock, shares, workerScript=null) {
|
||||
if (stock.playerShortShares === 0) {
|
||||
stock.playerAvgShortPx = 0;
|
||||
}
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
displayStockMarketContent();
|
||||
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(
|
||||
"Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
if (workerScript.shouldLog("sellShort")) { workerScript.log(resultTxt); }
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + "."
|
||||
);
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -51,6 +51,49 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
|
||||
return totalCost + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a buy transaction's resulting price movement.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processBuyTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
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);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price movement, just return
|
||||
if (shares <= stock.shareTxUntilMovement) {
|
||||
stock.shareTxUntilMovement -= shares;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
stock.price = currPrice;
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts
|
||||
* for spread, price movements, and commission.
|
||||
@ -115,3 +158,45 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
|
||||
|
||||
return totalGain - CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a sell transaction's resulting price movement
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processSellTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
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);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
if (shares <= stock.shareTxUntilMovement) {
|
||||
stock.shareTxUntilMovement -= shares;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to accoutn for
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
stock.price = currPrice;
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user