mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2025-03-07 19:14:37 +01:00
Added spread and price movement properties to stocks. Refactored Stock Market implementation code
This commit is contained in:
@ -1648,42 +1648,9 @@ function NetscriptFunctions(workerScript) {
|
||||
if (stock == null) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into buyStock()");
|
||||
}
|
||||
if (shares < 0 || isNaN(shares)) {
|
||||
workerScript.scriptRef.log("ERROR: Invalid 'shares' argument passed to buyStock()");
|
||||
return 0;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares === 0) {return 0;}
|
||||
const res = buyStock(stock, shares, workerScript);
|
||||
|
||||
// Does player have enough money?
|
||||
var totalPrice = stock.price * shares;
|
||||
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||
workerScript.scriptRef.log("Not enough money to purchase " + formatNumber(shares, 0) + " shares of " +
|
||||
symbol + ". Need $" +
|
||||
formatNumber(totalPrice + CONSTANTS.StockMarketCommission, 2).toString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Would this purchase exceed the maximum number of shares?
|
||||
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||
workerScript.scriptRef.log(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
|
||||
`${stock.maxShares} shares.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var origTotal = stock.playerShares * stock.playerAvgPx;
|
||||
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||
var newTotal = origTotal + totalPrice;
|
||||
stock.playerShares += shares;
|
||||
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||
if (routing.isOn(Page.StockMarket)) {
|
||||
updateStockPlayerPosition(stock);
|
||||
}
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.buyStock == null) {
|
||||
workerScript.scriptRef.log("Bought " + formatNumber(shares, 0) + " shares of " + stock.symbol + " at $" +
|
||||
formatNumber(stock.price, 2) + " per share");
|
||||
}
|
||||
return stock.price;
|
||||
return res ? stock.price : 0;
|
||||
},
|
||||
sellStock : function(symbol, shares) {
|
||||
if (workerScript.checkingRam) {
|
||||
@ -1697,36 +1664,10 @@ function NetscriptFunctions(workerScript) {
|
||||
if (stock == null) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into sellStock()");
|
||||
}
|
||||
if (shares < 0 || isNaN(shares)) {
|
||||
workerScript.scriptRef.log("ERROR: Invalid 'shares' argument passed to sellStock()");
|
||||
return 0;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares > stock.playerShares) {shares = stock.playerShares;}
|
||||
if (shares === 0) {return 0;}
|
||||
var gains = stock.price * shares - CONSTANTS.StockMarketCommission;
|
||||
Player.gainMoney(gains);
|
||||
|
||||
// Calculate net profit and add to script stats
|
||||
var netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission;
|
||||
if (isNaN(netProfit)) {netProfit = 0;}
|
||||
workerScript.scriptRef.onlineMoneyMade += netProfit;
|
||||
Player.scriptProdSinceLastAug += netProfit;
|
||||
Player.recordMoneySource(netProfit, "stock");
|
||||
const res = sellStock(stock, shares, workerScript);
|
||||
|
||||
stock.playerShares -= shares;
|
||||
if (stock.playerShares == 0) {
|
||||
stock.playerAvgPx = 0;
|
||||
}
|
||||
if (routing.isOn(Page.StockMarket)) {
|
||||
updateStockPlayerPosition(stock);
|
||||
}
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.sellStock == null) {
|
||||
workerScript.scriptRef.log("Sold " + formatNumber(shares, 0) + " shares of " + stock.symbol + " at $" +
|
||||
formatNumber(stock.price, 2) + " per share. Gained " +
|
||||
"$" + formatNumber(gains, 2));
|
||||
}
|
||||
return stock.price;
|
||||
return res ? stock.price : 0;
|
||||
},
|
||||
shortStock(symbol, shares) {
|
||||
if (workerScript.checkingRam) {
|
||||
@ -1745,7 +1686,8 @@ function NetscriptFunctions(workerScript) {
|
||||
if (stock == null) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into shortStock()");
|
||||
}
|
||||
var res = shortStock(stock, shares, workerScript);
|
||||
const res = shortStock(stock, shares, workerScript);
|
||||
|
||||
return res ? stock.price : 0;
|
||||
},
|
||||
sellShort(symbol, shares) {
|
||||
@ -1765,7 +1707,8 @@ function NetscriptFunctions(workerScript) {
|
||||
if (stock == null) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "ERROR: Invalid stock symbol passed into sellShort()");
|
||||
}
|
||||
var res = sellShort(stock, shares, workerScript);
|
||||
const res = sellShort(stock, shares, workerScript);
|
||||
|
||||
return res ? stock.price : 0;
|
||||
},
|
||||
placeOrder(symbol, shares, price, type, pos) {
|
||||
|
@ -1,23 +1,7 @@
|
||||
// tslint:disable:max-file-line-count
|
||||
|
||||
// This could actually be a JSON file as it should be constant metadata to be imported...
|
||||
|
||||
/**
|
||||
* Defines the minimum and maximum values for a range.
|
||||
* It is up to the consumer if these values are inclusive or exclusive.
|
||||
* It is up to the implementor to ensure max > min.
|
||||
*/
|
||||
interface IMinMaxRange {
|
||||
/**
|
||||
* The maximum bound of the range.
|
||||
*/
|
||||
max: number;
|
||||
|
||||
/**
|
||||
* The minimum bound of the range.
|
||||
*/
|
||||
min: number;
|
||||
}
|
||||
import { IMinMaxRange } from "../../types";
|
||||
|
||||
/**
|
||||
* The metadata describing the base state of servers on the network.
|
||||
|
45
src/StockMarket/Order.ts
Normal file
45
src/StockMarket/Order.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Represents a Limit or Buy Order on the stock market. Does not represent
|
||||
* a Market Order since those are just executed immediately
|
||||
*/
|
||||
import { Stock } from "./Stock";
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
export class Order {
|
||||
/**
|
||||
* Initializes a Order from a JSON save state
|
||||
*/
|
||||
static fromJSON(value: any): Order {
|
||||
return Generic_fromJSON(Order, value.data);
|
||||
}
|
||||
|
||||
readonly pos: PositionTypes;
|
||||
readonly price: number;
|
||||
readonly shares: number;
|
||||
readonly stock: Stock;
|
||||
readonly type: OrderTypes;
|
||||
|
||||
constructor(stk: Stock = new Stock(), shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) {
|
||||
this.stock = stk;
|
||||
this.shares = shares;
|
||||
this.price = price;
|
||||
this.type = typ;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the Order to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Order", this);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.Order = Order;
|
@ -1,6 +1,57 @@
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
import { IMinMaxRange } from "../types";
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
export interface IConstructorParams {
|
||||
b: boolean;
|
||||
initPrice: number | IMinMaxRange;
|
||||
marketCap: number;
|
||||
mv: number | IMinMaxRange;
|
||||
name: string;
|
||||
otlkMag: number;
|
||||
spreadPerc: number | IMinMaxRange;
|
||||
shareTxForMovement: number | IMinMaxRange;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const defaultConstructorParams: IConstructorParams = {
|
||||
b: true,
|
||||
initPrice: 10e3,
|
||||
marketCap: 1e12,
|
||||
mv: 1,
|
||||
name: "",
|
||||
otlkMag: 0,
|
||||
spreadPerc: 0,
|
||||
shareTxForMovement: 1e6,
|
||||
symbol: "",
|
||||
}
|
||||
|
||||
// Helper function that convert a IMinMaxRange to a number
|
||||
function toNumber(n: number | IMinMaxRange): number {
|
||||
let value: number;
|
||||
switch (typeof n) {
|
||||
case "number": {
|
||||
return n;
|
||||
}
|
||||
case "object": {
|
||||
value = getRandomInt(n.min, n.max);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`Do not know how to convert the type '${typeof n}' to a number`);
|
||||
}
|
||||
|
||||
if (typeof n === "object" && typeof n.divisor === "number") {
|
||||
return value / n.divisor;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the valuation of a company in the World Stock Exchange.
|
||||
*/
|
||||
@ -73,6 +124,28 @@ export class Stock {
|
||||
*/
|
||||
price: number;
|
||||
|
||||
/**
|
||||
* Percentage by which the stock's price changes for a transaction-induced
|
||||
* price movement.
|
||||
*/
|
||||
readonly priceMovementPerc: number;
|
||||
|
||||
/**
|
||||
* How many shares need to be transacted in order to trigger a price movement
|
||||
*/
|
||||
readonly shareTxForMovement: number;
|
||||
|
||||
/**
|
||||
* How many share transactions remaining until a price movement occurs
|
||||
*/
|
||||
shareTxUntilMovement: number;
|
||||
|
||||
/**
|
||||
* Spread percentage. The bid/ask prices for this stock are N% above or below
|
||||
* the "real price" to emulate spread.
|
||||
*/
|
||||
readonly spreadPerc: number;
|
||||
|
||||
/**
|
||||
* The stock's ticker symbol
|
||||
*/
|
||||
@ -85,36 +158,48 @@ export class Stock {
|
||||
*/
|
||||
readonly totalShares: number;
|
||||
|
||||
constructor(name: string = "",
|
||||
symbol: string = "",
|
||||
mv: number = 1,
|
||||
b: boolean = true,
|
||||
otlkMag: number = 0,
|
||||
initPrice: number = 10e3,
|
||||
marketCap: number = 1e12) {
|
||||
this.name = name;
|
||||
this.symbol = symbol;
|
||||
this.price = initPrice;
|
||||
this.playerShares = 0;
|
||||
this.playerAvgPx = 0;
|
||||
this.playerShortShares = 0;
|
||||
this.playerAvgShortPx = 0;
|
||||
this.mv = mv;
|
||||
this.b = b;
|
||||
this.otlkMag = otlkMag;
|
||||
this.cap = getRandomInt(initPrice * 1e3, initPrice * 25e3);
|
||||
constructor(p: IConstructorParams = defaultConstructorParams) {
|
||||
this.name = p.name;
|
||||
this.symbol = p.symbol;
|
||||
this.price = toNumber(p.initPrice);
|
||||
this.playerShares = 0;
|
||||
this.playerAvgPx = 0;
|
||||
this.playerShortShares = 0;
|
||||
this.playerAvgShortPx = 0;
|
||||
this.mv = toNumber(p.mv);
|
||||
this.b = p.b;
|
||||
this.otlkMag = p.otlkMag;
|
||||
this.cap = getRandomInt(this.price * 1e3, this.price * 25e3);
|
||||
this.spreadPerc = toNumber(p.spreadPerc);
|
||||
this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10);
|
||||
this.shareTxForMovement = toNumber(p.shareTxForMovement);
|
||||
this.shareTxUntilMovement = this.shareTxForMovement;
|
||||
|
||||
// Total shares is determined by market cap, and is rounded to nearest 100k
|
||||
let totalSharesUnrounded: number = (marketCap / initPrice);
|
||||
let totalSharesUnrounded: number = (p.marketCap / this.price);
|
||||
this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5;
|
||||
|
||||
// Max Shares (Outstanding shares) is a percentage of total shares
|
||||
const outstandingSharePercentage: number = 0.2;
|
||||
const outstandingSharePercentage: number = 0.15;
|
||||
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
|
||||
|
||||
this.posTxtEl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price at which YOUR stock is bought (market ask price). Accounts for spread
|
||||
*/
|
||||
getAskPrice(): number {
|
||||
return this.price * (1 + (this.spreadPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price at which YOUR stock is sold (market bid price). Accounts for spread
|
||||
*/
|
||||
getBidPrice(): number {
|
||||
return this.price * (1 - (this.spreadPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the Stock to a JSON save state.
|
||||
*/
|
||||
|
@ -1,20 +1,31 @@
|
||||
import {Stock} from "./Stock";
|
||||
import { getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost } from "./StockMarketCosts";
|
||||
import {
|
||||
Order,
|
||||
OrderTypes,
|
||||
PositionTypes
|
||||
} from "./Order";
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost
|
||||
} from "./StockMarketCosts";
|
||||
import { StockSymbols } from "./data/StockSymbols";
|
||||
|
||||
import {CONSTANTS} from "../Constants";
|
||||
import { LocationName } from "../Locations/data/LocationNames";
|
||||
import {hasWallStreetSF, wallStreetSFLvl} from "../NetscriptFunctions";
|
||||
import {WorkerScript} from "../NetscriptWorker";
|
||||
import {Player} from "../Player";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { LocationName } from "../Locations/data/LocationNames";
|
||||
import { hasWallStreetSF, wallStreetSFLvl } from "../NetscriptFunctions";
|
||||
import { WorkerScript } from "../NetscriptWorker";
|
||||
import { Player } from "../Player";
|
||||
|
||||
import {Page, routing} from ".././ui/navigationTracking";
|
||||
import {numeralWrapper} from ".././ui/numeralFormat";
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
import { numeralWrapper } from ".././ui/numeralFormat";
|
||||
|
||||
import {dialogBoxCreate} from "../../utils/DialogBox";
|
||||
import {clearEventListeners} from "../../utils/uiHelpers/clearEventListeners";
|
||||
import {Reviver, Generic_toJSON,
|
||||
Generic_fromJSON} from "../../utils/JSONReviver";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON
|
||||
} from "../../utils/JSONReviver";
|
||||
import {exceptionAlert} from "../../utils/helpers/exceptionAlert";
|
||||
import {getRandomInt} from "../../utils/helpers/getRandomInt";
|
||||
import {KEY} from "../../utils/helpers/keyCodes";
|
||||
@ -27,19 +38,7 @@ import {yesNoBoxCreate, yesNoTxtInpBoxCreate,
|
||||
yesNoTxtInpBoxGetInput, yesNoBoxClose,
|
||||
yesNoTxtInpBoxClose, yesNoBoxOpen} from "../../utils/YesNoBox";
|
||||
|
||||
var OrderTypes = {
|
||||
LimitBuy: "Limit Buy Order",
|
||||
LimitSell: "Limit Sell Order",
|
||||
StopBuy: "Stop Buy Order",
|
||||
StopSell: "Stop Sell Order"
|
||||
}
|
||||
|
||||
var PositionTypes = {
|
||||
Long: "L",
|
||||
Short: "S"
|
||||
}
|
||||
|
||||
function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
var order = new Order(stock, shares, price, type, position);
|
||||
if (isNaN(shares) || isNaN(price)) {
|
||||
@ -51,12 +50,12 @@ function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
return false;
|
||||
}
|
||||
if (StockMarket["Orders"] == null) {
|
||||
var orders = {};
|
||||
for (var name in StockMarket) {
|
||||
const orders = {};
|
||||
for (const name in StockMarket) {
|
||||
if (StockMarket.hasOwnProperty(name)) {
|
||||
var stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) {continue;}
|
||||
orders[stock.symbol] = [];
|
||||
const stk = StockMarket[name];
|
||||
if (!(stk instanceof Stock)) { continue; }
|
||||
orders[stk.symbol] = [];
|
||||
}
|
||||
}
|
||||
StockMarket["Orders"] = orders;
|
||||
@ -68,8 +67,8 @@ function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns true if successfully cancels an order, false otherwise
|
||||
function cancelOrder(params, workerScript=null) {
|
||||
// Returns true if successfully cancels an order, false otherwise
|
||||
export function cancelOrder(params, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
if (StockMarket["Orders"] == null) {return false;}
|
||||
if (params.order && params.order instanceof Order) {
|
||||
@ -153,34 +152,20 @@ function executeOrder(order) {
|
||||
}
|
||||
}
|
||||
|
||||
function Order(stock, shares, price, type, position) {
|
||||
this.stock = stock;
|
||||
this.shares = shares;
|
||||
this.price = price;
|
||||
this.type = type;
|
||||
this.pos = position;
|
||||
}
|
||||
|
||||
Order.prototype.toJSON = function() {
|
||||
return Generic_toJSON("Order", this);
|
||||
}
|
||||
|
||||
Order.fromJSON = function(value) {
|
||||
return Generic_fromJSON(Order, value.data);
|
||||
}
|
||||
|
||||
Reviver.constructors.Order = Order;
|
||||
|
||||
let StockMarket = {} //Full name to stock object
|
||||
let StockSymbols = {} //Full name to symbol
|
||||
let SymbolToStockMap = {}; //Symbol to Stock object
|
||||
export let StockMarket = {}; // Maps full stock name -> Stock object
|
||||
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
|
||||
|
||||
let formatHelpData = {
|
||||
longestName: 0,
|
||||
longestSymbol: 0,
|
||||
};
|
||||
|
||||
function loadStockMarket(saveString) {
|
||||
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 = {};
|
||||
} else {
|
||||
@ -188,51 +173,7 @@ function loadStockMarket(saveString) {
|
||||
}
|
||||
}
|
||||
|
||||
function initStockSymbols() {
|
||||
//Stocks for companies at which you can work
|
||||
StockSymbols[LocationName.AevumECorp] = "ECP";
|
||||
StockSymbols[LocationName.Sector12MegaCorp] = "MGCP";
|
||||
StockSymbols[LocationName.Sector12BladeIndustries] = "BLD";
|
||||
StockSymbols[LocationName.AevumClarkeIncorporated] = "CLRK";
|
||||
StockSymbols[LocationName.VolhavenOmniTekIncorporated] = "OMTK";
|
||||
StockSymbols[LocationName.Sector12FourSigma] = "FSIG";
|
||||
StockSymbols[LocationName.ChongqingKuaiGongInternational] = "KGI";
|
||||
StockSymbols[LocationName.AevumFulcrumTechnologies] = "FLCM";
|
||||
StockSymbols[LocationName.IshimaStormTechnologies] = "STM";
|
||||
StockSymbols[LocationName.NewTokyoDefComm] = "DCOMM";
|
||||
StockSymbols[LocationName.VolhavenHeliosLabs] = "HLS";
|
||||
StockSymbols[LocationName.NewTokyoVitaLife] = "VITA";
|
||||
StockSymbols[LocationName.Sector12IcarusMicrosystems] = "ICRS";
|
||||
StockSymbols[LocationName.Sector12UniversalEnergy] = "UNV";
|
||||
StockSymbols[LocationName.AevumAeroCorp] = "AERO";
|
||||
StockSymbols[LocationName.VolhavenOmniaCybersystems] = "OMN";
|
||||
StockSymbols[LocationName.ChongqingSolarisSpaceSystems] = "SLRS";
|
||||
StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals] = "GPH";
|
||||
StockSymbols[LocationName.IshimaNovaMedical] = "NVMD";
|
||||
StockSymbols[LocationName.AevumWatchdogSecurity] = "WDS";
|
||||
StockSymbols[LocationName.VolhavenLexoCorp] = "LXO";
|
||||
StockSymbols[LocationName.AevumRhoConstruction] = "RHOC";
|
||||
StockSymbols[LocationName.Sector12AlphaEnterprises] = "APHE";
|
||||
StockSymbols[LocationName.VolhavenSysCoreSecurities] = "SYSC";
|
||||
StockSymbols[LocationName.VolhavenCompuTek] = "CTK";
|
||||
StockSymbols[LocationName.AevumNetLinkTechnologies] = "NTLK";
|
||||
StockSymbols[LocationName.IshimaOmegaSoftware] = "OMGA";
|
||||
StockSymbols[LocationName.Sector12FoodNStuff] = "FNS";
|
||||
|
||||
//Stocks for other companies
|
||||
StockSymbols["Sigma Cosmetics"] = "SGC";
|
||||
StockSymbols["Joes Guns"] = "JGN";
|
||||
StockSymbols["Catalyst Ventures"] = "CTYS";
|
||||
StockSymbols["Microdyne Technologies"] = "MDYN";
|
||||
StockSymbols["Titan Laboratories"] = "TITN";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function initStockMarket() {
|
||||
export function initStockMarket() {
|
||||
for (var stk in StockMarket) {
|
||||
if (StockMarket.hasOwnProperty(stk)) {
|
||||
delete StockMarket[stk];
|
||||
@ -387,7 +328,7 @@ function initStockMarket() {
|
||||
StockMarket.lastUpdate = 0;
|
||||
}
|
||||
|
||||
function initSymbolToStockMap() {
|
||||
export function initSymbolToStockMap() {
|
||||
for (var name in StockSymbols) {
|
||||
if (StockSymbols.hasOwnProperty(name)) {
|
||||
var stock = StockMarket[name];
|
||||
@ -401,7 +342,7 @@ function initSymbolToStockMap() {
|
||||
}
|
||||
}
|
||||
|
||||
function stockMarketCycle() {
|
||||
export function stockMarketCycle() {
|
||||
for (var name in StockMarket) {
|
||||
if (StockMarket.hasOwnProperty(name)) {
|
||||
var stock = StockMarket[name];
|
||||
@ -415,78 +356,151 @@ function stockMarketCycle() {
|
||||
}
|
||||
}
|
||||
|
||||
//Returns true if successful, false otherwise
|
||||
function buyStock(stock, shares) {
|
||||
/**
|
||||
* Attempt to buy a stock in the long position
|
||||
* @param {Stock} stock - Stock to buy
|
||||
* @param {number} shares - Number of shares to buy
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @returns {boolean} - true if successful, false otherwise
|
||||
*/
|
||||
export function buyStock(stock, shares, workerScript=null) {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Validate arguments
|
||||
shares = Math.round(shares);
|
||||
if (shares == 0 || shares < 0) { return false; }
|
||||
if (stock == null || isNaN(shares)) {
|
||||
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: buyStock() failed due to invalid arguments`);
|
||||
} else {
|
||||
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does player have enough money?
|
||||
var totalPrice = stock.price * shares;
|
||||
const totalPrice = stock.price * shares;
|
||||
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||
dialogBoxCreate("You do not have enough money to purchase this. You need " +
|
||||
numeralWrapper.format(totalPrice + CONSTANTS.StockMarketCommission, '($0.000a)') + ".");
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice + CONSTANTS.StockMarketCommission)}`);
|
||||
} else {
|
||||
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice + CONSTANTS.StockMarketCommission)}`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Would this purchase exceed the maximum number of shares?
|
||||
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
|
||||
`${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
|
||||
} else {
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var origTotal = stock.playerShares * stock.playerAvgPx;
|
||||
const origTotal = stock.playerShares * stock.playerAvgPx;
|
||||
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||
var newTotal = origTotal + totalPrice;
|
||||
const newTotal = origTotal + totalPrice;
|
||||
stock.playerShares = Math.round(stock.playerShares + shares);
|
||||
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||
updateStockPlayerPosition(stock);
|
||||
dialogBoxCreate("Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees.");
|
||||
if (tixApi) {
|
||||
if (workerScript.shouldLog("buyStock")) {
|
||||
workerScript.log(
|
||||
"Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns true if successful and false otherwise
|
||||
function sellStock(stock, shares) {
|
||||
if (shares == 0) {return false;}
|
||||
/**
|
||||
* Attempt to sell a stock in the long position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to sell
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise
|
||||
*/
|
||||
export function sellStock(stock, shares, workerScript=null) {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Sanitize/Validate arguments
|
||||
if (stock == null || shares < 0 || isNaN(shares)) {
|
||||
dialogBoxCreate("Failed to sell stock. This may be a bug, contact developer");
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: sellStock() failed due to invalid arguments`);
|
||||
} else {
|
||||
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares > stock.playerShares) {shares = stock.playerShares;}
|
||||
if (shares === 0) {return false;}
|
||||
|
||||
const gains = stock.price * shares - CONSTANTS.StockMarketCommission;
|
||||
const netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission;
|
||||
let netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission;
|
||||
if (isNaN(netProfit)) { netProfit = 0; }
|
||||
Player.gainMoney(gains);
|
||||
Player.recordMoneySource(netProfit, "stock");
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.onlineMoneyMade += netProfit;
|
||||
Player.scriptProdSinceLastAug += netProfit;
|
||||
}
|
||||
|
||||
stock.playerShares = Math.round(stock.playerShares - shares);
|
||||
if (stock.playerShares == 0) {
|
||||
if (stock.playerShares === 0) {
|
||||
stock.playerAvgPx = 0;
|
||||
}
|
||||
updateStockPlayerPosition(stock);
|
||||
dialogBoxCreate("Sold " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(gains, '($0.000a)') + ".");
|
||||
if (tixApi) {
|
||||
if (workerScript.shouldLog("sellStock")) {
|
||||
workerScript.log(
|
||||
"Sold " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(gains, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dialogBoxCreate(
|
||||
"Sold " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(gains, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns true if successful and false otherwise
|
||||
function shortStock(stock, shares, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
/**
|
||||
* Attempt to buy a stock in the short position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to short
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @returns {boolean} - true if successful, false otherwise
|
||||
*/
|
||||
export function shortStock(stock, shares, workerScript=null) {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Validate arguments
|
||||
shares = Math.round(shares);
|
||||
if (shares === 0 || shares < 0) { return false; }
|
||||
if (stock == null || isNaN(shares)) {
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("ERROR: shortStock() failed because of invalid arguments.");
|
||||
workerScript.log("ERROR: shortStock() failed because of invalid arguments.");
|
||||
} else {
|
||||
dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " +
|
||||
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||||
@ -495,15 +509,15 @@ function shortStock(stock, shares, workerScript=null) {
|
||||
}
|
||||
|
||||
// Does the player have enough money?
|
||||
var totalPrice = stock.price * shares;
|
||||
const totalPrice = stock.price * shares;
|
||||
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("ERROR: shortStock() failed because you do not have enough " +
|
||||
"money to purchase this short position. You need " +
|
||||
numeralWrapper.format(totalPrice + CONSTANTS.StockMarketCommission, '($0.000a)') + ".");
|
||||
workerScript.log("ERROR: shortStock() failed because you do not have enough " +
|
||||
"money to purchase this short position. You need " +
|
||||
numeralWrapper.formatMoney(totalPrice + CONSTANTS.StockMarketCommission));
|
||||
} else {
|
||||
dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
|
||||
numeralWrapper.format(totalPrice + CONSTANTS.StockMarketCommission, '($0.000a)') + ".");
|
||||
numeralWrapper.formatMoney(totalPrice + CONSTANTS.StockMarketCommission));
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -512,55 +526,66 @@ function shortStock(stock, shares, workerScript=null) {
|
||||
// Would this purchase exceed the maximum number of shares?
|
||||
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("ERROR: shortStock() failed because purchasing this many short shares would exceed " +
|
||||
`${stock.symbol}'s maximum number of shares.`);
|
||||
workerScript.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
|
||||
} else {
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
|
||||
`${stock.maxShares} shares.`);
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var origTotal = stock.playerShortShares * stock.playerAvgShortPx;
|
||||
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
|
||||
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||
var newTotal = origTotal + totalPrice;
|
||||
const newTotal = origTotal + totalPrice;
|
||||
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||||
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||||
updateStockPlayerPosition(stock);
|
||||
if (tixApi) {
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.shortStock == null) {
|
||||
workerScript.scriptRef.log("Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees.");
|
||||
workerScript.log(
|
||||
"Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dialogBoxCreate("Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees.");
|
||||
dialogBoxCreate(
|
||||
"Bought a short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. Paid " +
|
||||
numeralWrapper.format(CONSTANTS.StockMarketCommission, '($0.000a)') + " in commission fees."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns true if successful and false otherwise
|
||||
function sellShort(stock, shares, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
/**
|
||||
* Attempt to sell a stock in the short position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to sell
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @returns {boolean} true if successfully sells given amount OR max owned, false otherwise
|
||||
*/
|
||||
export function sellShort(stock, shares, workerScript=null) {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
if (stock == null || isNaN(shares) || shares < 0) {
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("ERROR: sellShort() failed because of invalid arguments.");
|
||||
workerScript.log("ERROR: sellShort() failed because of invalid arguments.");
|
||||
} else {
|
||||
dialogBoxCreate("Failed to sell a short position in a stock. This is probably " +
|
||||
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares > stock.playerShortShares) {shares = stock.playerShortShares;}
|
||||
if (shares === 0) {return false;}
|
||||
|
||||
var origCost = shares * stock.playerAvgShortPx;
|
||||
var profit = ((stock.playerAvgShortPx - stock.price) * shares) - CONSTANTS.StockMarketCommission;
|
||||
if (isNaN(profit)) {profit = 0;}
|
||||
const origCost = shares * stock.playerAvgShortPx;
|
||||
let profit = ((stock.playerAvgShortPx - stock.price) * shares) - CONSTANTS.StockMarketCommission;
|
||||
if (isNaN(profit)) { profit = 0; }
|
||||
Player.gainMoney(origCost + profit);
|
||||
Player.recordMoneySource(profit, "stock");
|
||||
if (tixApi) {
|
||||
@ -574,21 +599,25 @@ function sellShort(stock, shares, workerScript=null) {
|
||||
}
|
||||
updateStockPlayerPosition(stock);
|
||||
if (tixApi) {
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.sellShort == null) {
|
||||
workerScript.scriptRef.log("Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + ".");
|
||||
if (workerScript.shouldLog("sellShort")) {
|
||||
workerScript.log(
|
||||
"Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dialogBoxCreate("Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + ".");
|
||||
dialogBoxCreate(
|
||||
"Sold your short position of " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
|
||||
numeralWrapper.format(stock.price, '($0.000a)') + " per share. After commissions, you gained " +
|
||||
"a total of " + numeralWrapper.format(origCost + profit, '($0.000a)') + "."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processStockPrices(numCycles=1) {
|
||||
export function processStockPrices(numCycles=1) {
|
||||
if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; }
|
||||
StockMarket.storedCycles += numCycles;
|
||||
|
||||
@ -726,14 +755,15 @@ function processOrders(stock, orderType, posType) {
|
||||
}
|
||||
}
|
||||
|
||||
function setStockMarketContentCreated(b) {
|
||||
export function setStockMarketContentCreated(b) {
|
||||
stockMarketContentCreated = b;
|
||||
}
|
||||
|
||||
var stockMarketContentCreated = false;
|
||||
var stockMarketPortfolioMode = false;
|
||||
var COMM = CONSTANTS.StockMarketCommission;
|
||||
function displayStockMarketContent() {
|
||||
export function displayStockMarketContent() {
|
||||
// Backwards compatibility
|
||||
if (Player.hasWseAccount == null) {Player.hasWseAccount = false;}
|
||||
if (Player.hasTixApiAccess == null) {Player.hasTixApiAccess = false;}
|
||||
if (Player.has4SData == null) {Player.has4SData = false;}
|
||||
@ -1341,9 +1371,9 @@ function setStockTickerClickHandlers() {
|
||||
}
|
||||
}
|
||||
|
||||
//'increase' argument is a boolean indicating whether the price increased or decreased
|
||||
function updateStockTicker(stock, increase) {
|
||||
if (!routing.isOn(Page.StockMarket)) {return;}
|
||||
// 'increase' argument is a boolean indicating whether the price increased or decreased
|
||||
export function updateStockTicker(stock, increase) {
|
||||
if (!routing.isOn(Page.StockMarket)) { return; }
|
||||
if (!(stock instanceof Stock)) {
|
||||
console.log("Invalid stock in updateStockTicker():");
|
||||
console.log(stock);
|
||||
@ -1378,8 +1408,8 @@ function updateStockTicker(stock, increase) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateStockPlayerPosition(stock) {
|
||||
if (!routing.isOn(Page.StockMarket)) {return;}
|
||||
export function updateStockPlayerPosition(stock) {
|
||||
if (!routing.isOn(Page.StockMarket)) { return; }
|
||||
if (!(stock instanceof Stock)) {
|
||||
console.log("Invalid stock in updateStockPlayerPosition():");
|
||||
console.log(stock);
|
||||
@ -1395,8 +1425,8 @@ function updateStockPlayerPosition(stock) {
|
||||
removeElementById(tickerId + "-panel");
|
||||
return;
|
||||
} else {
|
||||
//If the ticker hasn't been created, create it (handles updating)
|
||||
//If it has been created, continue normally
|
||||
// If the ticker hasn't been created, create it (handles updating)
|
||||
// If it has been created, continue normally
|
||||
if (document.getElementById(tickerId + "-hdr") == null) {
|
||||
createStockTicker(stock);
|
||||
setStockTickerClickHandlers();
|
||||
@ -1413,7 +1443,7 @@ function updateStockPlayerPosition(stock) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Calculate returns
|
||||
// Calculate returns
|
||||
const totalCost = stock.playerShares * stock.playerAvgPx;
|
||||
let gains = (stock.price - stock.playerAvgPx) * stock.playerShares;
|
||||
let percentageGains = gains / totalCost;
|
||||
@ -1426,6 +1456,8 @@ function updateStockPlayerPosition(stock) {
|
||||
|
||||
stock.posTxtEl.innerHTML =
|
||||
`Max Shares: ${numeralWrapper.format(stock.maxShares, "0.000a")}<br>` +
|
||||
`<p class="tooltip">Ask Price: ${numeralWrapper.formatMoney(stock.getAskPrice())}<span class="tooltiptext">See Investopedia for details on what this is</span></p><br>` +
|
||||
`<p class="tooltip">Bid Price: ${numeralWrapper.formatMoney(stock.getBidPrice())}<span class="tooltiptext">See Investopedia for details on what this is</span></p><br>` +
|
||||
"<h3 class='tooltip stock-market-position-text'>Long Position: " +
|
||||
"<span class='tooltiptext'>Shares in the long position will increase " +
|
||||
"in value if the price of the corresponding stock increases</span></h3>" +
|
||||
@ -1493,7 +1525,7 @@ function updateStockOrderList(stock) {
|
||||
}
|
||||
}
|
||||
|
||||
//Remove everything from list
|
||||
// Remove everything from list
|
||||
while (orderList.firstChild) {
|
||||
orderList.removeChild(orderList.firstChild);
|
||||
}
|
||||
@ -1522,9 +1554,3 @@ function updateStockOrderList(stock) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols,
|
||||
initStockMarket, initSymbolToStockMap, stockMarketCycle, buyStock,
|
||||
sellStock, shortStock, sellShort, processStockPrices, displayStockMarketContent,
|
||||
updateStockTicker, updateStockPlayerPosition, loadStockMarket,
|
||||
setStockMarketContentCreated, placeOrder, cancelOrder, Order, OrderTypes, PositionTypes};
|
||||
|
109
src/StockMarket/StockMarketHelpers.ts
Normal file
109
src/StockMarket/StockMarketHelpers.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { Stock } from "./Stock";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
/**
|
||||
* Calculate the total cost of a "buy" transaction. This accounts for spread,
|
||||
* price movements, and commission.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
* @returns {number | null} Total transaction cost. Returns null for an invalid transaction
|
||||
*/
|
||||
export function getBuyTransactionCost(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price movement, its a simple calculation
|
||||
if (shares <= stock.shareTxUntilMovement) {
|
||||
if (isLong) {
|
||||
return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission;
|
||||
} else {
|
||||
return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
let totalCost = (stock.shareTxUntilMovement * currPrice);
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalCost += (amt * currPrice);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return totalCost + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts
|
||||
* for spread, price movements, and commission.
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
* @returns {number | null} Amount of money gained from transaction. Returns null for an invalid transaction
|
||||
*/
|
||||
export function getSellTransactionGain(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price mvoement, its a simple calculation
|
||||
if (shares <= stock.shareTxUntilMovement) {
|
||||
if (isLong) {
|
||||
return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission;
|
||||
} else {
|
||||
// Calculating gains for a short position requires calculating the profit made
|
||||
const origCost = shares * stock.playerAvgShortPx;
|
||||
const profit = ((stock.playerAvgShortPx - stock.getAskPrice()) * shares) - CONSTANTS.StockMarketCommission;
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to accoutn for
|
||||
let remainingShares = shares - stock.shareTxUntilMovement;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// Helper function to calculate gain for a single iteration
|
||||
function calculateGain(thisPrice: number, thisShares: number) {
|
||||
if (isLong) {
|
||||
return thisShares * thisPrice;
|
||||
} else {
|
||||
const origCost = thisShares * stock.playerAvgShortPx;
|
||||
const profit = ((stock.playerAvgShortPx - thisPrice) * thisShares);
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
let totalGain = calculateGain(currPrice, stock.shareTxUntilMovement);
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalGain += calculateGain(currPrice, amt);
|
||||
remainingShares -= amt;
|
||||
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= (1 - (stock.priceMovementPerc / 100));
|
||||
} else {
|
||||
currPrice *= (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return totalGain - CONSTANTS.StockMarketCommission;
|
||||
}
|
873
src/StockMarket/data/InitStockMetadata.ts
Normal file
873
src/StockMarket/data/InitStockMetadata.ts
Normal file
@ -0,0 +1,873 @@
|
||||
/**
|
||||
* Initialization metadata for all Stocks. This is used to generate the
|
||||
* stock parameter values upon a reset
|
||||
*
|
||||
* Some notes:
|
||||
* - Megacorporations have better otlkMags
|
||||
* - Higher volatility -> Bigger spread
|
||||
* - Lower price -> Bigger spread
|
||||
* - Share tx required for movement used for balancing
|
||||
*/
|
||||
import { StockSymbols } from "./StockSymbols";
|
||||
import { IConstructorParams } from "../Stock";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
|
||||
export const InitStockMetadata: IConstructorParams[] = [
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 28e3,
|
||||
min: 17e3,
|
||||
},
|
||||
marketCap: 2.4e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 50,
|
||||
min: 40,
|
||||
},
|
||||
name: LocationName.AevumECorp,
|
||||
otlkMag: 19,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 5,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumECorp],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 34e3,
|
||||
min: 24e3,
|
||||
},
|
||||
marketCap: 2.4e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 50,
|
||||
min: 40,
|
||||
},
|
||||
name: LocationName.Sector12MegaCorp,
|
||||
otlkMag: 19,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 5,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12MegaCorp],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 25e3,
|
||||
min: 12e3,
|
||||
},
|
||||
marketCap: 1.6e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: LocationName.Sector12BladeIndustries,
|
||||
otlkMag: 13,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 6,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12BladeIndustries],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 25e3,
|
||||
min: 10e3,
|
||||
},
|
||||
marketCap: 1.5e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 75,
|
||||
min: 65,
|
||||
},
|
||||
name: LocationName.AevumClarkeIncorporated,
|
||||
otlkMag: 12,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 5,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumClarkeIncorporated],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 43e3,
|
||||
min: 32e3,
|
||||
},
|
||||
marketCap: 1.8e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 70,
|
||||
min: 60,
|
||||
},
|
||||
name: LocationName.VolhavenOmniTekIncorporated,
|
||||
otlkMag: 12,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 6,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 80e3,
|
||||
min: 50e3,
|
||||
},
|
||||
marketCap: 2e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 110,
|
||||
min: 100,
|
||||
},
|
||||
name: LocationName.Sector12FourSigma,
|
||||
otlkMag: 17,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12FourSigma],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 28e3,
|
||||
min: 16e3,
|
||||
},
|
||||
marketCap: 1.9e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 85,
|
||||
min: 75,
|
||||
},
|
||||
name: LocationName.ChongqingKuaiGongInternational,
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 7,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 36e3,
|
||||
min: 29e3,
|
||||
},
|
||||
marketCap: 2e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 130,
|
||||
min: 120,
|
||||
},
|
||||
name: LocationName.AevumFulcrumTechnologies,
|
||||
otlkMag: 16,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumFulcrumTechnologies],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 25e3,
|
||||
min: 20e3,
|
||||
},
|
||||
marketCap: 1.2e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 90,
|
||||
min: 80,
|
||||
},
|
||||
name: LocationName.IshimaStormTechnologies,
|
||||
otlkMag: 7,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.IshimaStormTechnologies],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 19e3,
|
||||
min: 6e3,
|
||||
},
|
||||
marketCap: 900e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 70,
|
||||
min: 60,
|
||||
},
|
||||
name: LocationName.NewTokyoDefComm,
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.NewTokyoDefComm],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 18e3,
|
||||
min: 10e3,
|
||||
},
|
||||
marketCap: 825e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 65,
|
||||
min: 55,
|
||||
},
|
||||
name: LocationName.VolhavenHeliosLabs,
|
||||
otlkMag: 9,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenHeliosLabs],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 14e3,
|
||||
min: 8e3,
|
||||
},
|
||||
marketCap: 1e12,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: LocationName.NewTokyoVitaLife,
|
||||
otlkMag: 7,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.NewTokyoVitaLife],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 24e3,
|
||||
min: 12e3,
|
||||
},
|
||||
marketCap: 800e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 70,
|
||||
min: 60,
|
||||
},
|
||||
name: LocationName.Sector12IcarusMicrosystems,
|
||||
otlkMag: 7.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 3,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 29e3,
|
||||
min: 16e3,
|
||||
},
|
||||
marketCap: 900e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 60,
|
||||
min: 50,
|
||||
},
|
||||
name: LocationName.Sector12UniversalEnergy,
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12UniversalEnergy],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 17e3,
|
||||
min: 8e3,
|
||||
},
|
||||
marketCap: 640e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 65,
|
||||
min: 55,
|
||||
},
|
||||
name: LocationName.AevumAeroCorp,
|
||||
otlkMag: 6,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 3,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 14e3,
|
||||
min: 7e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumAeroCorp],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 15e3,
|
||||
min: 6e3,
|
||||
},
|
||||
marketCap: 600e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 75,
|
||||
min: 65,
|
||||
},
|
||||
name: LocationName.VolhavenOmniaCybersystems,
|
||||
otlkMag: 4.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 11,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 14e3,
|
||||
min: 7e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 28e3,
|
||||
min: 14e3,
|
||||
},
|
||||
marketCap: 705e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: LocationName.ChongqingSolarisSpaceSystems,
|
||||
otlkMag: 8.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 12,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 14e3,
|
||||
min: 7e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 30e3,
|
||||
min: 12e3,
|
||||
},
|
||||
marketCap: 695e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 65,
|
||||
min: 55,
|
||||
},
|
||||
name: LocationName.NewTokyoGlobalPharmaceuticals,
|
||||
otlkMag: 10.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 14e3,
|
||||
min: 7e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 27e3,
|
||||
min: 15e3,
|
||||
},
|
||||
marketCap: 600e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: LocationName.IshimaNovaMedical,
|
||||
otlkMag: 5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 11,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 14e3,
|
||||
min: 7e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.IshimaNovaMedical],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 8.5e3,
|
||||
min: 4e3,
|
||||
},
|
||||
marketCap: 450e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 260,
|
||||
min: 240,
|
||||
},
|
||||
name: LocationName.AevumWatchdogSecurity,
|
||||
otlkMag: 1.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 12,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 6e3,
|
||||
min: 2e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumWatchdogSecurity],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 8e3,
|
||||
min: 4.5e3,
|
||||
},
|
||||
marketCap: 300e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 135,
|
||||
min: 115,
|
||||
},
|
||||
name: LocationName.VolhavenLexoCorp,
|
||||
otlkMag: 6,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 12,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 12e3,
|
||||
min: 6e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenLexoCorp],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 7e3,
|
||||
min: 2e3,
|
||||
},
|
||||
marketCap: 180e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 70,
|
||||
min: 50,
|
||||
},
|
||||
name: LocationName.AevumRhoConstruction,
|
||||
otlkMag: 1,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 3,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 20e3,
|
||||
min: 10e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumRhoConstruction],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 8.5e3,
|
||||
min: 4e3,
|
||||
},
|
||||
marketCap: 240e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 205,
|
||||
min: 175,
|
||||
},
|
||||
name: LocationName.Sector12AlphaEnterprises,
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 16,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12AlphaEnterprises],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 8e3,
|
||||
min: 3e3,
|
||||
},
|
||||
marketCap: 200e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 170,
|
||||
min: 150,
|
||||
},
|
||||
name: LocationName.VolhavenSysCoreSecurities,
|
||||
otlkMag: 3,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 12,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 6e3,
|
||||
min: 1e3,
|
||||
},
|
||||
marketCap: 185e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 100,
|
||||
min: 80,
|
||||
},
|
||||
name: LocationName.VolhavenCompuTek,
|
||||
otlkMag: 4,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 12,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 15e3,
|
||||
min: 10e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.VolhavenCompuTek],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 5e3,
|
||||
min: 1e3,
|
||||
},
|
||||
marketCap: 58e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 430,
|
||||
min: 400,
|
||||
},
|
||||
name: LocationName.AevumNetLinkTechnologies,
|
||||
otlkMag: 1,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 20,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 6e3,
|
||||
min: 3e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.AevumNetLinkTechnologies],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 8e3,
|
||||
min: 1e3,
|
||||
},
|
||||
marketCap: 60e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 110,
|
||||
min: 90,
|
||||
},
|
||||
name: LocationName.IshimaOmegaSoftware,
|
||||
otlkMag: 0.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 13,
|
||||
min: 4,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.IshimaOmegaSoftware],
|
||||
},
|
||||
|
||||
{
|
||||
b: false,
|
||||
initPrice: {
|
||||
max: 4.5e3,
|
||||
min: 500,
|
||||
},
|
||||
marketCap: 45e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: LocationName.Sector12FoodNStuff,
|
||||
otlkMag: 1,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 6,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 20e3,
|
||||
min: 10e3,
|
||||
},
|
||||
symbol: StockSymbols[LocationName.Sector12FoodNStuff],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 3.5e3,
|
||||
min: 1.5e3,
|
||||
},
|
||||
marketCap: 30e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 300,
|
||||
min: 260,
|
||||
},
|
||||
name: "Sigma Cosmetics",
|
||||
otlkMag: 0,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 14,
|
||||
min: 6,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 8e3,
|
||||
min: 4e3,
|
||||
},
|
||||
symbol: StockSymbols["Sigma Cosmetics"],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 1.5e3,
|
||||
min: 250,
|
||||
},
|
||||
marketCap: 42e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 400,
|
||||
min: 360,
|
||||
},
|
||||
name: "Joes Guns",
|
||||
otlkMag: 1,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 14,
|
||||
min: 6,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 7e3,
|
||||
min: 3e3,
|
||||
},
|
||||
symbol: StockSymbols["Joes Guns"],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 1.5e3,
|
||||
min: 250,
|
||||
},
|
||||
marketCap: 100e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 175,
|
||||
min: 120,
|
||||
},
|
||||
name: "Catalyst Ventures",
|
||||
otlkMag: 13.5,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 14,
|
||||
min: 5,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 8e3,
|
||||
min: 4e3,
|
||||
},
|
||||
symbol: StockSymbols["Catalyst Ventures"],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 30e3,
|
||||
min: 15e3,
|
||||
},
|
||||
marketCap: 360e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 80,
|
||||
min: 70,
|
||||
},
|
||||
name: "Microdyne Technologies",
|
||||
otlkMag: 8,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 3,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 25e3,
|
||||
min: 15e3,
|
||||
},
|
||||
symbol: StockSymbols["Microdyne Technologies"],
|
||||
},
|
||||
|
||||
{
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 24e3,
|
||||
min: 12e3,
|
||||
},
|
||||
marketCap: 420e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 70,
|
||||
min: 50,
|
||||
},
|
||||
name: "Titan Laboratories",
|
||||
otlkMag: 11,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 2,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 25e3,
|
||||
min: 15e3,
|
||||
},
|
||||
symbol: StockSymbols["Titan Laboratories"],
|
||||
},
|
||||
];
|
6
src/StockMarket/data/OrderTypes.ts
Normal file
6
src/StockMarket/data/OrderTypes.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export enum OrderTypes {
|
||||
LimitBuy = "Limit Buy Order",
|
||||
LimitSell = "Limit Sell Order",
|
||||
StopBuy = "Stop Buy Order",
|
||||
StopSell = "Stop Sell Order"
|
||||
}
|
4
src/StockMarket/data/PositionTypes.ts
Normal file
4
src/StockMarket/data/PositionTypes.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum PositionTypes {
|
||||
Long = "L",
|
||||
Short = "S"
|
||||
}
|
41
src/StockMarket/data/StockSymbols.ts
Normal file
41
src/StockMarket/data/StockSymbols.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { IMap } from "../../types";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
|
||||
export const StockSymbols: IMap<string> = {};
|
||||
|
||||
// Stocks for companies at which you can work
|
||||
StockSymbols[LocationName.AevumECorp] = "ECP";
|
||||
StockSymbols[LocationName.Sector12MegaCorp] = "MGCP";
|
||||
StockSymbols[LocationName.Sector12BladeIndustries] = "BLD";
|
||||
StockSymbols[LocationName.AevumClarkeIncorporated] = "CLRK";
|
||||
StockSymbols[LocationName.VolhavenOmniTekIncorporated] = "OMTK";
|
||||
StockSymbols[LocationName.Sector12FourSigma] = "FSIG";
|
||||
StockSymbols[LocationName.ChongqingKuaiGongInternational] = "KGI";
|
||||
StockSymbols[LocationName.AevumFulcrumTechnologies] = "FLCM";
|
||||
StockSymbols[LocationName.IshimaStormTechnologies] = "STM";
|
||||
StockSymbols[LocationName.NewTokyoDefComm] = "DCOMM";
|
||||
StockSymbols[LocationName.VolhavenHeliosLabs] = "HLS";
|
||||
StockSymbols[LocationName.NewTokyoVitaLife] = "VITA";
|
||||
StockSymbols[LocationName.Sector12IcarusMicrosystems] = "ICRS";
|
||||
StockSymbols[LocationName.Sector12UniversalEnergy] = "UNV";
|
||||
StockSymbols[LocationName.AevumAeroCorp] = "AERO";
|
||||
StockSymbols[LocationName.VolhavenOmniaCybersystems] = "OMN";
|
||||
StockSymbols[LocationName.ChongqingSolarisSpaceSystems] = "SLRS";
|
||||
StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals] = "GPH";
|
||||
StockSymbols[LocationName.IshimaNovaMedical] = "NVMD";
|
||||
StockSymbols[LocationName.AevumWatchdogSecurity] = "WDS";
|
||||
StockSymbols[LocationName.VolhavenLexoCorp] = "LXO";
|
||||
StockSymbols[LocationName.AevumRhoConstruction] = "RHOC";
|
||||
StockSymbols[LocationName.Sector12AlphaEnterprises] = "APHE";
|
||||
StockSymbols[LocationName.VolhavenSysCoreSecurities] = "SYSC";
|
||||
StockSymbols[LocationName.VolhavenCompuTek] = "CTK";
|
||||
StockSymbols[LocationName.AevumNetLinkTechnologies] = "NTLK";
|
||||
StockSymbols[LocationName.IshimaOmegaSoftware] = "OMGA";
|
||||
StockSymbols[LocationName.Sector12FoodNStuff] = "FNS";
|
||||
|
||||
// Stocks for other companies
|
||||
StockSymbols["Sigma Cosmetics"] = "SGC";
|
||||
StockSymbols["Joes Guns"] = "JGN";
|
||||
StockSymbols["Catalyst Ventures"] = "CTYS";
|
||||
StockSymbols["Microdyne Technologies"] = "MDYN";
|
||||
StockSymbols["Titan Laboratories"] = "TITN";
|
@ -84,7 +84,6 @@ import {
|
||||
StockMarket,
|
||||
StockSymbols,
|
||||
SymbolToStockMap,
|
||||
initStockSymbols,
|
||||
initSymbolToStockMap,
|
||||
stockMarketCycle,
|
||||
processStockPrices,
|
||||
@ -1085,7 +1084,6 @@ const Engine = {
|
||||
Engine.init(); // Initialize buttons, work, etc.
|
||||
initAugmentations(); // Also calls Player.reapplyAllAugmentations()
|
||||
Player.reapplyAllSourceFiles();
|
||||
initStockSymbols();
|
||||
if (Player.hasWseAccount) {
|
||||
initSymbolToStockMap();
|
||||
}
|
||||
@ -1215,7 +1213,6 @@ const Engine = {
|
||||
initFactions();
|
||||
initAugmentations();
|
||||
initMessages();
|
||||
initStockSymbols();
|
||||
initLiterature();
|
||||
initSingularitySFFlags();
|
||||
|
||||
|
22
src/types.ts
22
src/types.ts
@ -44,3 +44,25 @@ export interface IReturnStatus {
|
||||
res: boolean;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the minimum and maximum values for a range.
|
||||
* It is up to the consumer if these values are inclusive or exclusive.
|
||||
* It is up to the implementor to ensure max > min.
|
||||
*/
|
||||
export interface IMinMaxRange {
|
||||
/**
|
||||
* Value by which the bounds are to be divided for the final range
|
||||
*/
|
||||
divisor?: number;
|
||||
|
||||
/**
|
||||
* The maximum bound of the range.
|
||||
*/
|
||||
max: number;
|
||||
|
||||
/**
|
||||
* The minimum bound of the range.
|
||||
*/
|
||||
min: number;
|
||||
}
|
||||
|
Reference in New Issue
Block a user