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