mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-09-22 22:00:57 +02:00
253 lines
11 KiB
TypeScript
253 lines
11 KiB
TypeScript
|
/**
|
||
|
* Helper functions for determine whether Limit and Stop orders should
|
||
|
* be executed (and executing them)
|
||
|
*/
|
||
|
import {
|
||
|
buyStock,
|
||
|
sellStock,
|
||
|
shortStock,
|
||
|
sellShort,
|
||
|
} from "./BuyingAndSelling";
|
||
|
import { IOrderBook } from "./IOrderBook";
|
||
|
import { IStockMarket } from "./IStockMarket";
|
||
|
import { Order } from "./Order";
|
||
|
import { Stock } from "./Stock";
|
||
|
|
||
|
import { OrderTypes } from "./data/OrderTypes";
|
||
|
import { PositionTypes } from "./data/PositionTypes";
|
||
|
|
||
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||
|
|
||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||
|
|
||
|
interface IProcessOrderRefs {
|
||
|
rerenderFn: () => void;
|
||
|
stockMarket: IStockMarket;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Search for all orders of a specific type and execute them if appropriate
|
||
|
* @param {Stock} stock - Stock for which orders should be processed
|
||
|
* @param {OrderTypes} orderType - Type of order to check (Limit/Stop buy/sell)
|
||
|
* @param {PositionTypes} posType - Long or short
|
||
|
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
|
||
|
*/
|
||
|
export function processOrders(stock: Stock, orderType: OrderTypes, posType: PositionTypes, refs: IProcessOrderRefs): void {
|
||
|
let orderBook = refs.stockMarket["Orders"];
|
||
|
if (orderBook == null) {
|
||
|
const orders: IOrderBook = {};
|
||
|
for (const name in refs.stockMarket) {
|
||
|
const stock = refs.stockMarket[name];
|
||
|
if (!(stock instanceof Stock)) { continue; }
|
||
|
orders[stock.symbol] = [];
|
||
|
}
|
||
|
refs.stockMarket["Orders"] = orders;
|
||
|
return; // Newly created, so no orders to process
|
||
|
}
|
||
|
let stockOrders = orderBook[stock.symbol];
|
||
|
if (stockOrders == null || !(stockOrders.constructor === Array)) {
|
||
|
console.error(`Invalid Order book for ${stock.symbol} in processOrders()`);
|
||
|
stockOrders = [];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (const order of stockOrders) {
|
||
|
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, refs.stockMarket, refs.rerenderFn);
|
||
|
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
}
|
||
|
break;
|
||
|
case OrderTypes.LimitSell:
|
||
|
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
}
|
||
|
break;
|
||
|
case OrderTypes.StopBuy:
|
||
|
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
}
|
||
|
break;
|
||
|
case OrderTypes.StopSell:
|
||
|
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||
|
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
console.warn(`Invalid order type: ${order.type}`);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute a Stop or Limit Order.
|
||
|
* @param {Order} order - Order being executed
|
||
|
* @param {IStockMarket} stockMarket - Reference to StockMarket object
|
||
|
*/
|
||
|
function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () => void) {
|
||
|
const stock = order.stock;
|
||
|
const orderBook = stockMarket["Orders"];
|
||
|
const stockOrders = orderBook[stock.symbol];
|
||
|
const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell);
|
||
|
let limitShares = 0;
|
||
|
|
||
|
// When orders are executed, the buying and selling functions shouldn't
|
||
|
// emit popup dialog boxes. This options object configures the functions for that
|
||
|
const opts = {
|
||
|
rerenderFn: rerenderFn,
|
||
|
suppressDialog: true
|
||
|
}
|
||
|
|
||
|
let res = true;
|
||
|
let isBuy = false;
|
||
|
console.log("Executing the following order:");
|
||
|
console.log(order);
|
||
|
switch (order.type) {
|
||
|
case OrderTypes.LimitBuy: {
|
||
|
isBuy = true;
|
||
|
|
||
|
// We only execute limit orders until the price fails to match the order condition
|
||
|
const isLong = (order.pos === PositionTypes.Long);
|
||
|
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
|
||
|
|
||
|
// First transaction to trigger movement
|
||
|
if (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts)) {
|
||
|
limitShares = firstShares;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
let remainingShares = order.shares - firstShares;
|
||
|
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
|
||
|
for (let i = 0; i < remainingIterations; ++i) {
|
||
|
if (isLong && stock.price > order.price) {
|
||
|
break;
|
||
|
} else if (!isLong && stock.price < order.price) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const shares = Math.min(remainingShares, stock.shareTxForMovement);
|
||
|
if (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts)) {
|
||
|
limitShares += shares;
|
||
|
remainingShares -= shares;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
case OrderTypes.StopBuy: {
|
||
|
isBuy = true;
|
||
|
if (order.pos === PositionTypes.Long) {
|
||
|
res = buyStock(stock, order.shares, null, opts) && res;
|
||
|
} else if (order.pos === PositionTypes.Short) {
|
||
|
res = shortStock(stock, order.shares, null, opts) && res;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case OrderTypes.LimitSell: {
|
||
|
// We only execute limit orders until the price fails to match the order condition
|
||
|
const isLong = (order.pos === PositionTypes.Long);
|
||
|
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
|
||
|
|
||
|
// First transaction to trigger movement
|
||
|
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {
|
||
|
limitShares = firstShares;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
let remainingShares = order.shares - firstShares;
|
||
|
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
|
||
|
for (let i = 0; i < remainingIterations; ++i) {
|
||
|
if (isLong && stock.price < order.price) {
|
||
|
break;
|
||
|
} else if (!isLong && stock.price > order.price) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const shares = Math.min(remainingShares, stock.shareTxForMovement);
|
||
|
if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) {
|
||
|
limitShares += shares;
|
||
|
remainingShares -= shares;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
case OrderTypes.StopSell: {
|
||
|
if (order.pos === PositionTypes.Long) {
|
||
|
res = sellStock(stock, order.shares, null, opts) && res;
|
||
|
} else if (order.pos === PositionTypes.Short) {
|
||
|
res = sellShort(stock, order.shares, null, opts) && res;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
console.warn(`Invalid order type: ${order.type}`);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Position type, for logging/message purposes
|
||
|
const pos = order.pos === PositionTypes.Long ? "Long" : "Short";
|
||
|
|
||
|
if (res) {
|
||
|
if (isLimit) {
|
||
|
|
||
|
} else {
|
||
|
|
||
|
for (let i = 0; i < stockOrders.length; ++i) {
|
||
|
if (order == stockOrders[i]) {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (let i = 0; i < stockOrders.length; ++i) {
|
||
|
if (order == stockOrders[i]) {
|
||
|
if (isLimit) {
|
||
|
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
|
||
|
stockOrders[i].shares -= limitShares;
|
||
|
if (stockOrders[i].shares <= 0) {
|
||
|
stockOrders.splice(i, 1);
|
||
|
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
|
||
|
`(${Math.round(limitShares)} shares`);
|
||
|
} else {
|
||
|
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` +
|
||
|
`(${Math.round(limitShares)} shares transacted, ${stockOrders[i].shares} shares remaining`);
|
||
|
}
|
||
|
} else {
|
||
|
// Stop orders will transact everything, so they can be removed completely
|
||
|
stockOrders.splice(i, 1);
|
||
|
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
|
||
|
`(${Math.round(order.shares)} shares transacted)`);
|
||
|
}
|
||
|
|
||
|
rerenderFn();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
console.error("Could not find the following Order in Order Book: ");
|
||
|
console.error(order);
|
||
|
} else {
|
||
|
if (isBuy) {
|
||
|
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
|
||
|
`This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`);
|
||
|
} else {
|
||
|
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
|
||
|
`This is most likely a bug, please report to game developer with details.`);
|
||
|
}
|
||
|
}
|
||
|
}
|