mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-18 13:43:49 +01:00
Finished React components for new Stock Market UI
This commit is contained in:
parent
6b3646e981
commit
4809a21e38
@ -17,9 +17,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
#stock-market-list li {
|
||||
button {
|
||||
font-size: $defaultFontSize;
|
||||
#stock-market-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
button {
|
||||
font-size: $defaultFontSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
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;
|
||||
|
5
src/StockMarket/IOrderBook.ts
Normal file
5
src/StockMarket/IOrderBook.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Order } from "./Order";
|
||||
|
||||
export interface IOrderBook {
|
||||
[key: string]: Order[];
|
||||
}
|
10
src/StockMarket/IStockMarket.ts
Normal file
10
src/StockMarket/IStockMarket.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
11
src/StockMarket/data/TickerHeaderFormatData.ts
Normal file
11
src/StockMarket/data/TickerHeaderFormatData.ts
Normal file
@ -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);
|
||||
}
|
222
src/StockMarket/ui/InfoAndPurchases.tsx
Normal file
222
src/StockMarket/ui/InfoAndPurchases.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
75
src/StockMarket/ui/Root.tsx
Normal file
75
src/StockMarket/ui/Root.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
343
src/StockMarket/ui/StockTicker.tsx
Normal file
343
src/StockMarket/ui/StockTicker.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
41
src/StockMarket/ui/StockTickerHeaderText.tsx
Normal file
41
src/StockMarket/ui/StockTickerHeaderText.tsx
Normal file
@ -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>;
|
||||
}
|
42
src/StockMarket/ui/StockTickerOrder.tsx
Normal file
42
src/StockMarket/ui/StockTickerOrder.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
33
src/StockMarket/ui/StockTickerOrderList.tsx
Normal file
33
src/StockMarket/ui/StockTickerOrderList.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
117
src/StockMarket/ui/StockTickerPositionText.tsx
Normal file
117
src/StockMarket/ui/StockTickerPositionText.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
18
src/StockMarket/ui/StockTickerTxButton.tsx
Normal file
18
src/StockMarket/ui/StockTickerTxButton.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
129
src/StockMarket/ui/StockTickers.tsx
Normal file
129
src/StockMarket/ui/StockTickers.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
61
src/StockMarket/ui/StockTickersConfig.tsx
Normal file
61
src/StockMarket/ui/StockTickersConfig.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user