mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-30 09:57:32 +01:00
Fixed Stock market UI compilation errors and removed refactored code
This commit is contained in:
parent
4809a21e38
commit
d7fb335815
@ -75,19 +75,18 @@ import { SpecialServerIps } from "./Server/SpecialServerIps";
|
|||||||
import { Stock } from "./StockMarket/Stock";
|
import { Stock } from "./StockMarket/Stock";
|
||||||
import {
|
import {
|
||||||
StockMarket,
|
StockMarket,
|
||||||
StockSymbols,
|
|
||||||
SymbolToStockMap,
|
SymbolToStockMap,
|
||||||
initSymbolToStockMap,
|
initSymbolToStockMap,
|
||||||
buyStock,
|
buyStock,
|
||||||
sellStock,
|
sellStock,
|
||||||
updateStockPlayerPosition,
|
|
||||||
shortStock,
|
shortStock,
|
||||||
sellShort,
|
sellShort,
|
||||||
OrderTypes,
|
|
||||||
PositionTypes,
|
|
||||||
placeOrder,
|
placeOrder,
|
||||||
cancelOrder
|
cancelOrder
|
||||||
} from "./StockMarket/StockMarket";
|
} from "./StockMarket/StockMarket";
|
||||||
|
import { OrderTypes } from "./StockMarket/data/OrderTypes";
|
||||||
|
import { PositionTypes } from "./StockMarket/data/PositionTypes";
|
||||||
|
import { StockSymbols } from "./StockMarket/data/StockSymbols";
|
||||||
import {
|
import {
|
||||||
getStockmarket4SDataCost,
|
getStockmarket4SDataCost,
|
||||||
getStockMarket4STixApiCost
|
getStockMarket4STixApiCost
|
||||||
|
@ -47,8 +47,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
initStockMarket,
|
initStockMarket,
|
||||||
initSymbolToStockMap,
|
initSymbolToStockMap,
|
||||||
stockMarketContentCreated,
|
|
||||||
setStockMarketContentCreated
|
|
||||||
} from "./StockMarket/StockMarket";
|
} from "./StockMarket/StockMarket";
|
||||||
import { Terminal, postNetburnerText } from "./Terminal";
|
import { Terminal, postNetburnerText } from "./Terminal";
|
||||||
|
|
||||||
@ -163,13 +161,6 @@ function prestigeAugmentation() {
|
|||||||
initStockMarket();
|
initStockMarket();
|
||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
}
|
}
|
||||||
setStockMarketContentCreated(false);
|
|
||||||
var stockMarketList = document.getElementById("stock-market-list");
|
|
||||||
while(stockMarketList.firstChild) {
|
|
||||||
stockMarketList.removeChild(stockMarketList.firstChild);
|
|
||||||
}
|
|
||||||
var watchlist = document.getElementById("stock-market-watchlist-filter");
|
|
||||||
watchlist.value = ""; // Reset watchlist filter
|
|
||||||
|
|
||||||
// Refresh Main Menu (the 'World' menu, specifically)
|
// Refresh Main Menu (the 'World' menu, specifically)
|
||||||
document.getElementById("world-menu-header").click();
|
document.getElementById("world-menu-header").click();
|
||||||
@ -336,11 +327,6 @@ function prestigeSourceFile() {
|
|||||||
initStockMarket();
|
initStockMarket();
|
||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
}
|
}
|
||||||
setStockMarketContentCreated(false);
|
|
||||||
var stockMarketList = document.getElementById("stock-market-list");
|
|
||||||
while(stockMarketList.firstChild) {
|
|
||||||
stockMarketList.removeChild(stockMarketList.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Player.inGang()) { Player.gang.clearUI(); }
|
if (Player.inGang()) { Player.gang.clearUI(); }
|
||||||
Player.gang = null;
|
Player.gang = null;
|
||||||
|
@ -35,10 +35,11 @@ function toNumber(n: number | IMinMaxRange): number {
|
|||||||
let value: number;
|
let value: number;
|
||||||
switch (typeof n) {
|
switch (typeof n) {
|
||||||
case "number": {
|
case "number": {
|
||||||
return n;
|
return <number>n;
|
||||||
}
|
}
|
||||||
case "object": {
|
case "object": {
|
||||||
value = getRandomInt(n.min, n.max);
|
const range = <IMinMaxRange>n;
|
||||||
|
value = getRandomInt(range.min, range.max);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
File diff suppressed because it is too large
Load Diff
637
src/StockMarket/StockMarket.jsx
Normal file
637
src/StockMarket/StockMarket.jsx
Normal file
@ -0,0 +1,637 @@
|
|||||||
|
import {
|
||||||
|
Order,
|
||||||
|
OrderTypes,
|
||||||
|
PositionTypes
|
||||||
|
} from "./Order";
|
||||||
|
import { Stock } from "./Stock";
|
||||||
|
import {
|
||||||
|
getStockMarket4SDataCost,
|
||||||
|
getStockMarket4STixApiCost
|
||||||
|
} from "./StockMarketCosts";
|
||||||
|
import { InitStockMetadata } from "./data/InitStockMetadata";
|
||||||
|
import { StockSymbols } from "./data/StockSymbols";
|
||||||
|
import { StockMarketRoot } from "./ui/Root";
|
||||||
|
|
||||||
|
import { CONSTANTS } from "../Constants";
|
||||||
|
import { WorkerScript } from "../NetscriptWorker";
|
||||||
|
import { Player } from "../Player";
|
||||||
|
|
||||||
|
import { Page, routing } from ".././ui/navigationTracking";
|
||||||
|
import { numeralWrapper } from ".././ui/numeralFormat";
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
|
import { Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOm from "react-dom";
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
if (tixApi) {
|
||||||
|
workerScript.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||||
|
} else {
|
||||||
|
dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StockMarket["Orders"] == null) {
|
||||||
|
const orders = {};
|
||||||
|
for (const name in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(name)) {
|
||||||
|
const stk = StockMarket[name];
|
||||||
|
if (!(stk instanceof Stock)) { continue; }
|
||||||
|
orders[stk.symbol] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StockMarket["Orders"] = orders;
|
||||||
|
}
|
||||||
|
StockMarket["Orders"][stock.symbol].push(order);
|
||||||
|
//Process to see if it should be executed immediately
|
||||||
|
processOrders(order.stock, order.type, order.pos);
|
||||||
|
displayStockMarketContent();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var order = params.order;
|
||||||
|
//An 'Order' object is passed in
|
||||||
|
var stockOrders = StockMarket["Orders"][order.stock.symbol];
|
||||||
|
for (var i = 0; i < stockOrders.length; ++i) {
|
||||||
|
if (order == stockOrders[i]) {
|
||||||
|
stockOrders.splice(i, 1);
|
||||||
|
displayStockMarketContent();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (params.stock && params.shares && params.price && params.type &&
|
||||||
|
params.pos && params.stock instanceof Stock) {
|
||||||
|
//Order properties are passed in. Need to look for the order
|
||||||
|
var stockOrders = StockMarket["Orders"][params.stock.symbol];
|
||||||
|
var orderTxt = params.stock.symbol + " - " + params.shares + " @ " +
|
||||||
|
numeralWrapper.format(params.price, '$0.000a');
|
||||||
|
for (var i = 0; i < stockOrders.length; ++i) {
|
||||||
|
var order = stockOrders[i];
|
||||||
|
if (params.shares === order.shares &&
|
||||||
|
params.price === order.price &&
|
||||||
|
params.type === order.type &&
|
||||||
|
params.pos === order.pos) {
|
||||||
|
stockOrders.splice(i, 1);
|
||||||
|
displayStockMarketContent();
|
||||||
|
if (tixApi) {
|
||||||
|
workerScript.scriptRef.log("Successfully cancelled order: " + orderTxt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tixApi) {
|
||||||
|
workerScript.scriptRef.log("Failed to cancel order: " + orderTxt);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeOrder(order) {
|
||||||
|
var stock = order.stock;
|
||||||
|
var orderBook = StockMarket["Orders"];
|
||||||
|
var stockOrders = orderBook[stock.symbol];
|
||||||
|
var res = true;
|
||||||
|
console.log("Executing the following order:");
|
||||||
|
console.log(order);
|
||||||
|
switch (order.type) {
|
||||||
|
case OrderTypes.LimitBuy:
|
||||||
|
case OrderTypes.StopBuy:
|
||||||
|
if (order.pos === PositionTypes.Long) {
|
||||||
|
res = buyStock(order.stock, order.shares) && res;
|
||||||
|
} else if (order.pos === PositionTypes.Short) {
|
||||||
|
res = shortStock(order.stock, order.shares) && res;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OrderTypes.LimitSell:
|
||||||
|
case OrderTypes.StopSell:
|
||||||
|
if (order.pos === PositionTypes.Long) {
|
||||||
|
res = sellStock(order.stock, order.shares) && res;
|
||||||
|
} else if (order.pos === PositionTypes.Short) {
|
||||||
|
res = sellShort(order.stock, order.shares) && res;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (res) {
|
||||||
|
//Remove order from order book
|
||||||
|
for (var i = 0; i < stockOrders.length; ++i) {
|
||||||
|
if (order == stockOrders[i]) {
|
||||||
|
stockOrders.splice(i, 1);
|
||||||
|
displayStockMarketContent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("ERROR: Could not find the following Order in Order Book: ");
|
||||||
|
console.log(order);
|
||||||
|
} else {
|
||||||
|
console.log("Order failed to execute");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let StockMarket = {}; // Maps full stock name -> Stock object
|
||||||
|
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
|
||||||
|
|
||||||
|
export function loadStockMarket(saveString) {
|
||||||
|
if (saveString === "") {
|
||||||
|
StockMarket = {};
|
||||||
|
} else {
|
||||||
|
StockMarket = JSON.parse(saveString, Reviver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initStockMarket() {
|
||||||
|
for (const stk in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(stk)) {
|
||||||
|
delete StockMarket[stk];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const metadata of InitStockMetadata) {
|
||||||
|
const name = metadata.name;
|
||||||
|
StockMarket[name] = new Stock(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orders = {};
|
||||||
|
for (const name in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(name)) {
|
||||||
|
const stock = StockMarket[name];
|
||||||
|
if (!(stock instanceof Stock)) { continue; }
|
||||||
|
orders[stock.symbol] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StockMarket["Orders"] = orders;
|
||||||
|
|
||||||
|
StockMarket.storedCycles = 0;
|
||||||
|
StockMarket.lastUpdate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initSymbolToStockMap() {
|
||||||
|
for (const name in StockSymbols) {
|
||||||
|
if (StockSymbols.hasOwnProperty(name)) {
|
||||||
|
const stock = StockMarket[name];
|
||||||
|
if (stock == null) {
|
||||||
|
console.error(`Could not find Stock for ${name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const symbol = StockSymbols[name];
|
||||||
|
SymbolToStockMap[symbol] = stock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stockMarketCycle() {
|
||||||
|
for (var name in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(name)) {
|
||||||
|
var stock = StockMarket[name];
|
||||||
|
if (!(stock instanceof Stock)) {continue;}
|
||||||
|
var thresh = 0.6;
|
||||||
|
if (stock.b) {thresh = 0.4;}
|
||||||
|
if (Math.random() < thresh) {
|
||||||
|
stock.b = !stock.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
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?
|
||||||
|
const totalPrice = stock.price * shares;
|
||||||
|
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origTotal = stock.playerShares * stock.playerAvgPx;
|
||||||
|
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||||
|
const newTotal = origTotal + totalPrice;
|
||||||
|
stock.playerShares = Math.round(stock.playerShares + shares);
|
||||||
|
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||||
|
displayStockMarketContent();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
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;
|
||||||
|
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) {
|
||||||
|
stock.playerAvgPx = 0;
|
||||||
|
}
|
||||||
|
displayStockMarketContent();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.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");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the player have enough money?
|
||||||
|
const totalPrice = stock.price * shares;
|
||||||
|
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
|
||||||
|
if (tixApi) {
|
||||||
|
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.formatMoney(totalPrice + CONSTANTS.StockMarketCommission));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Would this purchase exceed the maximum number of shares?
|
||||||
|
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||||
|
if (tixApi) {
|
||||||
|
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.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
|
||||||
|
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
|
||||||
|
const newTotal = origTotal + totalPrice;
|
||||||
|
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||||||
|
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||||||
|
displayStockMarketContent();
|
||||||
|
if (tixApi) {
|
||||||
|
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.shortStock == null) {
|
||||||
|
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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.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;}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
workerScript.scriptRef.onlineMoneyMade += profit;
|
||||||
|
Player.scriptProdSinceLastAug += profit;
|
||||||
|
}
|
||||||
|
|
||||||
|
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
|
||||||
|
if (stock.playerShortShares === 0) {
|
||||||
|
stock.playerAvgShortPx = 0;
|
||||||
|
}
|
||||||
|
displayStockMarketContent();
|
||||||
|
if (tixApi) {
|
||||||
|
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)') + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processStockPrices(numCycles=1) {
|
||||||
|
if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; }
|
||||||
|
StockMarket.storedCycles += numCycles;
|
||||||
|
|
||||||
|
// Stock Prices updated every 6 seconds on average. But if there are stored
|
||||||
|
// cycles they update 50% faster, so every 4 seconds
|
||||||
|
const msPerStockUpdate = 6e3;
|
||||||
|
const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle;
|
||||||
|
if (StockMarket.storedCycles < cyclesPerStockUpdate) { return; }
|
||||||
|
|
||||||
|
const timeNow = new Date().getTime();
|
||||||
|
if (timeNow - StockMarket.lastUpdate < 4e3) { return; }
|
||||||
|
|
||||||
|
StockMarket.lastUpdate = timeNow;
|
||||||
|
StockMarket.storedCycles -= cyclesPerStockUpdate;
|
||||||
|
|
||||||
|
var v = Math.random();
|
||||||
|
for (var name in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(name)) {
|
||||||
|
var stock = StockMarket[name];
|
||||||
|
if (!(stock instanceof Stock)) { continue; }
|
||||||
|
var av = (v * stock.mv) / 100;
|
||||||
|
if (isNaN(av)) {av = .02;}
|
||||||
|
|
||||||
|
var chc = 50;
|
||||||
|
if (stock.b) {
|
||||||
|
chc = (chc + stock.otlkMag) / 100;
|
||||||
|
if (isNaN(chc)) {chc = 0.5;}
|
||||||
|
} else {
|
||||||
|
chc = (chc - stock.otlkMag) / 100;
|
||||||
|
if (isNaN(chc)) {chc = 0.5;}
|
||||||
|
}
|
||||||
|
if (stock.price >= stock.cap) {
|
||||||
|
chc = 0.1; // "Soft Limit" on stock price. It could still go up but its unlikely
|
||||||
|
stock.b = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = Math.random();
|
||||||
|
if (c < chc) {
|
||||||
|
stock.price *= (1 + av);
|
||||||
|
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short);
|
||||||
|
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long);
|
||||||
|
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long);
|
||||||
|
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short);
|
||||||
|
displayStockMarketContent();
|
||||||
|
} else {
|
||||||
|
stock.price /= (1 + av);
|
||||||
|
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long);
|
||||||
|
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short);
|
||||||
|
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short);
|
||||||
|
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long);
|
||||||
|
displayStockMarketContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
var otlkMagChange = stock.otlkMag * av;
|
||||||
|
if (stock.otlkMag <= 0.1) {
|
||||||
|
otlkMagChange = 1;
|
||||||
|
}
|
||||||
|
if (c < 0.5) {
|
||||||
|
stock.otlkMag += otlkMagChange;
|
||||||
|
} else {
|
||||||
|
stock.otlkMag -= otlkMagChange;
|
||||||
|
}
|
||||||
|
if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100
|
||||||
|
if (stock.otlkMag < 0) {
|
||||||
|
stock.otlkMag *= -1;
|
||||||
|
stock.b = !stock.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Checks and triggers any orders for the specified stock
|
||||||
|
function processOrders(stock, orderType, posType) {
|
||||||
|
var orderBook = StockMarket["Orders"];
|
||||||
|
if (orderBook == null) {
|
||||||
|
var orders = {};
|
||||||
|
for (var name in StockMarket) {
|
||||||
|
if (StockMarket.hasOwnProperty(name)) {
|
||||||
|
var stock = StockMarket[name];
|
||||||
|
if (!(stock instanceof Stock)) {continue;}
|
||||||
|
orders[stock.symbol] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StockMarket["Orders"] = orders;
|
||||||
|
return; //Newly created, so no orders to process
|
||||||
|
}
|
||||||
|
var stockOrders = orderBook[stock.symbol];
|
||||||
|
if (stockOrders == null || !(stockOrders.constructor === Array)) {
|
||||||
|
console.log("ERROR: Invalid Order book for " + stock.symbol + " in processOrders()");
|
||||||
|
stockOrders = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < stockOrders.length; ++i) {
|
||||||
|
var order = stockOrders[i];
|
||||||
|
if (order.type === orderType && order.pos === posType) {
|
||||||
|
switch(order.type) {
|
||||||
|
case OrderTypes.LimitBuy:
|
||||||
|
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OrderTypes.LimitSell:
|
||||||
|
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OrderTypes.StopBuy:
|
||||||
|
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OrderTypes.StopSell:
|
||||||
|
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||||||
|
executeOrder/*66*/(order);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Invalid order type: " + order.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stockMarketContainer = null;
|
||||||
|
function setStockMarketContainer() {
|
||||||
|
stockMarketContainer = document.getElementById("stock-market-container");
|
||||||
|
document.removeEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||||
|
|
||||||
|
|
||||||
|
function initStockMarketFnForReact() {
|
||||||
|
initStockMarket();
|
||||||
|
initSymbolToStockMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function displayStockMarketContent() {
|
||||||
|
if (!routing.isOn(Page.StockMarket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stockMarketContainer instanceof HTMLElement) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<StockMarketRoot
|
||||||
|
buyStockLong={buyStock}
|
||||||
|
buyStockShort={shortStock}
|
||||||
|
cancelOrder={cancelOrder}
|
||||||
|
initStockMarket={initStockMarketFnForReact}
|
||||||
|
p={Player}
|
||||||
|
placeOrder={placeOrder}
|
||||||
|
sellStockLong={sellStock}
|
||||||
|
sellStockShort={sellShort}
|
||||||
|
stockMarket={StockMarket}
|
||||||
|
/>,
|
||||||
|
stockMarketContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -91,6 +91,10 @@ export class StockTickers extends React.Component<IProps, IState> {
|
|||||||
for (const stockMarketProp in this.props.stockMarket) {
|
for (const stockMarketProp in this.props.stockMarket) {
|
||||||
const val = this.props.stockMarket[stockMarketProp];
|
const val = this.props.stockMarket[stockMarketProp];
|
||||||
if (val instanceof Stock) {
|
if (val instanceof Stock) {
|
||||||
|
if (this.state.watchlistSymbols.length > 0 && !this.state.watchlistSymbols.includes(val.symbol)) {
|
||||||
|
continue; // Not in watchlist
|
||||||
|
}
|
||||||
|
|
||||||
let orders = this.props.stockMarket.Orders[val.symbol];
|
let orders = this.props.stockMarket.Orders[val.symbol];
|
||||||
if (orders == null) {
|
if (orders == null) {
|
||||||
orders = [];
|
orders = [];
|
||||||
|
Loading…
Reference in New Issue
Block a user