Finished React components for new Stock Market UI

This commit is contained in:
danielyxie 2019-04-23 01:23:08 -07:00 committed by danielyxie
parent 6b3646e981
commit 4809a21e38
21 changed files with 1173 additions and 207 deletions

@ -17,11 +17,15 @@
}
}
#stock-market-list li {
#stock-market-list {
list-style: none;
li {
button {
font-size: $defaultFontSize;
}
}
}
#stock-market-container p {
padding: 6px;
@ -50,11 +54,21 @@
.stock-market-position-text {
color: #fff;
display: inline-block;
p {
color: #fff;
display: block;
}
}
.stock-market-order-list {
overflow-y: auto;
max-height: 100px;
li {
color: #fff;
padding: 4px;
}
}
.stock-market-order-cancel-btn {

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "0.45.0",
"version": "0.46.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -77,7 +77,6 @@ import {
StockMarket,
StockSymbols,
SymbolToStockMap,
initStockMarket,
initSymbolToStockMap,
buyStock,
sellStock,

@ -32,7 +32,10 @@ export interface IPlayer {
factions: string[];
firstTimeTraveled: boolean;
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
has4SData: boolean;
has4SDataTixApi: boolean;
hashManager: HashManager;
hasTixApiAccess: boolean;
hasWseAccount: boolean;
homeComputer: string;
hp: number;

@ -0,0 +1,5 @@
import { Order } from "./Order";
export interface IOrderBook {
[key: string]: Order[];
}

@ -0,0 +1,10 @@
import { IOrderBook } from "./IOrderBook";
import { Stock } from "./Stock";
export type IStockMarket = {
[key: string]: Stock;
} & {
lastUpdate: number;
storedCycles: number;
Orders: IOrderBook;
}

@ -73,6 +73,11 @@ export class Stock {
*/
readonly cap: number;
/**
* Stocks previous share price
*/
lastPrice: number;
/**
* Maximum number of shares that player can own (both long and short combined)
*/
@ -114,11 +119,6 @@ export class Stock {
*/
playerShortShares: number;
/**
* The HTML element that displays the stock's info in the UI
*/
posTxtEl: HTMLElement | null;
/**
* Stock's share price
*/
@ -162,6 +162,7 @@ export class Stock {
this.name = p.name;
this.symbol = p.symbol;
this.price = toNumber(p.initPrice);
this.lastPrice = this.price;
this.playerShares = 0;
this.playerAvgPx = 0;
this.playerShortShares = 0;
@ -182,8 +183,11 @@ export class Stock {
// Max Shares (Outstanding shares) is a percentage of total shares
const outstandingSharePercentage: number = 0.15;
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
}
this.posTxtEl = null;
changePrice(newPrice: number): void {
this.lastPrice = this.price;
this.price = newPrice;
}
/**

@ -8,6 +8,7 @@ import {
getStockMarket4SDataCost,
getStockMarket4STixApiCost
} from "./StockMarketCosts";
import { InitStockMetadata } from "./data/InitStockMetadata";
import { StockSymbols } from "./data/StockSymbols";
import { CONSTANTS } from "../Constants";
@ -155,16 +156,6 @@ function executeOrder(order) {
export let StockMarket = {}; // Maps full stock name -> Stock object
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
let formatHelpData = {
longestName: 0,
longestSymbol: 0,
};
for (const key in StockSymbols) {
formatHelpData.longestName = key.length > formatHelpData.longestName ? key.length : formatHelpData.longestName;
formatHelpData.longestSymbol = StockSymbols[key].length > formatHelpData.longestSymbol ? StockSymbols[key].length : formatHelpData.longestSymbol;
}
export function loadStockMarket(saveString) {
if (saveString === "") {
StockMarket = {};
@ -174,145 +165,16 @@ export function loadStockMarket(saveString) {
}
export function initStockMarket() {
for (var stk in StockMarket) {
for (const stk in StockMarket) {
if (StockMarket.hasOwnProperty(stk)) {
delete StockMarket[stk];
}
}
const randInt = getRandomInt;
var ecorp = LocationName.AevumECorp;
var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], randInt(40, 50) / 100, true, 19, randInt(17e3, 28e3), 2.4e12);
StockMarket[ecorp] = ecorpStk;
var megacorp = LocationName.Sector12MegaCorp;
var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], randInt(40,50)/100, true, 19, randInt(24e3, 34e3), 2.4e12);
StockMarket[megacorp] = megacorpStk;
var blade = LocationName.Sector12BladeIndustries;
var bladeStk = new Stock(blade, StockSymbols[blade], randInt(70, 80)/100, true, 13, randInt(12e3, 25e3), 1.6e12);
StockMarket[blade] = bladeStk;
var clarke = LocationName.AevumClarkeIncorporated;
var clarkeStk = new Stock(clarke, StockSymbols[clarke], randInt(65, 75)/100, true, 12, randInt(10e3, 25e3), 1.5e12);
StockMarket[clarke] = clarkeStk;
var omnitek = LocationName.VolhavenOmniTekIncorporated;
var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], randInt(60, 70)/100, true, 12, randInt(32e3, 43e3), 1.8e12);
StockMarket[omnitek] = omnitekStk;
var foursigma = LocationName.Sector12FourSigma;
var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], randInt(100, 110)/100, true, 17, randInt(50e3, 80e3), 2e12);
StockMarket[foursigma] = foursigmaStk;
var kuaigong = LocationName.ChongqingKuaiGongInternational;
var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], randInt(75, 85)/100, true, 10, randInt(16e3, 28e3), 1.9e12);
StockMarket[kuaigong] = kuaigongStk;
var fulcrum = LocationName.AevumFulcrumTechnologies;
var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], randInt(120, 130)/100, true, 16, randInt(29e3, 36e3), 2e12);
StockMarket[fulcrum] = fulcrumStk;
var storm = LocationName.IshimaStormTechnologies;
var stormStk = new Stock(storm, StockSymbols[storm], randInt(80, 90)/100, true, 7, randInt(20e3, 25e3), 1.2e12);
StockMarket[storm] = stormStk;
var defcomm = LocationName.NewTokyoDefComm;
var defcommStk = new Stock(defcomm, StockSymbols[defcomm], randInt(60, 70)/100, true, 10, randInt(6e3, 19e3), 900e9);
StockMarket[defcomm] = defcommStk;
var helios = LocationName.VolhavenHeliosLabs;
var heliosStk = new Stock(helios, StockSymbols[helios], randInt(55, 65)/100, true, 9, randInt(10e3, 18e3), 825e9);
StockMarket[helios] = heliosStk;
var vitalife = LocationName.NewTokyoVitaLife;
var vitalifeStk = new Stock(vitalife, StockSymbols[vitalife], randInt(70, 80)/100, true, 7, randInt(8e3, 14e3), 1e12);
StockMarket[vitalife] = vitalifeStk;
var icarus = LocationName.Sector12IcarusMicrosystems;
var icarusStk = new Stock(icarus, StockSymbols[icarus], randInt(60, 70)/100, true, 7.5, randInt(12e3, 24e3), 800e9);
StockMarket[icarus] = icarusStk;
var universalenergy = LocationName.Sector12UniversalEnergy;
var universalenergyStk = new Stock(universalenergy, StockSymbols[universalenergy], randInt(50, 60)/100, true, 10, randInt(16e3, 29e3), 900e9);
StockMarket[universalenergy] = universalenergyStk;
var aerocorp = LocationName.AevumAeroCorp;
var aerocorpStk = new Stock(aerocorp, StockSymbols[aerocorp], randInt(55, 65)/100, true, 6, randInt(8e3, 17e3), 640e9);
StockMarket[aerocorp] = aerocorpStk;
var omnia = LocationName.VolhavenOmniaCybersystems;
var omniaStk = new Stock(omnia, StockSymbols[omnia], randInt(65, 75)/100, true, 4.5, randInt(6e3, 15e3), 600e9);
StockMarket[omnia] = omniaStk;
var solaris = LocationName.ChongqingSolarisSpaceSystems;
var solarisStk = new Stock(solaris, StockSymbols[solaris], randInt(70, 80)/100, true, 8.5, randInt(14e3, 28e3), 705e9);
StockMarket[solaris] = solarisStk;
var globalpharm = LocationName.NewTokyoGlobalPharmaceuticals;
var globalpharmStk = new Stock(globalpharm, StockSymbols[globalpharm], randInt(55, 65)/100, true, 10.5, randInt(12e3, 30e3), 695e9);
StockMarket[globalpharm] = globalpharmStk;
var nova = LocationName.IshimaNovaMedical;
var novaStk = new Stock(nova, StockSymbols[nova], randInt(70, 80)/100, true, 5, randInt(15e3, 27e3), 600e9);
StockMarket[nova] = novaStk;
var watchdog = LocationName.AevumWatchdogSecurity;
var watchdogStk = new Stock(watchdog, StockSymbols[watchdog], randInt(240, 260)/100, true, 1.5, randInt(4e3, 8.5e3), 450e9);
StockMarket[watchdog] = watchdogStk;
var lexocorp = LocationName.VolhavenLexoCorp;
var lexocorpStk = new Stock(lexocorp, StockSymbols[lexocorp], randInt(115, 135)/100, true, 6, randInt(4.5e3, 8e3), 300e9);
StockMarket[lexocorp] = lexocorpStk;
var rho = LocationName.AevumRhoConstruction;
var rhoStk = new Stock(rho, StockSymbols[rho], randInt(50, 70)/100, true, 1, randInt(2e3, 7e3), 180e9);
StockMarket[rho] = rhoStk;
var alpha = LocationName.Sector12AlphaEnterprises;
var alphaStk = new Stock(alpha, StockSymbols[alpha], randInt(175, 205)/100, true, 10, randInt(4e3, 8.5e3), 240e9);
StockMarket[alpha] = alphaStk;
var syscore = LocationName.VolhavenSysCoreSecurities;
var syscoreStk = new Stock(syscore, StockSymbols[syscore], randInt(150, 170)/100, true, 3, randInt(3e3, 8e3), 200e9);
StockMarket[syscore] = syscoreStk;
var computek = LocationName.VolhavenCompuTek;
var computekStk = new Stock(computek, StockSymbols[computek], randInt(80, 100)/100, true, 4, randInt(1e3, 6e3), 185e9);
StockMarket[computek] = computekStk;
var netlink = LocationName.AevumNetLinkTechnologies;
var netlinkStk = new Stock(netlink, StockSymbols[netlink], randInt(400, 430)/100, true, 1, randInt(1e3, 5e3), 58e9);
StockMarket[netlink] = netlinkStk;
var omega = LocationName.IshimaOmegaSoftware;
var omegaStk = new Stock(omega, StockSymbols[omega], randInt(90, 110)/100, true, 0.5, randInt(1e3, 8e3), 60e9);
StockMarket[omega] = omegaStk;
var fns = LocationName.Sector12FoodNStuff;
var fnsStk = new Stock(fns, StockSymbols[fns], randInt(70, 80)/100, false, 1, randInt(500, 4.5e3), 45e9);
StockMarket[fns] = fnsStk;
var sigmacosm = "Sigma Cosmetics";
var sigmacosmStk = new Stock(sigmacosm, StockSymbols[sigmacosm], randInt(260, 300)/100, true, 0, randInt(1.5e3, 3.5e3), 30e9);
StockMarket[sigmacosm] = sigmacosmStk;
var joesguns = "Joes Guns";
var joesgunsStk = new Stock(joesguns, StockSymbols[joesguns], randInt(360, 400)/100, true, 1, randInt(250, 1.5e3), 42e9);
StockMarket[joesguns] = joesgunsStk;
var catalyst = "Catalyst Ventures";
var catalystStk = new Stock(catalyst, StockSymbols[catalyst], randInt(120, 175)/100, true, 13.5, randInt(250, 1.5e3), 100e9);
StockMarket[catalyst] = catalystStk;
var microdyne = "Microdyne Technologies";
var microdyneStk = new Stock(microdyne, StockSymbols[microdyne], randInt(70, 80)/100, true, 8, randInt(15e3, 30e3), 360e9);
StockMarket[microdyne] = microdyneStk;
var titanlabs = "Titan Laboratories";
var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], randInt(50, 70)/100, true, 11, randInt(12e3, 24e3), 420e9);
StockMarket[titanlabs] = titanlabsStk;
for (const metadata of InitStockMetadata) {
const name = metadata.name;
StockMarket[name] = new Stock(metadata);
}
var orders = {};
for (var name in StockMarket) {
@ -329,14 +191,14 @@ export function initStockMarket() {
}
export function initSymbolToStockMap() {
for (var name in StockSymbols) {
for (const name in StockSymbols) {
if (StockSymbols.hasOwnProperty(name)) {
var stock = StockMarket[name];
const stock = StockMarket[name];
if (stock == null) {
console.error(`Could not find Stock for ${name}`);
continue;
}
var symbol = StockSymbols[name];
const symbol = StockSymbols[name];
SymbolToStockMap[symbol] = stock;
}
}

@ -0,0 +1,11 @@
import { StockSymbols } from "./StockSymbols";
export const TickerHeaderFormatData = {
longestName: 0,
longestSymbol: 0,
}
for (const key in StockSymbols) {
TickerHeaderFormatData.longestName = Math.max(key.length, TickerHeaderFormatData.longestName);
TickerHeaderFormatData.longestSymbol = Math.max(StockSymbols[key].length, TickerHeaderFormatData.longestSymbol);
}

@ -0,0 +1,222 @@
/**
* React component for the Stock Market UI. This component displays
* general information about the stock market, buttons for the various purchases,
* and a link to the documentation (Investopedia)
*/
import * as React from "react";
import {
getStockMarket4SDataCost,
getStockMarket4STixApiCost
} from "../StockMarketCosts";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
initStockMarket: () => void;
p: IPlayer;
rerender: () => void;
}
const blockStyleMarkup = {
display: "block",
}
export class InfoAndPurchases extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
this.handleClick4SMarketDataHelpTip = this.handleClick4SMarketDataHelpTip.bind(this);
this.purchaseWseAccount = this.purchaseWseAccount.bind(this);
this.purchaseTixApiAccess = this.purchaseTixApiAccess.bind(this);
this.purchase4SMarketData = this.purchase4SMarketData.bind(this);
this.purchase4SMarketDataTixApiAccess = this.purchase4SMarketDataTixApiAccess.bind(this);
}
handleClick4SMarketDataHelpTip() {
dialogBoxCreate(
"Access to the 4S Market Data feed will display two additional pieces " +
"of information about each stock: Price Forecast & Volatility<br><br>" +
"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.<br><br>" +
"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).<br><br>" +
"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."
);
}
purchaseWseAccount() {
if (this.props.p.hasWseAccount) { return; }
if (!this.props.p.canAfford(CONSTANTS.WSEAccountCost)) { return; }
this.props.p.hasWseAccount = true;
this.props.initStockMarket();
this.props.p.loseMoney(CONSTANTS.WSEAccountCost);
this.props.rerender();
}
purchaseTixApiAccess() {
if (this.props.p.hasTixApiAccess) { return; }
if (!this.props.p.canAfford(CONSTANTS.TIXAPICost)) { return; }
this.props.p.hasTixApiAccess = true;
this.props.p.loseMoney(CONSTANTS.TIXAPICost);
this.props.rerender();
}
purchase4SMarketData() {
if (this.props.p.has4SData) { return; }
if (!this.props.p.canAfford(getStockMarket4SDataCost())) { return; }
this.props.p.has4SData = true;
this.props.p.loseMoney(getStockMarket4SDataCost());
this.props.rerender();
}
purchase4SMarketDataTixApiAccess() {
if (this.props.p.has4SDataTixApi) { return; }
if (!this.props.p.canAfford(getStockMarket4STixApiCost())) { return; }
this.props.p.has4SDataTixApi = true;
this.props.p.loseMoney(getStockMarket4STixApiCost());
this.props.rerender();
}
renderPurchaseWseAccountButton(): React.ReactElement {
if (this.props.p.hasWseAccount) {
return (
<StdButtonPurchased text={"WSE Account - Purchased"} />
)
} else {
const cost = CONSTANTS.WSEAccountCost;
return (
<StdButton
disabled={!this.props.p.canAfford(cost)}
onClick={this.purchaseWseAccount}
text={`Buy WSE Account - ${numeralWrapper.formatMoney(cost)}`}
/>
)
}
}
renderPurchaseTixApiAccessButton(): React.ReactElement {
if (this.props.p.hasTixApiAccess) {
return (
<StdButtonPurchased text={"TIX API Access - Purchased"} />
)
} else {
const cost = CONSTANTS.TIXAPICost;
return (
<StdButton
disabled={!this.props.p.canAfford(cost)}
onClick={this.purchaseTixApiAccess}
style={blockStyleMarkup}
text={`Buy Trade Information eXchange (TIX) API Access - ${numeralWrapper.formatMoney(cost)}`}
/>
)
}
}
renderPurchase4SMarketDataButton(): React.ReactElement {
if (this.props.p.has4SData) {
return (
<StdButtonPurchased
text={"4S Market Data - Purchased"}
tooltip={"Lets you view additional pricing and volatility information about stocks"}
/>
)
} else {
const cost = getStockMarket4SDataCost();
return (
<StdButton
disabled={!this.props.p.canAfford(cost)}
onClick={this.purchase4SMarketData}
text={`Buy 4S Market Data Access - ${numeralWrapper.formatMoney(cost)}`}
tooltip={"Lets you view additional pricing and volatility information about stocks"}
/>
)
}
}
renderPurchase4SMarketDataTixApiAccessButton(): React.ReactElement {
if (!this.props.p.hasTixApiAccess) {
return (
<StdButton
disabled={true}
text={`Buy 4S Market Data TIX API Access`}
tooltip={"Requires TIX API Access"}
/>
)
} else if (this.props.p.has4SDataTixApi) {
return (
<StdButtonPurchased
text={"4S Market Data TIX API - Purchased"}
tooltip={"Let you access 4S Market Data through Netscript"}
/>
)
} else {
const cost = getStockMarket4STixApiCost();
return (
<StdButton
disabled={!this.props.p.canAfford(cost)}
onClick={this.purchase4SMarketDataTixApiAccess}
text={`Buy 4S Market Data TIX API Access - ${numeralWrapper.formatMoney(cost)}`}
tooltip={"Let you access 4S Market Data through Netscript"}
/>
)
}
}
render() {
const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html";
return (
<div>
<p>Welcome to the World Stock Exchange (WSE)!</p><br /><br />
<p>
To begin trading, you must first purchase an account.
</p>
{this.renderPurchaseWseAccountButton()}
<a className={"std-button"} href={documentationLink} target={"_blank"}>
Investopedia
</a>
<h2>Trade Information eXchange (TIX) API</h2>
<p>
TIX, short for Trade Information eXchange, is the communications protocol
used by the WSE. Purchasing access to the TIX API lets you write code to create
your own algorithmic/automated trading strategies.
</p>
{this.renderPurchaseTixApiAccessButton()}
<h2>Four Sigma (4S) Market Data Feed</h2>
<p>
Four Sigma's (4S) Market Data Feed provides information about stocks that will help
your trading strategies.
</p>
{this.renderPurchase4SMarketDataButton()}
<button className={"help-tip-big"} onClick={this.handleClick4SMarketDataHelpTip}>
?
</button>
{this.renderPurchase4SMarketDataTixApiAccessButton()}
<p>
Commission Fees: Every transaction you make has
a {numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} commission fee.
</p>
<p>
WARNING: When you reset after installing Augmentations, the Stock
Market is reset. You will retain your WSE Account, access to the
TIX API, and 4S Market Data access. However, all of your stock
positions are lost, so make sure to sell your stocks before
installing Augmentations!
</p>
</div>
)
}
}

@ -0,0 +1,75 @@
/**
* Root React component for the Stock Market UI
*/
import * as React from "react";
import { InfoAndPurchases } from "./InfoAndPurchases";
import { StockTickers } from "./StockTickers";
import { IStockMarket } from "../IStockMarket";
import { Stock } from "../Stock";
import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer";
type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
type IProps = {
buyStockLong: txFn;
buyStockShort: txFn;
cancelOrder: (params: object) => void;
initStockMarket: () => void;
p: IPlayer;
placeOrder: placeOrderFn;
sellStockLong: txFn;
sellStockShort: txFn;
stockMarket: IStockMarket;
}
type IState = {
rerenderFlag: boolean;
}
export class StockMarketRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
rerenderFlag: false,
}
this.rerender = this.rerender.bind(this);
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
return (
<div>
<InfoAndPurchases
initStockMarket={this.props.initStockMarket}
p={this.props.p}
rerender={this.rerender}
/>
<StockTickers
buyStockLong={this.props.buyStockLong}
buyStockShort={this.props.buyStockShort}
cancelOrder={this.props.cancelOrder}
p={this.props.p}
placeOrder={this.props.placeOrder}
sellStockLong={this.props.sellStockLong}
sellStockShort={this.props.sellStockShort}
stockMarket={this.props.stockMarket}
/>
</div>
)
}
}

