2019-05-05 06:03:40 +02:00
/ * *
* 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 ,
2019-05-23 04:12:06 +02:00
processTransactionForecastMovement ,
2019-05-05 06:03:40 +02:00
} 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" ;
2021-03-31 06:45:21 +02:00
import { Money } from "../ui/React/Money" ;
2019-05-05 06:03:40 +02:00
import { dialogBoxCreate } from "../../utils/DialogBox" ;
2021-03-31 06:45:21 +02:00
import * as React from "react" ;
2019-05-05 06:03:40 +02:00
/ * *
* 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 {
// Validate arguments
shares = Math . round ( shares ) ;
2019-05-07 03:01:06 +02:00
if ( shares <= 0 ) { return false ; }
2019-05-05 06:03:40 +02:00
if ( stock == null || isNaN ( shares ) ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "buyStock" , ` Invalid arguments: stock=' ${ stock } ' shares=' ${ shares } ' ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2019-05-05 06:03:40 +02:00
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 ) ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "buyStock" , ` You do not have enough money to purchase this position. You need ${ numeralWrapper . formatMoney ( totalPrice ) } . ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > You do not have enough money to purchase this . You need < Money money = { totalPrice } / > < / > ) ;
2019-05-05 06:03:40 +02:00
}
return false ;
}
// Would this purchase exceed the maximum number of shares?
if ( shares + stock . playerShares + stock . playerShortShares > stock . maxShares ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "buyStock" , ` Purchasing ' ${ shares + stock . playerShares + stock . playerShortShares } ' shares would exceed ${ stock . symbol } 's maximum ( ${ stock . maxShares } ) number of shares ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2021-03-31 06:45:21 +02:00
dialogBoxCreate ( ` You cannot purchase this many shares. ${ stock . symbol } has a maximum of ${ numeralWrapper . formatShares ( stock . maxShares ) } shares. ` ) ;
2019-05-05 06:03:40 +02:00
}
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 ;
2019-05-23 04:12:06 +02:00
processTransactionForecastMovement ( stock , shares ) ;
2019-05-05 06:03:40 +02:00
if ( opts . rerenderFn != null && typeof opts . rerenderFn === "function" ) {
opts . rerenderFn ( ) ;
}
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
2021-03-31 06:45:21 +02:00
const resultTxt = ` Bought ${ numeralWrapper . formatShares ( shares ) } shares of ${ stock . symbol } for ${ numeralWrapper . formatMoney ( totalPrice ) } . ` +
` Paid ${ numeralWrapper . formatMoney ( CONSTANTS . StockMarketCommission ) } in commission fees. `
2021-05-01 09:17:31 +02:00
workerScript . log ( "buyStock" , resultTxt )
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > Bought { numeralWrapper . formatShares ( shares ) } shares of { stock . symbol } for < Money money = { totalPrice } / > . Paid < Money money = { CONSTANTS . StockMarketCommission } / > in commission fees . < / > ) ;
2019-05-05 06:03:40 +02:00
}
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 {
// Sanitize/Validate arguments
if ( stock == null || shares < 0 || isNaN ( shares ) ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "sellStock" , ` Invalid arguments: stock=' ${ stock } ' shares=' ${ shares } ' ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2019-05-05 06:03:40 +02:00
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 ) ;
2019-05-07 03:01:06 +02:00
if ( shares > stock . playerShares ) { shares = stock . playerShares ; }
if ( shares === 0 ) { return false ; }
2019-05-05 06:03:40 +02:00
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" ) ;
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . scriptRef . onlineMoneyMade += netProfit ;
2019-05-05 06:03:40 +02:00
Player . scriptProdSinceLastAug += netProfit ;
}
stock . playerShares = Math . round ( stock . playerShares - shares ) ;
if ( stock . playerShares === 0 ) {
stock . playerAvgPx = 0 ;
}
2019-05-23 04:12:06 +02:00
processTransactionForecastMovement ( stock , shares ) ;
2019-05-05 06:03:40 +02:00
if ( opts . rerenderFn != null && typeof opts . rerenderFn === "function" ) {
opts . rerenderFn ( ) ;
}
2021-03-31 06:45:21 +02:00
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
2021-03-31 06:45:21 +02:00
const resultTxt = ` Sold ${ numeralWrapper . formatShares ( shares ) } shares of ${ stock . symbol } . ` +
` After commissions, you gained a total of ${ numeralWrapper . formatMoney ( gains ) } . ` ;
2021-05-01 09:17:31 +02:00
workerScript . log ( "sellStock" , resultTxt )
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > Sold { numeralWrapper . formatShares ( shares ) } shares of { stock . symbol } . After commissions , you gained a total of < Money money = { gains } / > . < / > ) ;
2019-05-05 06:03:40 +02:00
}
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 {
// Validate arguments
shares = Math . round ( shares ) ;
2019-05-07 03:01:06 +02:00
if ( shares <= 0 ) { return false ; }
2019-05-05 06:03:40 +02:00
if ( stock == null || isNaN ( shares ) ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "shortStock" , ` Invalid arguments: stock=' ${ stock } ' shares=' ${ shares } ' ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2019-05-05 06:03:40 +02:00
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 ) ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "shortStock" , "You do not have enough " +
2019-05-05 06:03:40 +02:00
"money to purchase this short position. You need " +
numeralWrapper . formatMoney ( totalPrice ) ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > You do not have enough money to purchase this short position . You need < Money money = { totalPrice } / > < / > ) ;
2019-05-05 06:03:40 +02:00
}
return false ;
}
// Would this purchase exceed the maximum number of shares?
if ( shares + stock . playerShares + stock . playerShortShares > stock . maxShares ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "shortStock" , ` This ' ${ shares + stock . playerShares + stock . playerShortShares } ' short shares would exceed ${ stock . symbol } 's maximum ( ${ stock . maxShares } ) number of shares. ` ) ;
2019-05-07 03:01:06 +02:00
} else if ( opts . suppressDialog !== true ) {
2019-05-05 06:03:40 +02:00
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 ;
2019-05-23 04:12:06 +02:00
processTransactionForecastMovement ( stock , shares ) ;
2019-05-05 06:03:40 +02:00
if ( opts . rerenderFn != null && typeof opts . rerenderFn === "function" ) {
opts . rerenderFn ( ) ;
}
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
2021-03-31 06:45:21 +02:00
const resultTxt = ` Bought a short position of ${ numeralWrapper . formatShares ( shares ) } shares of ${ stock . symbol } ` +
` for ${ numeralWrapper . formatMoney ( totalPrice ) } . Paid ${ numeralWrapper . formatMoney ( CONSTANTS . StockMarketCommission ) } ` +
` in commission fees. ` ;
2021-05-01 09:17:31 +02:00
workerScript . log ( "shortStock" , resultTxt ) ;
2019-05-05 06:03:40 +02:00
} else if ( ! opts . suppressDialog ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > Bought a short position of { numeralWrapper . formatShares ( shares ) } shares of { stock . symbol } for < Money money = { totalPrice } / > . Paid < Money money = { CONSTANTS . StockMarketCommission } / > in commission fees . < / > ) ;
2019-05-05 06:03:40 +02:00
}
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 {
if ( stock == null || isNaN ( shares ) || shares < 0 ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "sellShort" , ` Invalid arguments: stock=' ${ stock } ' shares=' ${ shares } ' ` ) ;
2019-05-05 06:03:40 +02:00
} 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 ) {
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . log ( "sellShort" , ` Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug ` ) ;
2019-05-05 06:03:40 +02:00
} 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" ) ;
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
workerScript . scriptRef . onlineMoneyMade += profit ;
2019-05-05 06:03:40 +02:00
Player . scriptProdSinceLastAug += profit ;
}
stock . playerShortShares = Math . round ( stock . playerShortShares - shares ) ;
if ( stock . playerShortShares === 0 ) {
stock . playerAvgShortPx = 0 ;
}
2019-05-23 04:12:06 +02:00
processTransactionForecastMovement ( stock , shares ) ;
2019-05-05 06:03:40 +02:00
if ( opts . rerenderFn != null && typeof opts . rerenderFn === "function" ) {
opts . rerenderFn ( ) ;
}
2021-05-01 09:17:31 +02:00
if ( workerScript ) {
2021-03-31 06:45:21 +02:00
const resultTxt = ` Sold your short position of ${ numeralWrapper . formatShares ( shares ) } shares of ${ stock . symbol } . ` +
` After commissions, you gained a total of ${ numeralWrapper . formatMoney ( totalGain ) } ` ;
2021-05-01 09:17:31 +02:00
workerScript . log ( "sellShort" , resultTxt ) ;
2019-05-05 06:03:40 +02:00
} else if ( ! opts . suppressDialog ) {
2021-09-04 09:27:31 +02:00
dialogBoxCreate ( < > Sold your short position of { numeralWrapper . formatShares ( shares ) } shares of { stock . symbol } . After commissions , you gained a total of < Money money = { totalGain } / > < / > ) ;
2019-05-05 06:03:40 +02:00
}
return true ;
}