From e4f02b298bc4661db8cbbe27b57bd7b242f589bc Mon Sep 17 00:00:00 2001 From: Daniel Xie Date: Fri, 24 Aug 2018 15:44:48 -0500 Subject: [PATCH] Added Four Sigma (4S) Market Data feature, and its Netscript TIX API functions as well --- css/menupages.scss | 11 +++ css/styles.scss | 16 +++- doc/source/netscriptbladeburnerapi.rst | 28 +++--- doc/source/netscriptixapi.rst | 37 +++++++- index.html | 31 +++++-- package.json | 2 +- src/Constants.js | 12 ++- src/NetscriptFunctions.js | 47 +++++++++- src/StockMarket.js | 124 ++++++++++++++++++++----- 9 files changed, 261 insertions(+), 47 deletions(-) diff --git a/css/menupages.scss b/css/menupages.scss index 3cac489fe..7ed7b1d19 100644 --- a/css/menupages.scss +++ b/css/menupages.scss @@ -552,6 +552,17 @@ #stock-market-container { position: fixed; padding: 6px; + p { + font-size: $defaultFontSize * 0.8125; + } + a { + font-size: $defaultFontSize * 0.875; + } + h2 { + margin-top:10px; + margin-left:10px; + display:block; + } } #stock-market-container p { diff --git a/css/styles.scss b/css/styles.scss index 3c41b82dd..feebc7177 100644 --- a/css/styles.scss +++ b/css/styles.scss @@ -310,11 +310,23 @@ a:visited { display: inline-block; } -.help-tip:hover { +.help-tip-big { + content: '?'; + padding: 3px; + margin-left: 3px; + color: #fff; + border: 1px solid #fff; + border-radius: 8px; + display: inline-block; +} + +.help-tip:hover, +.help-tip-big:hover { background-color: #888; } -.help-tip:active { +.help-tip:active, +.help-tip-big:active { @include boxShadow(inset 0 1px 4px rgba(0, 0, 0, 0.6)); } diff --git a/doc/source/netscriptbladeburnerapi.rst b/doc/source/netscriptbladeburnerapi.rst index 68cbebeee..1c211c01a 100644 --- a/doc/source/netscriptbladeburnerapi.rst +++ b/doc/source/netscriptbladeburnerapi.rst @@ -143,7 +143,9 @@ getActionEstimatedSuccessChance :param string type: Type of action. See :ref:`bladeburner_action_types` :param string name: Name of action. Must be an exact match - Returns the estimated success chance for the specified action + Returns the estimated success chance for the specified action. This chance + is returned as a decimal value, NOT a percentage (e.g. if you have an estimated + success chance of 80%, then this function will return 0.80, NOT 80). getActionCountRemaining ----------------------- @@ -240,17 +242,17 @@ getSkillLevel This function returns your level in the specified skill. The function returns -1 if an invalid skill name is passed in - + getSkillUpgradeCost ------------------- .. js:function:: getSkillUpgradeCost(skillName="") :param string skillName: Name of skill. Case-sensitive and must be an exact match - - This function returns the number of skill points needed to upgrade the + + This function returns the number of skill points needed to upgrade the specified skill. - + The function returns -1 if an invalid skill name is passed in. upgradeSkill @@ -369,21 +371,21 @@ joinBladeburnerDivision are already a member. Returns false otherwise - + getBonusTime ------------ .. js:function:: getBonusTime() Returns the amount of accumulated "bonus time" (seconds) for the Bladeburner mechanic. - - "Bonus time" is accumulated when the game is offline or if the game is - inactive in the browser. - - "Bonus time" makes the game progress faster, up to 5x the normal speed. + + "Bonus time" is accumulated when the game is offline or if the game is + inactive in the browser. + + "Bonus time" makes the game progress faster, up to 5x the normal speed. For example, if an action takes 30 seconds to complete but you've accumulated - over 30 seconds in bonus time, then the action will only take 6 seconds - in real life to complete. + over 30 seconds in bonus time, then the action will only take 6 seconds + in real life to complete. Examples -------- diff --git a/doc/source/netscriptixapi.rst b/doc/source/netscriptixapi.rst index 8f34f99ae..0f767a9ee 100644 --- a/doc/source/netscriptixapi.rst +++ b/doc/source/netscriptixapi.rst @@ -159,7 +159,7 @@ cancelOrder :param number price: Execution price for the order :param string type: Type of order. It must specify "limit" or "stop", and must also specify "buy" or "sell". This is NOT case-sensitive. Here are four examples that will work: - + * limitbuy * limitsell * stopbuy @@ -172,3 +172,38 @@ cancelOrder Cancels an oustanding Limit or Stop order on the stock market. The ability to use limit and stop orders is **not** immediately available to the player and must be unlocked later on in the game. + +getStockVolatility +------------------ + +.. js:function:: getStockVolatility(sym) + + :param string sym: Symbol of stock + + Returns the volatility of the specified stock. + + Volatility represents the maximum percentage by which a stock's price can + change every tick. The volatility is returned as a decimal value, NOT + a percentage (e.g. if a stock has a volatility of 3%, then this function will + return 0.03, NOT 3). + + In order to use this function, you must first purchase access to the Four Sigma (4S) + Market Data TIX API. + +getStockForecast +---------------- + +.. js:function:: getStockForecast(sym) + + :param string sym: Symbol of stock + + Returns the probability that the specified stock's price will increase + (as opposed to decrease) during the next tick. + + The probability is returned as a decimal value, NOT a percentage (e.g. if a + stock has a 60% chance of increasing, then this function will return 0.6, + NOT 60). + + In other words, if this function returned 0.30 for a stock, then this means + that the stock's price has a 30% chance of increasing and a 70% chance of + decreasing during the next tick. diff --git a/index.html b/index.html index 61e1fd16a..19f370c7f 100644 --- a/index.html +++ b/index.html @@ -679,18 +679,37 @@ after you 'reset' by installing Augmentations.