@ -0,0 +1,343 @@
/**
* React Component for a single stock ticker in the Stock Market UI
*/
import * as React from "react";
import { StockTickerHeaderText } from "./StockTickerHeaderText";
import { StockTickerOrderList } from "./StockTickerOrderList";
import { StockTickerPositionText } from "./StockTickerPositionText";
import { StockTickerTxButton } from "./StockTickerTxButton";
import { Order } from "../Order";
import { Stock } from "../Stock";
import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import {
yesNoTxtInpBoxClose,
yesNoTxtInpBoxCreate,
yesNoTxtInpBoxGetInput,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxGetYesButton,
} from "../../../utils/YesNoBox";
enum SelectorOrderType {
Market = "Market Order",
Limit = "Limit Order",
Stop = "Stop Order",
}
export type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
type IProps = {
buyStockLong: txFn;
buyStockShort: txFn;
cancelOrder: (params: object) => void;
orders: Order[];
p: IPlayer;
placeOrder: placeOrderFn;
sellStockLong: txFn;
sellStockShort: txFn;
stock: Stock;
}
type IState = {
orderType: SelectorOrderType;
position: PositionTypes;
qty: string;
}
export class StockTicker extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
orderType: SelectorOrderType.Market,
position: PositionTypes.Long,
qty: "",
}
this.handleBuyButtonClick = this.handleBuyButtonClick.bind(this);
this.handleBuyMaxButtonClick = this.handleBuyMaxButtonClick.bind(this);
this.handleOrderTypeChange = this.handleOrderTypeChange.bind(this);
this.handlePositionTypeChange = this.handlePositionTypeChange.bind(this);
this.handleQuantityChange = this.handleQuantityChange.bind(this);
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
}
createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void) {
const yesBtn = yesNoTxtInpBoxGetYesButton();
const noBtn = yesNoTxtInpBoxGetNoButton();
yesBtn!.innerText = yesTxt;
yesBtn!.addEventListener("click", () => {
const price = parseFloat(yesNoTxtInpBoxGetInput());
if (isNaN(price)) {
dialogBoxCreate(`Invalid input for price: ${yesNoTxtInpBoxGetInput()}`);
return false;
}
yesBtnCb(price);
yesNoTxtInpBoxClose();
});
noBtn!.innerText = "Cancel Order";
noBtn!.addEventListener("click", () => {
yesNoTxtInpBoxClose();
});
yesNoTxtInpBoxCreate(popupTxt);
}
handleBuyButtonClick() {
const shares = parseInt(this.state.qty);
if (isNaN(shares)) {
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
return;
}
switch (this.state.orderType) {
case SelectorOrderType.Market: {
if (this.state.position === PositionTypes.Short) {
this.props.buyStockShort(this.props.stock, shares);
} else {
this.props.buyStockLong(this.props.stock, shares);
}
break;
}
case SelectorOrderType.Limit: {
this.createPlaceOrderPopupBox(
"Place Buy Limit Order",
"Enter the price for your Limit Order",
(price: number) => {
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitBuy, this.state.position);
}
);
break;
}
case SelectorOrderType.Stop: {
this.createPlaceOrderPopupBox(
"Place Buy Stop Order",
"Enter the price for your Stop Order",
(price: number) => {
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopBuy, this.state.position);
}
);
break;
}
default:
break;
}
}
handleBuyMaxButtonClick() {
const playerMoney: number = this.props.p.money.toNumber();
const stock = this.props.stock;
let maxShares = Math.floor((playerMoney - CONSTANTS.StockMarketCommission) / this.props.stock.price);
maxShares = Math.min(maxShares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
switch (this.state.orderType) {
case SelectorOrderType.Market: {
if (this.state.position === PositionTypes.Short) {
this.props.buyStockShort(stock, maxShares);
} else {
this.props.buyStockLong(stock, maxShares);
}
break;
}
case SelectorOrderType.Limit: {
this.createPlaceOrderPopupBox(
"Place Buy Limit Order",
"Enter the price for your Limit Order",
(price: number) => {
this.props.placeOrder(stock, maxShares, price, OrderTypes.LimitBuy, this.state.position);
}
);
break;
}
case SelectorOrderType.Stop: {
this.createPlaceOrderPopupBox(
"Place Buy Stop Order",
"Enter the price for your Stop Order",
(price: number) => {
this.props.placeOrder(stock, maxShares, price, OrderTypes.StopBuy, this.state.position);
}
)
break;
}
default:
break;
}
}
handleOrderTypeChange(e: React.ChangeEvent<HTMLSelectElement>) {
const val = e.target.value;
// The select value returns a string. Afaik TypeScript doesnt make it easy
// to convert that string back to an enum type so we'll just do this for now
switch (val) {
case SelectorOrderType.Limit:
this.setState({
orderType: SelectorOrderType.Limit,
});
break;
case SelectorOrderType.Stop:
this.setState({
orderType: SelectorOrderType.Stop,
});
break;
case SelectorOrderType.Market:
default:
this.setState({
orderType: SelectorOrderType.Market,
});
}
}
handlePositionTypeChange(e: React.ChangeEvent<HTMLSelectElement>) {
const val = e.target.value;
if (val === "Short") {
this.setState({
position: PositionTypes.Short,
});
} else {
this.setState({
position: PositionTypes.Long,
});
}
}
handleQuantityChange(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({
qty: e.target.value,
});
}
handleSellButtonClick() {
const shares = parseInt(this.state.qty);
if (isNaN(shares)) {
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
return;
}
switch (this.state.orderType) {
case SelectorOrderType.Market: {
if (this.state.position === PositionTypes.Short) {
this.props.sellStockShort(this.props.stock, shares);
} else {
this.props.sellStockLong(this.props.stock, shares);
}
break;
}
case SelectorOrderType.Limit: {
this.createPlaceOrderPopupBox(
"Place Sell Limit Order",
"Enter the price for your Limit Order",
(price: number) => {
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitSell, this.state.position);
}
);
break;
}
case SelectorOrderType.Stop: {
this.createPlaceOrderPopupBox(
"Place Sell Stop Order",
"Enter the price for your Stop Order",
(price: number) => {
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopSell, this.state.position);
}
)
break;
}
default:
break;
}
}
handleSellAllButtonClick() {
const stock = this.props.stock;
switch (this.state.orderType) {
case SelectorOrderType.Market: {
if (this.state.position === PositionTypes.Short) {
this.props.sellStockShort(stock, stock.playerShortShares);
} else {
this.props.sellStockLong(stock, stock.playerShares);
}
break;
}
default: {
dialogBoxCreate(`ERROR: 'Sell All' only works for Market Orders`);
break;
}
}
}
// Whether the player has access to orders besides market orders (limit/stop)
hasOrderAccess(): boolean {
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 3));
}
// Whether the player has access to shorting stocks
hasShortAccess(): boolean {
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2));
}
render() {
return (
<li>
<button className={"accordion-header"}>
<StockTickerHeaderText p={this.props.p} stock={this.props.stock} />
</button>
<div className={"accordion-panel"}>
<input
className={"stock-market-input"}
onChange={this.handleQuantityChange}
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>
{
this.hasShortAccess() &&
<option value={"Short"}>Short</option>
}
</select>
<select className={"stock-market-input dropdown"} onChange={this.handleOrderTypeChange} value={this.state.orderType}>
<option value={SelectorOrderType.Market}>{SelectorOrderType.Market}</option>
{
this.hasOrderAccess() &&
<option value={SelectorOrderType.Limit}>{SelectorOrderType.Limit}</option>
}
{
this.hasOrderAccess() &&
<option value={SelectorOrderType.Stop}>{SelectorOrderType.Stop}</option>
}
</select>
<StockTickerTxButton onClick={this.handleBuyButtonClick} text={"Buy"} />
<StockTickerTxButton onClick={this.handleSellButtonClick} text={"Sell"} />
<StockTickerTxButton onClick={this.handleBuyMaxButtonClick} text={"Buy MAX"} />
<StockTickerTxButton onClick={this.handleSellAllButtonClick} text={"Sell ALL"} />
<StockTickerPositionText p={this.props.p} stock={this.props.stock} />
<StockTickerOrderList
cancelOrder={this.props.cancelOrder}
orders={this.props.orders}
p={this.props.p}
stock={this.props.stock}
/>
</div>
</li>
)
}
}

