diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index e61407b29..0976e0ba7 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -7,10 +7,14 @@ buy and sell stocks in order to make money. The WSE can be found in the 'City' tab, and is accessible in every city. -Automating the Stock Market -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You can write scripts to perform automatic and algorithmic trading on the Stock Market. -See :ref:`netscript_tixapi` for more details. +Fundamentals +------------ +The Stock Market is not as simple as "buy at price X and sell at price Y". The following +are several fundamental concepts you need to understand about the stock market. + +.. note:: For those that have experience with finance/trading/investing, please be aware + that the game's stock market does not function exactly like it does in the real + world. So these concepts below should seem similar, but won't be exactly the same. Positions: Long vs Short ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,16 +25,61 @@ is the exact opposite. In a Short position you purchase shares of a stock and earn a profit if the price of that stock decreases. This is also called 'shorting' a stock. -NOTE: Shorting stocks is not available immediately, and must be unlocked later in the -game. +.. note:: Shorting stocks is not available immediately, and must be unlocked later in the + game. + +.. _gameplay_stock_market_spread: + +Spread (Bid Price & Ask Price) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The **bid price** is the maximum price at which someone will buy a stock on the +stock market. + +The **ask price** is the minimum price that a seller is willing to receive for a stock +on the stock market + +The ask price will always be higher than the bid price (This is because if a seller +is willing to receive less than the bid price, that transaction is guaranteed to +happen). The difference between the bid and ask price is known as the **spread**. +A stock's "price" will be the average of the bid and ask price. + +The bid and ask price are important because these are the prices at which a +transaction actually occurs. If you purchase a stock in the long position, the cost +of your purchase depends on that stock's ask price. If you then try to sell that +stock (still in the long position), the price at which you sell is the stock's +bid price. Note that this is reversed for a short position. Purchasing a stock +in the short position will occur at the stock's bid price, and selling a stock +in the short position will occur at the stock's ask price. + +.. _gameplay_stock_spread_price_movement: + +Transactions Influencing Stock Price +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Buying or selling a large number of shares of a stock will influence that stock's price. + +Buying a stock in the long position will cause that stock's price to +increase. Selling a stock in the long position will cause the stock's price to decrease. +The reverse occurs for the short position. The effect of this depends on how many shares +are being transacted. More shares will have a bigger effect on the stock price. If +only a small number of shares are being transacted, the stock price may not be affected. + +Note that this "influencing" of the stock price **can happen in the middle of a transaction**. +For example, assume you are selling 1m shares of a stock. That stock's bid price +is currently $100. However, selling 100k shares of the stock causes its price to drop +by 1%. This means that for your transaction of 1m shares, the first 100k shares will be +sold at $100. Then the next 100k shares will be sold at $99. Then the next 100k shares will +be sold at $98.01, and so on. + +This is an important concept to keep in mind if you are trying to purchase/sell a +large number of shares, as **it can negatively affect your profits**. Order Types ^^^^^^^^^^^ There are three different types of orders you can make to buy or sell stocks on the exchange: Market Order, Limit Order, and Stop Order. -Note that Limit Orders and Stop Orders are not available immediately, and must be unlocked -later in the game. +.. note:: Limit Orders and Stop Orders are not available immediately, and must be unlocked + later in the game. When you place a Market Order to buy or sell a stock, the order executes immediately at whatever the current price of the stock is. For example if you choose to short a stock @@ -71,3 +120,8 @@ A Limit Order to sell will execute if the stock's price <= order's price A Stop Order to buy will execute if the stock's price <= order's price A Stop Order to sell will execute if the stock's price >= order's price. + +Automating the Stock Market +--------------------------- +You can write scripts to perform automatic and algorithmic trading on the Stock Market. +See :ref:`netscript_tixapi` for more details. diff --git a/doc/source/netscript/netscriptixapi.rst b/doc/source/netscript/netscriptixapi.rst index 9a1d11b66..76636efc5 100644 --- a/doc/source/netscript/netscriptixapi.rst +++ b/doc/source/netscript/netscriptixapi.rst @@ -15,11 +15,15 @@ access even after you 'reset' by installing Augmentations .. toctree:: :caption: API Functions: - + getStockSymbols() getStockPrice() + getStockAskPrice() + getStockBidPrice() getStockPosition() getStockMaxShares() + getStockPurchaseCost() + getStockSaleGain() buyStock() sellStock() shortStock() diff --git a/doc/source/netscript/tixapi/getStockAskPrice.rst b/doc/source/netscript/tixapi/getStockAskPrice.rst new file mode 100644 index 000000000..8365df502 --- /dev/null +++ b/doc/source/netscript/tixapi/getStockAskPrice.rst @@ -0,0 +1,12 @@ +getStockAskPrice() Netscript Function +===================================== + +.. js:function:: getStockAskPrice(sym) + + :param string sym: Stock symbol + :RAM cost: 2 GB + + Given a stock's symbol, returns the ask price of that stock (the symbol is a sequence + of two to four capital letters, **not** the name of the company to which that stock belongs). + + See :ref:`gameplay_stock_market_spread` for details on what the ask price is. diff --git a/doc/source/netscript/tixapi/getStockBidPrice.rst b/doc/source/netscript/tixapi/getStockBidPrice.rst new file mode 100644 index 000000000..e9e03e950 --- /dev/null +++ b/doc/source/netscript/tixapi/getStockBidPrice.rst @@ -0,0 +1,12 @@ +getStockBidPrice() Netscript Function +===================================== + +.. js:function:: getStockBidPrice(sym) + + :param string sym: Stock symbol + :RAM cost: 2 GB + + Given a stock's symbol, returns the bid price of that stock (the symbol is a sequence + of two to four capital letters, **not** the name of the company to which that stock belongs). + + See :ref:`gameplay_stock_market_spread` for details on what the bid price is. diff --git a/doc/source/netscript/tixapi/getStockPrice.rst b/doc/source/netscript/tixapi/getStockPrice.rst index ae88e644f..623e0ce5e 100644 --- a/doc/source/netscript/tixapi/getStockPrice.rst +++ b/doc/source/netscript/tixapi/getStockPrice.rst @@ -6,9 +6,12 @@ getStockPrice() Netscript Function :param string sym: Stock symbol :RAM cost: 2 GB - Returns the price of a stock, given its symbol (NOT the company name). The symbol is a sequence - of two to four capital letters. + Given a stock's symbol, returns the price of that stock (the symbol is a sequence + of two to four capital letters, **not** the name of the company to which that stock belongs). + + .. note:: The stock's price is the average of its bid and ask price. + See :ref:`gameplay_stock_market_spread` for details on what this means. Example:: - getStockPrice("FISG"); + getStockPrice("FSIG"); diff --git a/doc/source/netscript/tixapi/getStockPurchaseCost.rst b/doc/source/netscript/tixapi/getStockPurchaseCost.rst new file mode 100644 index 000000000..e8b6e3c2d --- /dev/null +++ b/doc/source/netscript/tixapi/getStockPurchaseCost.rst @@ -0,0 +1,15 @@ +getStockPurchaseCost() Netscript Function +========================================= + +.. js:function:: getStockPurchaseCost(sym, shares, posType) + + :param string sym: Stock symbol + :param number shares: Number of shares to purchase + :param string posType: Specifies whether the order is a "Long" or "Short" position. + The values "L" or "S" can also be used. + :RAM cost: 2 GB + + Calculates and returns how much it would cost to buy a given number of + shares of a stock. This takes into account :ref:`spread `, + :ref:`large transactions influencing the price of the stock ` + and commission fees. diff --git a/doc/source/netscript/tixapi/getStockSaleGain.rst b/doc/source/netscript/tixapi/getStockSaleGain.rst new file mode 100644 index 000000000..5d388c169 --- /dev/null +++ b/doc/source/netscript/tixapi/getStockSaleGain.rst @@ -0,0 +1,15 @@ +getStockSaleGain() Netscript Function +===================================== + +.. js:function:: getStockSaleGain(sym, shares, posType) + + :param string sym: Stock symbol + :param number shares: Number of shares to purchase + :param string posType: Specifies whether the order is a "Long" or "Short" position. + The values "L" or "S" can also be used. + :RAM cost: 2 GB + + Calculates and returns how much you would gain from selling a given number of + shares of a stock. This takes into account :ref:`spread `, + :ref:`large transactions influencing the price of the stock ` + and commission fees. diff --git a/src/Constants.ts b/src/Constants.ts index d8f4b2397..50ed1dffb 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -275,35 +275,15 @@ export let CONSTANTS: IMap = { LatestUpdate: ` - v0.46.3 - * Added a new Augmentation: The Shadow's Simulacrum - * Improved tab autocompletion feature in Terminal so that it works better with directories - * Bug Fix: Tech vendor location UI now properly refreshed when purchasing a TOR router - * Bug Fix: Fixed UI issue with faction donations - * Bug Fix: The money statistics & breakdown should now properly track money earned from Hacknet Server (hashes -> money) - * Bug Fix: Fixed issue with changing input in 'Minimum Path Sum in a Triangle' coding contract problem - * Fixed several typos in various places + v0.47.0 + * Stock Market changes: + ** Implemented spread. Stock's now have bid and ask prices at which transactions occur + ** Large transactions will now influence a stock's price. + ** This "influencing" can take effect in the middle of a transaction + ** See documentation for more details on these changes + ** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API + ** Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API - v0.46.2 - * Source-File 2 now allows you to form gangs in other BitNodes when your karma reaches a very large negative value - ** (Karma is a hidden stat and is lowered by committing crimes) - * Gang changes: - ** Bug Fix: Gangs can no longer clash with themselve - ** Bug Fix: Winning against another gang should properly reduce their power - * Bug Fix: Terminal 'wget' command now works properly - * Bug Fix: Hacknet Server Hash upgrades now properly reset upon installing Augs/switching BitNodes - * Bug Fix: Fixed button for creating Corporations - - v0.46.1 - * Added a very rudimentary directory system to the Terminal - ** Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories - * Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API - * 'Generate Coding Contract' hash upgrade is now more expensive - * 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer - * The cost of selling hashes for money no longer increases each time - * Selling hashes for money now costs 4 hashes (in exchange for $1m) - * Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline - * Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode ` } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index df8b73264..b3639c90f 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -84,6 +84,10 @@ import { placeOrder, cancelOrder } from "./StockMarket/StockMarket"; +import { + getBuyTransactionCost, + getSellTransactionGain, +} from "./StockMarket/StockMarketHelpers"; import { OrderTypes } from "./StockMarket/data/OrderTypes"; import { PositionTypes } from "./StockMarket/data/PositionTypes"; import { StockSymbols } from "./StockMarket/data/StockSymbols"; @@ -280,6 +284,29 @@ function NetscriptFunctions(workerScript) { return server; } + /** + * Checks if the player has TIX API access. Throws an error if the player does not + */ + const checkTixApiAccess = function(callingFn="") { + if (!Player.hasTixApiAccess) { + throw makeRuntimeRejectMsg(workerScript, `You don't have TIX API Access! Cannot use ${callingFn}()`); + } + } + + /** + * Gets a stock, given its symbol. Throws an error if the symbol is invalid + * @param {string} symbol - Stock's symbol + * @returns {Stock} stock object + */ + const getStockFromSymbol = function(symbol, callingFn="") { + const stock = SymbolToStockMap[symbol]; + if (stock == null) { + throw makeRuntimeRejectMsg(workerScript, `Invalid stock symbol passed into ${callingFn}()`); + } + + return stock; + } + /** * Used to fail a function if the function's target is a Hacknet Server. * This is used for functions that should run on normal Servers, but not Hacknet Servers @@ -1585,9 +1612,7 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("getStockSymbols", CONSTANTS.ScriptGetStockRamCost); } updateDynamicRam("getStockSymbols", CONSTANTS.ScriptGetStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use getStockSymbols()"); - } + checkTixApiAccess("getStockSymbols"); return Object.values(StockSymbols); }, getStockPrice : function(symbol) { @@ -1595,23 +1620,37 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("getStockPrice", CONSTANTS.ScriptGetStockRamCost); } updateDynamicRam("getStockPrice", CONSTANTS.ScriptGetStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use getStockPrice()"); + checkTixApiAccess("getStockPrice"); + const stock = getStockFromSymbol(symbol, "getStockPrice"); + + return stock.price; + }, + getStockAskPrice : function(symbol) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockAskPrice", CONSTANTS.ScriptGetStockRamCost); } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into getStockPrice()"); + updateDynamicRam("getStockAskPrice", CONSTANTS.ScriptGetStockRamCost); + checkTixApiAccess("getStockAskPrice"); + const stock = getStockFromSymbol(symbol, "getStockAskPrice"); + + return stock.getAskPrice(); + }, + getStockBidPrice : function(symbol) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockBidPrice", CONSTANTS.ScriptGetStockRamCost); } - return parseFloat(stock.price.toFixed(3)); + updateDynamicRam("getStockBidPrice", CONSTANTS.ScriptGetStockRamCost); + checkTixApiAccess("getStockBidPrice"); + const stock = getStockFromSymbol(symbol, "getStockBidPrice"); + + return stock.getBidPrice(); }, getStockPosition : function(symbol) { if (workerScript.checkingRam) { return updateStaticRam("getStockPosition", CONSTANTS.ScriptGetStockRamCost); } updateDynamicRam("getStockPosition", CONSTANTS.ScriptGetStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use getStockPosition()"); - } + checkTixApiAccess("getStockPosition"); var stock = SymbolToStockMap[symbol]; if (stock == null) { throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into getStockPosition()"); @@ -1623,29 +1662,66 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("getStockMaxShares", CONSTANTS.ScriptGetStockRamCost); } updateDynamicRam("getStockMaxShares", CONSTANTS.ScriptGetStockRamCost); - - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use getStockMaxShares()"); - } - const stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into getStockMaxShares()"); - } + checkTixApiAccess("getStockMaxShares"); + const stock = getStockFromSymbol(symbol, "getStockMaxShares"); return stock.maxShares; }, + getStockPurchaseCost : function(symbol, shares, posType) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockPurchaseCost", CONSTANTS.ScriptGetStockRamCost); + } + updateDynamicRam("getStockPurchaseCost", CONSTANTS.ScriptGetStockRamCost); + checkTixApiAccess("getStockPurchaseCost"); + const stock = getStockFromSymbol(symbol, "getStockPurchaseCost"); + shares = Math.round(shares); + + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return Infinity; + } + + const res = getBuyTransactionCost(stock, shares, pos); + if (res == null) { return Infinity; } + + return res; + }, + getStockSaleGain : function(symbol, shares, posType) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockSaleGain", CONSTANTS.ScriptGetStockRamCost); + } + updateDynamicRam("getStockSaleGain", CONSTANTS.ScriptGetStockRamCost); + checkTixApiAccess("getStockSaleGain"); + const stock = getStockFromSymbol(symbol, "getStockSaleGain"); + shares = Math.round(shares); + + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return 0; + } + + const res = getSellTransactionGain(stock, shares, pos); + if (res == null) { return 0; } + + return res; + }, buyStock : function(symbol, shares) { if (workerScript.checkingRam) { return updateStaticRam("buyStock", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("buyStock", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use buyStock()"); - } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into buyStock()"); - } + checkTixApiAccess("buyStock"); + const stock = getStockFromSymbol(symbol, "buyStock"); const res = buyStock(stock, shares, workerScript); return res ? stock.price : 0; @@ -1655,77 +1731,57 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("sellStock", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("sellStock", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use sellStock()"); - } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into sellStock()"); - } - + checkTixApiAccess("sellStock"); + const stock = getStockFromSymbol(symbol, "sellStock"); const res = sellStock(stock, shares, workerScript); return res ? stock.price : 0; }, - shortStock(symbol, shares) { + shortStock : function(symbol, shares) { if (workerScript.checkingRam) { return updateStaticRam("shortStock", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("shortStock", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use shortStock()"); - } + checkTixApiAccess("shortStock"); if (Player.bitNodeN !== 8) { if (!(hasWallStreetSF && wallStreetSFLvl >= 2)) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Cannot use shortStock(). You must either be in BitNode-8 or you must have Level 2 of Source-File 8"); } } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into shortStock()"); - } + const stock = getStockFromSymbol(symbol, "shortStock"); const res = shortStock(stock, shares, workerScript); return res ? stock.price : 0; }, - sellShort(symbol, shares) { + sellShort : function(symbol, shares) { if (workerScript.checkingRam) { return updateStaticRam("sellShort", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("sellShort", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use sellShort()"); - } + checkTixApiAccess("sellShort"); if (Player.bitNodeN !== 8) { if (!(hasWallStreetSF && wallStreetSFLvl >= 2)) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Cannot use sellShort(). You must either be in BitNode-8 or you must have Level 2 of Source-File 8"); } } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into sellShort()"); - } + const stock = getStockFromSymbol(symbol, "sellShort"); const res = sellShort(stock, shares, workerScript); return res ? stock.price : 0; }, - placeOrder(symbol, shares, price, type, pos) { + placeOrder : function(symbol, shares, price, type, pos) { if (workerScript.checkingRam) { return updateStaticRam("placeOrder", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("placeOrder", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use placeOrder()"); - } + checkTixApiAccess("placeOrder"); if (Player.bitNodeN !== 8) { if (!(hasWallStreetSF && wallStreetSFLvl >= 3)) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Cannot use placeOrder(). You must either be in BitNode-8 or have Level 3 of Source-File 8"); } } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into placeOrder()"); - } + const stock = getStockFromSymbol(symbol, "placeOrder"); + var orderType, orderPos; type = type.toLowerCase(); if (type.includes("limit") && type.includes("buy")) { @@ -1751,23 +1807,18 @@ function NetscriptFunctions(workerScript) { return placeOrder(stock, shares, price, orderType, orderPos, workerScript); }, - cancelOrder(symbol, shares, price, type, pos) { + cancelOrder : function(symbol, shares, price, type, pos) { if (workerScript.checkingRam) { return updateStaticRam("cancelOrder", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("cancelOrder", CONSTANTS.ScriptBuySellStockRamCost); - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use cancelOrder()"); - } + checkTixApiAccess("cancelOrder"); if (Player.bitNodeN !== 8) { if (!(hasWallStreetSF && wallStreetSFLvl >= 3)) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Cannot use cancelOrder(). You must either be in BitNode-8 or have Level 3 of Source-File 8"); } } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into cancelOrder()"); - } + const stock = getStockFrom(symbol, "cancelOrder"); if (isNaN(shares) || isNaN(price)) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid shares or price argument passed into cancelOrder(). Must be numeric"); } @@ -1844,10 +1895,8 @@ function NetscriptFunctions(workerScript) { if (!Player.has4SDataTixApi) { throw makeRuntimeRejectMsg(workerScript, "You don't have 4S Market Data TIX API Access! Cannot use getStockVolatility()"); } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into getStockVolatility()"); - } + const stock = getStockFromSymbol(symbol, "getStockVolatility"); + return stock.mv / 100; // Convert from percentage to decimal }, getStockForecast : function(symbol) { @@ -1858,10 +1907,8 @@ function NetscriptFunctions(workerScript) { if (!Player.has4SDataTixApi) { throw makeRuntimeRejectMsg(workerScript, "You don't have 4S Market Data TIX API Access! Cannot use getStockForecast()"); } - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into getStockForecast()"); - } + const stock = getStockFromSymbol(symbol, "getStockForecast"); + var forecast = 50; stock.b ? forecast += stock.otlkMag : forecast -= stock.otlkMag; return forecast / 100; // Convert from percentage to decimal @@ -1871,10 +1918,7 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("purchase4SMarketData", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("purchase4SMarketData", CONSTANTS.ScriptBuySellStockRamCost); - - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use purchase4SMarketData()"); - } + checkTixApiAccess("purchase4SMarketData"); if (Player.has4SData) { if (workerScript.shouldLog("purchase4SMarketData")) { @@ -1902,10 +1946,7 @@ function NetscriptFunctions(workerScript) { return updateStaticRam("purchase4SMarketDataTixApi", CONSTANTS.ScriptBuySellStockRamCost); } updateDynamicRam("purchase4SMarketDataTixApi", CONSTANTS.ScriptBuySellStockRamCost); - - if (!Player.hasTixApiAccess) { - throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use purchase4SMarketDataTixApi()"); - } + checkTixApiAccess("purchase4SMarketDataTixApi"); if (Player.has4SDataTixApi) { if (workerScript.shouldLog("purchase4SMarketDataTixApi")) { diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index 479590380..1d36dc9f4 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -182,7 +182,7 @@ export class Stock { this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5; // Max Shares (Outstanding shares) is a percentage of total shares - const outstandingSharePercentage: number = 0.15; + const outstandingSharePercentage: number = 0.2; this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5; } diff --git a/src/StockMarket/data/InitStockMetadata.ts b/src/StockMarket/data/InitStockMetadata.ts index ab0ce50ea..64b5ebe90 100644 --- a/src/StockMarket/data/InitStockMetadata.ts +++ b/src/StockMarket/data/InitStockMetadata.ts @@ -33,8 +33,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.AevumECorp], }, @@ -59,8 +59,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.Sector12MegaCorp], }, @@ -85,8 +85,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.Sector12BladeIndustries], }, @@ -111,8 +111,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.AevumClarkeIncorporated], }, @@ -137,8 +137,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated], }, @@ -163,8 +163,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.Sector12FourSigma], }, @@ -189,8 +189,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational], }, @@ -215,8 +215,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 1, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.AevumFulcrumTechnologies], }, @@ -241,8 +241,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.IshimaStormTechnologies], }, @@ -267,8 +267,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.NewTokyoDefComm], }, @@ -293,8 +293,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.VolhavenHeliosLabs], }, @@ -319,8 +319,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.NewTokyoVitaLife], }, @@ -345,8 +345,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 3, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems], }, @@ -371,8 +371,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.Sector12UniversalEnergy], }, @@ -397,8 +397,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 3, }, shareTxForMovement: { - max: 14e3, - min: 7e3, + max: 42e3, + min: 14e3, }, symbol: StockSymbols[LocationName.AevumAeroCorp], }, @@ -423,8 +423,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 14e3, - min: 7e3, + max: 42e3, + min: 14e3, }, symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems], }, @@ -449,8 +449,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 14e3, - min: 7e3, + max: 42e3, + min: 14e3, }, symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems], }, @@ -475,8 +475,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 14e3, - min: 7e3, + max: 42e3, + min: 14e3, }, symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals], }, @@ -501,8 +501,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 14e3, - min: 7e3, + max: 42e3, + min: 14e3, }, symbol: StockSymbols[LocationName.IshimaNovaMedical], }, @@ -527,8 +527,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 6e3, - min: 2e3, + max: 18e3, + min: 4e3, }, symbol: StockSymbols[LocationName.AevumWatchdogSecurity], }, @@ -553,8 +553,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 12e3, - min: 6e3, + max: 36e3, + min: 12e3, }, symbol: StockSymbols[LocationName.VolhavenLexoCorp], }, @@ -579,8 +579,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 3, }, shareTxForMovement: { - max: 20e3, - min: 10e3, + max: 42e3, + min: 20e3, }, symbol: StockSymbols[LocationName.AevumRhoConstruction], }, @@ -605,8 +605,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.Sector12AlphaEnterprises], }, @@ -631,8 +631,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities], }, @@ -657,8 +657,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 15e3, - min: 10e3, + max: 42e3, + min: 20e3, }, symbol: StockSymbols[LocationName.VolhavenCompuTek], }, @@ -683,8 +683,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 6e3, - min: 3e3, + max: 18e3, + min: 6e3, }, symbol: StockSymbols[LocationName.AevumNetLinkTechnologies], }, @@ -709,8 +709,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 4, }, shareTxForMovement: { - max: 10e3, - min: 5e3, + max: 30e3, + min: 10e3, }, symbol: StockSymbols[LocationName.IshimaOmegaSoftware], }, @@ -735,8 +735,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 6, }, shareTxForMovement: { - max: 20e3, - min: 10e3, + max: 60e3, + min: 20e3, }, symbol: StockSymbols[LocationName.Sector12FoodNStuff], }, @@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 6, }, shareTxForMovement: { - max: 8e3, - min: 4e3, + max: 28e3, + min: 8e3, }, symbol: StockSymbols["Sigma Cosmetics"], }, @@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 6, }, shareTxForMovement: { - max: 7e3, - min: 3e3, + max: 21e3, + min: 6e3, }, symbol: StockSymbols["Joes Guns"], }, @@ -813,8 +813,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 5, }, shareTxForMovement: { - max: 8e3, - min: 4e3, + max: 24e3, + min: 8e3, }, symbol: StockSymbols["Catalyst Ventures"], }, @@ -839,8 +839,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 3, }, shareTxForMovement: { - max: 25e3, - min: 15e3, + max: 72e3, + min: 30e3, }, symbol: StockSymbols["Microdyne Technologies"], }, @@ -865,8 +865,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 2, }, shareTxForMovement: { - max: 25e3, - min: 15e3, + max: 72e3, + min: 30e3, }, symbol: StockSymbols["Titan Laboratories"], }, diff --git a/src/StockMarket/ui/InfoAndPurchases.tsx b/src/StockMarket/ui/InfoAndPurchases.tsx index 11b33cad5..153934cc1 100644 --- a/src/StockMarket/ui/InfoAndPurchases.tsx +++ b/src/StockMarket/ui/InfoAndPurchases.tsx @@ -39,16 +39,6 @@ export class InfoAndPurchases extends React.Component { this.purchase4SMarketDataTixApiAccess = this.purchase4SMarketDataTixApiAccess.bind(this); } - shouldComponentUpdate(nextProps: IProps) { - // This only need to rerender if the player has purchased something new - if (this.props.p.hasWseAccount !== nextProps.p.hasWseAccount) { return true; } - if (this.props.p.hasTixApiAccess !== nextProps.p.hasTixApiAccess) { return true; } - if (this.props.p.has4SData !== nextProps.p.has4SData) { return true; } - if (this.props.p.has4SDataTixApi !== nextProps.p.has4SDataTixApi) { return true; } - - return false; - } - handleClick4SMarketDataHelpTip() { dialogBoxCreate( "Access to the 4S Market Data feed will display two additional pieces " + diff --git a/src/StockMarket/ui/StockTicker.tsx b/src/StockMarket/ui/StockTicker.tsx index 8be9c3b06..5929e8f82 100644 --- a/src/StockMarket/ui/StockTicker.tsx +++ b/src/StockMarket/ui/StockTicker.tsx @@ -109,10 +109,12 @@ export class StockTicker extends React.Component { const stock = this.props.stock; const qty: number = this.getQuantity(); if (isNaN(qty)) { return ""; } - const cost = getBuyTransactionCost(this.props.stock, qty, this.state.position); + + const cost = getBuyTransactionCost(stock, qty, this.state.position); if (cost == null) { return ""; } - let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares will cost ${numeralWrapper.formatMoney(cost)}. `; + let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` + + `will cost ${numeralWrapper.formatMoney(cost)}. `; const causesMovement = qty > stock.shareTxUntilMovement; if (causesMovement) { @@ -130,10 +132,22 @@ export class StockTicker extends React.Component { const stock = this.props.stock; const qty: number = this.getQuantity(); if (isNaN(qty)) { return ""; } - const cost = getSellTransactionGain(this.props.stock, qty, this.state.position); + + if (this.state.position === PositionTypes.Long) { + if (qty > stock.playerShares) { + return `You do not have this many shares in the Long position`; + } + } else { + if (qty > stock.playerShortShares) { + return `You do not have this many shares in the Short position`; + } + } + + const cost = getSellTransactionGain(stock, qty, this.state.position); if (cost == null) { return ""; } - let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares will result in a gain of ${numeralWrapper.formatMoney(cost)}. `; + let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` + + `will result in a gain of ${numeralWrapper.formatMoney(cost)}. `; const causesMovement = qty > stock.shareTxUntilMovement; if (causesMovement) { @@ -264,7 +278,7 @@ export class StockTicker extends React.Component { handlePositionTypeChange(e: React.ChangeEvent) { const val = e.target.value; - if (val === "Short") { + if (val === PositionTypes.Short) { this.setState({ position: PositionTypes.Short, }); @@ -369,19 +383,19 @@ export class StockTicker extends React.Component { panelContent={
- + { this.hasShortAccess() && - + } - { this.hasOrderAccess() && @@ -402,7 +416,7 @@ export class StockTicker extends React.Component {

WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares will affect the stock's price. This applies during the transaction itself as well. See Investopedia - for more details. + for more details.

} diff --git a/src/StockMarket/ui/StockTickerPositionText.tsx b/src/StockMarket/ui/StockTickerPositionText.tsx index 362709259..a8ea19d18 100644 --- a/src/StockMarket/ui/StockTickerPositionText.tsx +++ b/src/StockMarket/ui/StockTickerPositionText.tsx @@ -32,7 +32,7 @@ export class StockTickerPositionText extends React.Component { return (

- Short Position: + Long Position: Shares in the long position will increase in value if the price of the corresponding stock increases