bitburner-src/src/NetscriptFunctions/StockMarket.ts

413 lines
15 KiB
TypeScript
Raw Normal View History

2021-10-14 09:22:02 +02:00
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket";
2021-10-14 09:22:02 +02:00
import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers";
import { OrderTypes } from "../StockMarket/data/OrderTypes";
import { PositionTypes } from "../StockMarket/data/PositionTypes";
import { StockSymbols } from "../StockMarket/data/StockSymbols";
2022-03-19 17:58:18 +01:00
import {
getStockMarket4SDataCost,
getStockMarket4STixApiCost,
getStockMarketWseCost,
getStockMarketTixApiCost,
} from "../StockMarket/StockMarketCosts";
2021-10-14 09:22:02 +02:00
import { Stock } from "../StockMarket/Stock";
2021-10-30 22:03:34 +02:00
import { TIX } from "../ScriptEditor/NetscriptDefinitions";
2022-05-08 01:08:07 +02:00
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
2021-10-14 09:22:02 +02:00
2022-05-08 01:08:07 +02:00
export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript): InternalAPI<TIX> {
2021-10-14 09:22:02 +02:00
/**
* Checks if the player has TIX API access. Throws an error if the player does not
*/
2022-05-08 01:08:07 +02:00
const checkTixApiAccess = function (ctx: NetscriptContext): void {
2021-10-14 09:22:02 +02:00
if (!player.hasWseAccount) {
2022-05-08 01:08:07 +02:00
throw ctx.makeRuntimeErrorMsg(`You don't have WSE Access! Cannot use ${ctx.function}()`);
2021-10-14 09:22:02 +02:00
}
if (!player.hasTixApiAccess) {
2022-05-08 01:08:07 +02:00
throw ctx.makeRuntimeErrorMsg(`You don't have TIX API Access! Cannot use ${ctx.function}()`);
2021-10-14 09:22:02 +02:00
}
};
2022-05-08 01:08:07 +02:00
const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
2021-10-14 09:22:02 +02:00
const stock = SymbolToStockMap[symbol];
if (stock == null) {
2022-05-08 01:08:07 +02:00
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: '${symbol}'`);
2021-10-14 09:22:02 +02:00
}
return stock;
};
2021-10-14 09:22:02 +02:00
return {
2022-05-08 01:08:07 +02:00
getSymbols: (ctx: NetscriptContext) => (): string[] => {
checkTixApiAccess(ctx);
2021-10-14 09:22:02 +02:00
return Object.values(StockSymbols);
},
2022-05-08 01:08:07 +02:00
getPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.price;
},
getAskPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getAskPrice();
},
getBidPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getBidPrice();
},
getPosition:
(ctx: NetscriptContext) =>
(_symbol: unknown): [number, number, number, number] => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: ${symbol}`);
}
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
},
getMaxShares:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.maxShares;
},
getPurchaseCost:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return Infinity;
}
2021-10-14 09:22:02 +02:00
2022-05-08 01:08:07 +02:00
const res = getBuyTransactionCost(stock, shares, pos);
if (res == null) {
return Infinity;
}
2021-10-14 09:22:02 +02:00
2022-05-08 01:08:07 +02:00
return res;
},
getSaleGain:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return 0;
2021-10-14 09:22:02 +02:00
}
2022-05-08 01:08:07 +02:00
const res = getSellTransactionGain(stock, shares, pos);
if (res == null) {
return 0;
2021-10-14 09:22:02 +02:00
}
2022-05-08 01:08:07 +02:00
return res;
},
buy:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = buyStock(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
sell:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = sellStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
short:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = shortStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
sellShort:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
placeOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
2021-10-14 09:22:02 +02:00
}
2022-05-08 01:08:07 +02:00
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
2021-10-14 09:22:02 +02:00
2022-05-08 01:08:07 +02:00
return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
},
cancelOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
if (isNaN(shares) || isNaN(price)) {
throw ctx.makeRuntimeErrorMsg(`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`);
}
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
2021-10-14 09:22:02 +02:00
}
2022-05-08 01:08:07 +02:00
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
const params = {
stock: stock,
shares: shares,
price: price,
type: orderType,
pos: orderPos,
};
return cancelOrder(params, workerScript);
},
getOrders: (ctx: NetscriptContext) => (): any => {
checkTixApiAccess(ctx);
2021-10-14 09:22:02 +02:00
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
2022-05-08 01:08:07 +02:00
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or have Source-File 8 Level 3.");
2021-10-14 09:22:02 +02:00
}
}
const orders: any = {};
const stockMarketOrders = StockMarket["Orders"];
2022-01-16 01:45:03 +01:00
for (const symbol of Object.keys(stockMarketOrders)) {
2021-10-14 09:22:02 +02:00
const orderBook = stockMarketOrders[symbol];
if (orderBook.constructor === Array && orderBook.length > 0) {
orders[symbol] = [];
for (let i = 0; i < orderBook.length; ++i) {
orders[symbol].push({
shares: orderBook[i].shares,
price: orderBook[i].price,
type: orderBook[i].type,
position: orderBook[i].pos,
});
}
}
}
return orders;
},
2022-05-08 01:08:07 +02:00
getVolatility:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(ctx, symbol);
return stock.mv / 100; // Convert from percentage to decimal
},
getForecast:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(ctx, symbol);
2021-10-14 09:22:02 +02:00
2022-05-08 01:08:07 +02:00
let forecast = 50;
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal
},
purchase4SMarketData: (ctx: NetscriptContext) => (): boolean => {
2021-10-14 09:22:02 +02:00
if (player.has4SData) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Already purchased 4S Market Data.");
2021-10-14 09:22:02 +02:00
return true;
}
2021-11-12 03:35:26 +01:00
if (player.money < getStockMarket4SDataCost()) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Not enough money to purchase 4S Market Data.");
2021-10-14 09:22:02 +02:00
return false;
}
player.has4SData = true;
2021-10-27 20:18:33 +02:00
player.loseMoney(getStockMarket4SDataCost(), "stock");
2022-05-08 01:08:07 +02:00
ctx.log(() => "Purchased 4S Market Data");
2021-10-14 09:22:02 +02:00
return true;
},
2022-05-08 01:08:07 +02:00
purchase4SMarketDataTixApi: (ctx: NetscriptContext) => (): boolean => {
checkTixApiAccess(ctx);
2021-10-14 09:22:02 +02:00
if (player.has4SDataTixApi) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Already purchased 4S Market Data TIX API");
2021-10-14 09:22:02 +02:00
return true;
}
2021-11-12 03:35:26 +01:00
if (player.money < getStockMarket4STixApiCost()) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Not enough money to purchase 4S Market Data TIX API");
2021-10-14 09:22:02 +02:00
return false;
}
player.has4SDataTixApi = true;
2021-10-27 20:18:33 +02:00
player.loseMoney(getStockMarket4STixApiCost(), "stock");
2022-05-08 01:08:07 +02:00
ctx.log(() => "Purchased 4S Market Data TIX API");
2021-10-14 09:22:02 +02:00
return true;
},
2022-05-08 01:08:07 +02:00
purchaseWseAccount: (ctx: NetscriptContext) => (): boolean => {
2022-03-19 04:58:18 +01:00
if (player.hasWseAccount) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Already purchased WSE Account");
2022-03-19 04:58:18 +01:00
return true;
}
if (player.money < getStockMarketWseCost()) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Not enough money to purchase WSE Account Access");
2022-03-19 04:58:18 +01:00
return false;
}
player.hasWseAccount = true;
initStockMarketFn();
2022-03-19 04:58:18 +01:00
player.loseMoney(getStockMarketWseCost(), "stock");
2022-05-08 01:08:07 +02:00
ctx.log(() => "Purchased WSE Account Access");
2022-03-19 04:58:18 +01:00
return true;
},
2022-05-08 01:08:07 +02:00
purchaseTixApi: (ctx: NetscriptContext) => (): boolean => {
2022-03-19 04:58:18 +01:00
if (player.hasTixApiAccess) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Already purchased TIX API");
2022-03-19 04:58:18 +01:00
return true;
}
if (player.money < getStockMarketTixApiCost()) {
2022-05-08 01:08:07 +02:00
ctx.log(() => "Not enough money to purchase TIX API Access");
2022-03-19 04:58:18 +01:00
return false;
}
player.hasTixApiAccess = true;
player.loseMoney(getStockMarketTixApiCost(), "stock");
2022-05-08 01:08:07 +02:00
ctx.log(() => "Purchased TIX API");
2022-03-19 04:58:18 +01:00
return true;
},
2021-10-14 09:22:02 +02:00
};
}