@ -0,0 +1,41 @@
/**
* React Component for the text on a stock ticker's header. This text displays
* general information on the stock such as the name, symbol, price, and
* 4S Market Data
*/
import * as React from "react";
import { Stock } from "../Stock";
import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = {
p: IPlayer;
stock: Stock;
}
export function StockTickerHeaderText(props: IProps): React.ReactElement {
const stock = props.stock;
const p = props.p;
const stockPriceFormat = numeralWrapper.formatMoney(stock.price);
let hdrText = `${stock.name}${" ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length))}${stock.symbol} -${" ".repeat(10 - stockPriceFormat.length)}${stockPriceFormat}`;
if (props.p.has4SData) {
hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `;
hdrText += (stock.b ? "+" : "-").repeat(Math.floor(stock.otlkMag / 10) + 1);
}
let styleMarkup = {
color: "#66ff33"
};
if (stock.lastPrice === stock.price) {
styleMarkup.color = "white";
} else if (stock.lastPrice > stock.price) {
styleMarkup.color = "red";
}
return <pre style={styleMarkup}>{hdrText}</pre>;
}

@ -0,0 +1,42 @@
/**
* React component for displaying a single order in a stock's order book
*/
import * as React from "react";
import { Order } from "../Order";
import { PositionTypes } from "../data/PositionTypes";
import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = {
cancelOrder: (params: object) => void;
order: Order;
}
export class StockTickerOrder extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
this.handleCancelOrderClick = this.handleCancelOrderClick.bind(this);
}
handleCancelOrderClick() {
this.props.cancelOrder({ order: this.props.order });
}
render() {
const order = this.props.order;
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
const txt = `${order.type} - ${posTxt} - ${order.shares} @ ${numeralWrapper.formatMoney(order.price)}`
return (
<li>
{txt}
<button className={"std-button stock-market-order-cancel-btn"} onClick={this.handleCancelOrderClick}>
Cancel Order
</button>
</li>
)
}
}

@ -0,0 +1,33 @@
/**
* React component for displaying a stock's order list in the Stock Market UI.
* This component resides in the stock ticker
*/
import * as React from "react";
import { StockTickerOrder } from "./StockTickerOrder";
import { Order } from "../Order";
import { Stock } from "../Stock";
import { IPlayer } from "../../PersonObjects/IPlayer";
type IProps = {
cancelOrder: (params: object) => void;
orders: Order[];
p: IPlayer;
stock: Stock;
}
export class StockTickerOrderList extends React.Component<IProps, any> {
render() {
const orders: React.ReactElement[] = [];
for (let i = 0; i < this.props.orders.length; ++i) {
const o = this.props.orders[i];
orders.push(<StockTickerOrder cancelOrder={this.props.cancelOrder} order={o} key={i} />);
}
return (
<ul className={"stock-market-order-list"}>{orders}</ul>
)
}
}

@ -0,0 +1,117 @@
/**
* React Component for the text on a stock ticker that display's information
* about the player's position in that stock
*/
import * as React from "react";
import { Stock } from "../Stock";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
type IProps = {
p: IPlayer;
stock: Stock;
}
const blockStyleMarkup = {
display: "block",
}
export class StockTickerPositionText extends React.Component<IProps, any> {
renderLongPosition(): React.ReactElement {
const stock = this.props.stock;
// Caculate total returns
const totalCost = stock.playerShares * stock.playerAvgPx;
const gains = (stock.price - stock.playerAvgPx) * stock.playerShares;
let percentageGains = gains / totalCost;
if (isNaN(percentageGains)) { percentageGains = 0; }
return (
<div>
<h3 className={"tooltip"}>
Short Position:
<span className={"tooltiptext"}>
Shares in the long position will increase in value if the price
of the corresponding stock increases
</span>
</h3>
<p>
Shares: {numeralWrapper.format(stock.playerShares, "0,0")}
</p>
<p>
Average Price: {numeralWrapper.formatMoney(stock.playerAvgPx)}
(Total Cost: {numeralWrapper.formatMoney(totalCost)})
</p>
<p>
Profit: {numeralWrapper.formatMoney(gains)}
({numeralWrapper.formatPercentage(percentageGains)})
</p>
</div>
)
}
renderShortPosition(): React.ReactElement | null {
const stock = this.props.stock;
// Caculate total returns
const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
const gains = (stock.playerAvgShortPx - stock.price) * stock.playerShortShares;
let percentageGains = gains / totalCost;
if (isNaN(percentageGains)) { percentageGains = 0; }
if (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2)) {
return (
<div>
<h3 className={"tooltip"}>
Short Position:
<span className={"tooltiptext"}>
Shares in the short position will increase in value if the
price of the corresponding stock decreases
</span>
</h3>
<p>
Shares: {numeralWrapper.format(stock.playerShortShares, "0,0")}
</p>
<p>
Average Price: {numeralWrapper.formatMoney(stock.playerAvgShortPx)}
(Total Cost: {numeralWrapper.formatMoney(totalCost)})
</p>
<p>
Profit: {numeralWrapper.formatMoney(gains)}
({numeralWrapper.formatPercentage(percentageGains)})
</p>
</div>
)
} else {
return null;
}
}
render() {
const stock = this.props.stock;
return (
<div className={"stock-market-position-text"}>
<p style={blockStyleMarkup}>
Max Shares: ${numeralWrapper.formatMoney(stock.maxShares)}
</p>
<p className={"tooltip"} >
Ask Price: {numeralWrapper.formatMoney(stock.getAskPrice())}
<span className={"tooltiptext"}>
See Investopedia for details on what this is
</span>
</p>
<p className={"tooltip"} >
<span className={"tooltiptext"}>
See Investopedia for details on what this is
</span>
</p>
{this.renderLongPosition()}
{this.renderShortPosition()}
</div>
)
}
}

@ -0,0 +1,18 @@
/**
* React Component for a button that initiates a transaction on the Stock Market UI
* (Buy, Sell, Buy Max, etc.)
*/
import * as React from "react";
type IProps = {
onClick: () => void;
text: string;
}
export function StockTickerTxButton(props: IProps): React.ReactElement {
return (
<button className={"stock-market-input std-button"} onClick={props.onClick}>
{props.text}
</button>
)
}

@ -0,0 +1,129 @@
/**
* React Component for the Stock Market UI. This is the container for all
* of the stock tickers. It also contains the configuration for the
* stock ticker UI (watchlist filter, portfolio vs all mode, etc.)
*/
import * as React from "react";
import { StockTicker } from "./StockTicker";
import { StockTickersConfig, TickerDisplayMode } from "./StockTickersConfig";
import { IStockMarket } from "../IStockMarket";
import { Stock } from "../Stock";
import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer";
export type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
type IProps = {
buyStockLong: txFn;
buyStockShort: txFn;
cancelOrder: (params: object) => void;
p: IPlayer;
placeOrder: placeOrderFn;
sellStockLong: txFn;
sellStockShort: txFn;
stockMarket: IStockMarket;
}
type IState = {
rerenderFlag: boolean;
tickerDisplayMode: TickerDisplayMode;
watchlistFilter: string;
watchlistSymbols: string[];
}
export class StockTickers extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
rerenderFlag: false,
tickerDisplayMode: TickerDisplayMode.AllStocks,
watchlistFilter: "",
watchlistSymbols: [],
}
this.changeDisplayMode = this.changeDisplayMode.bind(this);
this.changeWatchlistFilter = this.changeWatchlistFilter.bind(this);
}
changeDisplayMode() {
if (this.state.tickerDisplayMode === TickerDisplayMode.AllStocks) {
this.setState({
tickerDisplayMode: TickerDisplayMode.Portfolio,
});
} else {
this.setState({
tickerDisplayMode: TickerDisplayMode.AllStocks,
});
}
}
changeWatchlistFilter(e: React.ChangeEvent<HTMLInputElement>) {
const watchlist = e.target.value;
const sanitizedWatchlist = watchlist.replace(/\s/g, '');
this.setState({
watchlistFilter: watchlist,
});
if (sanitizedWatchlist !== "") {
this.setState({
watchlistSymbols: sanitizedWatchlist.split(","),
});
}
}
rerender() {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
const tickers: React.ReactElement[] = [];
for (const stockMarketProp in this.props.stockMarket) {
const val = this.props.stockMarket[stockMarketProp];
if (val instanceof Stock) {
let orders = this.props.stockMarket.Orders[val.symbol];
if (orders == null) {
orders = [];
}
tickers.push(
<StockTicker
buyStockLong={this.props.buyStockLong}
buyStockShort={this.props.buyStockShort}
cancelOrder={this.props.cancelOrder}
orders={orders}
p={this.props.p}
placeOrder={this.props.placeOrder}
sellStockLong={this.props.sellStockLong}
sellStockShort={this.props.sellStockShort}
stock={val}
/>
)
}
}
return (
<div>
<StockTickersConfig
changeDisplayMode={this.changeDisplayMode}
changeWatchlistFilter={this.changeWatchlistFilter}
tickerDisplayMode={this.state.tickerDisplayMode}
/>
<ul id="stock-market-list">
{tickers}
</ul>
</div>
)
}
}

@ -0,0 +1,61 @@
/**
* React component for the tickers configuration section of the Stock Market UI.
* This config lets you change the way stock tickers are displayed (watchlist,
* all/portoflio mode, etc)
*/
import * as React from "react";
import { StdButton } from "../../ui/React/StdButton";
export enum TickerDisplayMode {
AllStocks,
Portfolio,
}
type IProps = {
changeDisplayMode: () => void;
changeWatchlistFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
tickerDisplayMode: TickerDisplayMode;
}
export class StockTickersConfig extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
}
renderDisplayModeButton() {
let txt: string = "";
let tooltip: string = "";
if (this.props.tickerDisplayMode === TickerDisplayMode.Portfolio) {
txt = "Switch to 'All Stocks' Mode";
tooltip = "Displays all stocks on the WSE";
} else {
txt = "Switch to 'Portfolio' Mode";
tooltip = "Displays only the stocks for which you have shares or orders";
}
return (
<StdButton
onClick={this.props.changeDisplayMode}
text={txt}
tooltip={tooltip}
/>
)
}
render() {
return (
<div>
{this.renderDisplayModeButton()}
<input
className="text-input"
id="stock-market-watchlist-filter"
onChange={this.props.changeWatchlistFilter}
placeholder="Filter Stocks by symbol (comma-separated list)"
type="text"
/>
</div>
)
}
}

@ -290,53 +290,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
</div>
<div id="stock-market-container" class="generic-menupage-container">
<p>
Welcome to the World Stock Exchange (WSE)! <br /><br />
To begin trading, you must first purchase an account. WSE accounts will persist
after you 'reset' by installing Augmentations.
</p>
<a id="stock-market-buy-account" class="a-link-button-inactive"> Buy WSE Account </a>
<a id="stock-market-investopedia" class="a-link-button">Investopedia</a>
<h2> Trade Information eXchange (TIX) API </h2>
<p>
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.
<br /><br />
If you purchase access to the TIX API, you will retain that access even after
you 'reset' by installing Augmentations.
</p>
<a id="stock-market-buy-tix-api" class="a-link-button-inactive">Buy Trade Information eXchange (TIX) API Access</a>
<h2> Four Sigma (4S) Market Data Feed </h2>
<p>
Four Sigma's (4S) Market Data Feed provides information about stocks
that will help your trading strategies.
<br /><br />
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.
</p>
<a id="stock-market-buy-4s-data" class="a-link-button-inactive tooltip">
Buy 4S Market Data Feed
</a>
<div class="help-tip-big" id="stock-market-4s-data-help-tip">?</div>
<a id="stock-market-buy-4s-tix-api" class="a-link-button-inactive tooltip">
Buy 4S Market Data TIX API Access
</a>
<p id="stock-market-commission"> </p>
<a id="stock-market-mode" class="a-link-button tooltip"></a>
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
<br /><br />
<input id="stock-market-watchlist-filter" class="text-input" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
<ul id="stock-market-list" style="list-style:none;">
</ul>
<!-- React Component -->
</div>
<!-- Log Box -->

@ -7,13 +7,36 @@ interface IStdButtonPurchasedProps {
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
style?: object;
text: string;
tooltip?: string;
}
type IInnerHTMLMarkup = {
__html: string;
}
export class StdButtonPurchased extends React.Component<IStdButtonPurchasedProps, any> {
render() {
const hasTooltip = this.props.tooltip != null && this.props.tooltip !== "";
let className = "std-button-bought";
if (hasTooltip) {
className += " tooltip";
}
// Tooltip will be set using inner HTML
let tooltipMarkup: IInnerHTMLMarkup | null;
if (hasTooltip) {
tooltipMarkup = {
__html: this.props.tooltip!
}
}
return (
<button className={"std-button-bought"} onClick={this.props.onClick} style={this.props.style}>
<button className={className} onClick={this.props.onClick} style={this.props.style}>
{this.props.text}
{
hasTooltip &&
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
}
</button>
)
}