Fixed Stock Market UI. Added documentation for stock market changes

This commit is contained in:
danielyxie 2019-04-28 23:20:27 -07:00 committed by danielyxie
parent 3a601a015d
commit dd9df0a18c
14 changed files with 353 additions and 213 deletions

@ -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,15 +25,60 @@ 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
.. 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
.. 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
@ -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.

@ -18,8 +18,12 @@ access even after you 'reset' by installing Augmentations
getStockSymbols() <tixapi/getStockSymbols>
getStockPrice() <tixapi/getStockPrice>
getStockAskPrice() <tixapi/getStockAskPrice>
getStockBidPrice() <tixapi/getStockBidPrice>
getStockPosition() <tixapi/getStockPosition>
getStockMaxShares() <tixapi/getStockMaxShares>
getStockPurchaseCost() <tixapi/getStockPurchaseCost>
getStockSaleGain() <tixapi/getStockSaleGain>
buyStock() <tixapi/buyStock>
sellStock() <tixapi/sellStock>
shortStock() <tixapi/shortStock>

@ -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.

@ -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.

@ -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");

@ -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 <gameplay_stock_market_spread>`,
:ref:`large transactions influencing the price of the stock <gameplay_stock_spread_price_movement>`
and commission fees.

@ -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 <gameplay_stock_market_spread>`,
:ref:`large transactions influencing the price of the stock <gameplay_stock_spread_price_movement>`
and commission fees.

@ -275,35 +275,15 @@ export let CONSTANTS: IMap<any> = {
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
`
}

@ -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")) {

@ -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;
}

@ -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"],
},

@ -39,16 +39,6 @@ export class InfoAndPurchases extends React.Component<IProps, any> {
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 " +

@ -109,10 +109,12 @@ export class StockTicker extends React.Component<IProps, IState> {
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<IProps, IState> {
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<IProps, IState> {
handlePositionTypeChange(e: React.ChangeEvent<HTMLSelectElement>) {
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<IProps, IState> {
panelContent={
<div>
<input
className={"stock-market-input"}
className="stock-market-input"
onChange={this.handleQuantityChange}
placeholder={"Quantity (Shares)"}
placeholder="Quantity (Shares)"
value={this.state.qty}
/>
<select className={"stock-market-input dropdown"} onChange={this.handlePositionTypeChange} value={this.state.position}>
<option value={"Long"}>Long</option>
<select className="stock-market-input dropdown" onChange={this.handlePositionTypeChange} value={this.state.position}>
<option value={PositionTypes.Long}>Long</option>
{
this.hasShortAccess() &&
<option value={"Short"}>Short</option>
<option value={PositionTypes.Short}>Short</option>
}
</select>
<select className={"stock-market-input dropdown"} onChange={this.handleOrderTypeChange} value={this.state.orderType}>
<select className="stock-market-input dropdown" onChange={this.handleOrderTypeChange} value={this.state.orderType}>
<option value={SelectorOrderType.Market}>{SelectorOrderType.Market}</option>
{
this.hasOrderAccess() &&

@ -32,7 +32,7 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
return (
<div>
<h3 className={"tooltip"}>
Short Position:
Long Position:
<span className={"tooltiptext"}>
Shares in the long position will increase in value if the price
of the corresponding stock increases