Buy WSE Account + Investopedia + +

Trade Information eXchange (TIX) API

- You can also purchase access to the World Stock Exchange's TIX API! TIX, short for - Trade Information eXchange, is the communications protocol supported by the WSE. -

- Gaining access to the TIX API lets you write code to build automated trading - systems. In other words, you can create your own algorithmic trading strategies! + TIX, short for Trade Information eXchange, is the communications protocol supported by the WSE. + Purchasing access to the TIX API lets you write code to create your own algorithmic/automated + trading strategies.

If you purchase access to the TIX API, you will retain that access even after you 'reset' by installing Augmentations.

Buy Trade Information eXchange (TIX) API Access - Investopedia + +

Four Sigma (4S) Market Data Feed

+

+ Four Sigma's (4S) Market Data Feed provides information about stocks + that will help your trading strategies. +

+ If you purchase access to 4S Market Data and/or the 4S TIX API, you will + retain that access even after you 'reset' by installing Augmentations. +

+ + + Buy 4S Market Data Feed + +
?
+ + + Buy 4S Market Data TIX API Access + +

Expand tickers diff --git a/package.json b/package.json index 43f25393d..84d48e9fe 100644 --- a/package.json +++ b/package.json @@ -101,5 +101,5 @@ "watch": "webpack --watch --mode production", "watch:dev": "webpack --watch --mode development" }, - "version": "0.35.1" + "version": "0.40.2" } diff --git a/src/Constants.js b/src/Constants.js index bdbdc3057..0c8b63730 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -115,6 +115,8 @@ let CONSTANTS = { //Stock market constants WSEAccountCost: 200e6, TIXAPICost: 5e9, + MarketData4SCost: 1e9, + MarketDataTixApi4SCost: 20e9, StockMarketCommission: 100e3, //Hospital/Health @@ -492,9 +494,9 @@ let CONSTANTS = { LatestUpdate: "v0.40.2
" + + "------------------------------
" + "* Bladeburner Changes:
" + - "*** Added getSkillUpgradeCost() Netscript function to the API
" + - "*** Added getBonusTime() Netscript function to the API
" + + "*** Added getBonusTime(), getSkillUpgradeCost(), and getCity() Netscript functions to the API
" + "*** Buffed the effects of many Bladeburner Augmentations
" + "*** The Blade's Simulacrum Augmentation requires significantly less reputation but slightly more money
" + "*** Slightly increased the amount of successes needed for a Contract/Operation in order to increase its max level
" + @@ -504,6 +506,12 @@ let CONSTANTS = { "*** The number (count) of Operations should now increase significantly faster
" + "*** There are now, on average, more Synthoid communities in a city
" + "*** If automation is enabled (the feature in Bladeburner console), then switching to another action such as working for a company will now disable the automation
" + + "------------------------------
" + + "* Stock Market Changes:
" + + "***Added a watchlist filter feature to the UI that allows you to specify which stocks to show
" + + "***Added the Four Sigma (4S) Market Data feed, which provides volatility and price forecast information about stocks
" + + "***Added the 4S Market Data TIX API, which lets you access the aforementioned data through Netscript
" + + "------------------------------
" + "* There is now a setting for enabling/disabling the popup that appears when you are hospitalized
" + "* Bug Fix: Stock market should now be correctly initialized in BitNode-8 (by Kline-)
" + "* Bug Fix: bladeburner.getCurrentAction() should now properly an 'Idle' object rather than null (by Kline-)
" + diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index f4b7d305c..d2a42e901 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -183,7 +183,7 @@ function NetscriptFunctions(workerScript) { /** * @param {number} ram The amount of server RAM to calculate cost of. * @exception {Error} If the value passed in is not numeric, out of range, or too large of a value. - * @returns {number} The cost of + * @returns {number} The cost of */ const getPurchaseServerRamCostGuard = (ram) => { const guardedRam = Math.round(ram); @@ -1599,6 +1599,36 @@ function NetscriptFunctions(workerScript) { }; return cancelOrder(params, workerScript); }, + getStockVolatility : function(symbol) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockVolatility", CONSTANTS.ScriptBuySellStockRamCost); + } + updateDynamicRam("getStockVolatility", CONSTANTS.ScriptBuySellStockRamCost); + 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()"); + } + return stock.mv / 100; //Convert from percentage to decimal + }, + getStockForecast : function(symbol) { + if (workerScript.checkingRam) { + return updateStaticRam("getStockForecast", CONSTANTS.ScriptBuySellStockRamCost); + } + updateDynamicRam("getStockForecast", CONSTANTS.ScriptBuySellStockRamCost); + 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()"); + } + var forecast = 50; + stock.b ? forecast += stock.otlkMag : forecast -= stock.otlkMag; + return forecast / 100; //Convert from percentage to decimal + }, getPurchasedServerLimit : function() { if (workerScript.checkingRam) { return updateStaticRam("getPurchasedServerLimit", CONSTANTS.ScriptGetPurchasedServerLimit); @@ -3744,6 +3774,21 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "getCityChaos() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " + "at the Bladeburner division or because you do not have Source-File 7"); }, + getCity : function() { + if (workerScript.checkingRam) { + return updateStaticRam("getCity", CONSTANTS.ScriptBladeburnerApiBaseRamCost); + } + updateDynamicRam("getCity", CONSTANTS.ScriptBladeburnerApiBaseRamCost); + if (Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || hasBladeburner2079SF)) { + try { + return Player.bladeburner.city; + } catch(e) { + throw makeRuntimeRejectMsg(workerScript, "Bladeburner.getCity() failed with exception: " + e); + } + } + throw makeRuntimeRejectMsg(workerScript, "getCity() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " + + "at the Bladeburner division or because you do not have Source-File 7"); + }, switchCity : function(cityName) { if (workerScript.checkingRam) { return updateStaticRam("switchCity", CONSTANTS.ScriptBladeburnerApiBaseRamCost); diff --git a/src/StockMarket.js b/src/StockMarket.js index 6bd7f6531..229b16946 100755 --- a/src/StockMarket.js +++ b/src/StockMarket.js @@ -13,6 +13,7 @@ import numeral from "numeral/min/numeral.min"; import {exceptionAlert} from "../utils/helpers/exceptionAlert"; import {getRandomInt} from "../utils/helpers/getRandomInt"; import {KEY} from "../utils/helpers/keyCodes"; +import {createElement} from "../utils/uiHelpers/createElement"; import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement"; import {removeElementById} from "../utils/uiHelpers/removeElementById"; import {yesNoBoxCreate, yesNoTxtInpBoxCreate, @@ -697,20 +698,31 @@ var stockMarketContentCreated = false; var stockMarketPortfolioMode = false; var COMM = CONSTANTS.StockMarketCommission; function displayStockMarketContent() { - if (Player.hasWseAccount == null) {Player.hasWseAccount = false;} + if (Player.hasWseAccount == null) {Player.hasWseAccount = false;} if (Player.hasTixApiAccess == null) {Player.hasTixApiAccess = false;} + if (Player.has4SData == null) {Player.has4SData = false;} + if (Player.has4SDataTixApi == null) {Player.has4SDataTixApi = false;} + + function stylePurchaseButton(btn, cost, flag, initMsg, purchasedMsg) { + btn.innerText = initMsg; + btn.classList.remove("a-link-button"); + btn.classList.remove("a-link-button-bought"); + btn.classList.remove("a-link-button-inactive"); + if (!flag && Player.money.gte(cost)) { + btn.classList.add("a-link-button"); + } else if (flag) { + btn.innerText = purchasedMsg; + btn.classList.add("a-link-button-bought"); + } else { + btn.classList.add("a-link-button-inactive"); + } + } //Purchase WSE Account button var wseAccountButton = clearEventListeners("stock-market-buy-account"); - wseAccountButton.innerText = "Buy WSE Account - " + numeral(CONSTANTS.WSEAccountCost).format('($0.000a)'); - if (!Player.hasWseAccount && Player.money.gte(CONSTANTS.WSEAccountCost)) { - wseAccountButton.setAttribute("class", "a-link-button"); - } else if (Player.hasWseAccount){ - wseAccountButton.innerText = "WSE Account - Purchased"; - wseAccountButton.setAttribute("class", "a-link-button-bought"); - } else { - wseAccountButton.setAttribute("class", "a-link-button-inactive"); - } + stylePurchaseButton(wseAccountButton, CONSTANTS.WSEAccountCost, Player.hasWseAccount, + "Buy WSE Account - " + numeral(CONSTANTS.WSEAccountCost).format('($0.000a)'), + "WSE Account - Purchased"); wseAccountButton.addEventListener("click", function() { Player.hasWseAccount = true; initStockMarket(); @@ -722,16 +734,9 @@ function displayStockMarketContent() { //Purchase TIX API Access account var tixApiAccessButton = clearEventListeners("stock-market-buy-tix-api"); - tixApiAccessButton.innerText = "Buy Trade Information eXchange (TIX) API Access - " + - numeral(CONSTANTS.TIXAPICost).format('($0.000a)'); - if (!Player.hasTixApiAccess && Player.money.gte(CONSTANTS.TIXAPICost)) { - tixApiAccessButton.setAttribute("class", "a-link-button"); - } else if(Player.hasTixApiAccess) { - tixApiAccessButton.innerText = "Trade Information eXchange (TIX) API Access - Purchased" - tixApiAccessButton.setAttribute("class", "a-link-button-bought"); - } else { - tixApiAccessButton.setAttribute("class", "a-link-button-inactive"); - } + stylePurchaseButton(tixApiAccessButton, CONSTANTS.TIXAPICost, Player.hasTixApiAccess, + "Buy Trade Information eXchange (TIX) API Access - " + numeral(CONSTANTS.TIXAPICost).format('($0.000a)'), + "TIX API Access - Purchased"); tixApiAccessButton.addEventListener("click", function() { Player.hasTixApiAccess = true; Player.loseMoney(CONSTANTS.TIXAPICost); @@ -739,9 +744,76 @@ function displayStockMarketContent() { return false; }); + //Purchase Four Sigma Market Data Feed + var marketDataButton = clearEventListeners("stock-market-buy-4s-data"); + stylePurchaseButton(marketDataButton, CONSTANTS.MarketData4SCost, Player.has4SData, + "Buy 4S Market Data Access - " + numeral(CONSTANTS.MarketData4SCost).format('($0.000a)'), + "4S Market Data - Purchased"); + marketDataButton.addEventListener("click", function() { + Player.has4SData = true; + Player.loseMoney(CONSTANTS.MarketData4SCost); + displayStockMarketContent(); + return false; + }); + marketDataButton.appendChild(createElement("span", { + class:"tooltiptext", + innerText:"Lets you view additional pricing and volatility information about stocks" + })); + marketDataButton.style.marginRight = "2px"; //Adjusts following help tip to be slightly closer + + //4S Market Data Help Tip + var marketDataHelpTip = clearEventListeners("stock-market-4s-data-help-tip"); + marketDataHelpTip.style.marginTop = "10px"; + marketDataHelpTip.addEventListener("click", ()=>{ + dialogBoxCreate("Access to the 4S Market Data feed will display two additional pieces " + + "of information about each stock: Price Forecast & Volatility

" + + "Price Forecast indicates the probability the stock has of increasing or " + + "decreasing. A '+' forecast means the stock has a higher chance of increasing " + + "than decreasing, and a '-' means the opposite. The number of '+/-' symbols " + + "is used to illustrate the magnitude of these probabilities. For example, " + + "'+++' means that the stock has a significantly higher chance of increasing " + + "than decreasing, while '+' means that the stock only has a slightly higher chance " + + "of increasing than decreasing.

" + + "Volatility represents the maximum percentage by which a stock's price " + + "can change every tick (a tick occurs every few seconds while the game " + + "is running).

" + + "A stock's price forecast can change over time. This is also affected by volatility. " + + "The more volatile a stock is, the more its price forecast will change."); + return false; + }); + + //Purchase Four Sigma Market Data TIX API (Requires TIX API Access) + var marketDataTixButton = clearEventListeners("stock-market-buy-4s-tix-api"); + stylePurchaseButton(marketDataTixButton, CONSTANTS.MarketDataTixApi4SCost, Player.has4SDataTixApi, + "Buy 4S Market Data TIX API Access - " + numeral(CONSTANTS.MarketDataTixApi4SCost).format('($0.000a)'), + "4S Market Data TIX API - Purchased"); + if (Player.hasTixApiAccess) { + marketDataTixButton.addEventListener("click", function() { + Player.has4SDataTixApi = true; + Player.loseMoney(CONSTANTS.MarketDataTixApi4SCost); + displayStockMarketContent(); + return false; + }); + marketDataTixButton.appendChild(createElement("span", { + class:"tooltiptext", + innerText:"Lets you access 4S Market Data through Netscript" + })); + } else { + marketDataTixButton.classList.remove("a-link-button"); + marketDataTixButton.classList.remove("a-link-button-bought"); + marketDataTixButton.classList.remove("a-link-button-inactive"); + marketDataTixButton.classList.add("a-link-button-inactive"); + marketDataTixButton.appendChild(createElement("span", { + class:"tooltiptext", + innerText:"Requires TIX API Access" + })); + } + + var stockList = document.getElementById("stock-market-list"); if (stockList == null) {return;} + //If Player doesn't have account, clear stocks UI and return if (!Player.hasWseAccount) { stockMarketContentCreated = false; while (stockList.firstChild) { @@ -1234,7 +1306,17 @@ function updateStockTicker(stock, increase) { } return; } - hdr.innerHTML = stock.name + " - " + stock.symbol + " - " + numeral(stock.price).format('($0.000a)'); + let hdrText = stock.name + " (" + stock.symbol + ") - " + numeral(stock.price).format('($0.000a)'); + if (Player.has4SData) { + hdrText += " - Volatility: " + numeral(stock.mv).format('0,0.00') + "%" + + " - Price Forecast: "; + if (stock.b) { + hdrText += "+".repeat(Math.floor(stock.otlkMag/10) + 1); + } else { + hdrText += "-".repeat(Math.floor(stock.otlkMag/10) + 1); + } + } + hdr.innerText = hdrText; if (increase != null) { increase ? hdr.style.color = "#66ff33" : hdr.style.color = "red"; }