2019-05-05 06:03:40 +02:00
|
|
|
/**
|
|
|
|
* Helper functions for determine whether Limit and Stop orders should
|
|
|
|
* be executed (and executing them)
|
|
|
|
*/
|
2021-09-05 01:09:30 +02:00
|
|
|
import { buyStock, sellStock, shortStock, sellShort } from "./BuyingAndSelling";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { IOrderBook } from "./IOrderBook";
|
|
|
|
import { IStockMarket } from "./IStockMarket";
|
|
|
|
import { Order } from "./Order";
|
|
|
|
import { Stock } from "./Stock";
|
|
|
|
|
2023-06-12 06:34:20 +02:00
|
|
|
import { PositionType, OrderType } from "@enums";
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2023-02-11 19:18:50 +01:00
|
|
|
import { formatShares } from "../ui/formatNumber";
|
2021-03-31 06:45:21 +02:00
|
|
|
import { Money } from "../ui/React/Money";
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-25 20:42:57 +02:00
|
|
|
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
2021-12-09 02:21:44 +01:00
|
|
|
import { Settings } from "../Settings/Settings";
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-03-31 06:45:21 +02:00
|
|
|
import * as React from "react";
|
|
|
|
|
2019-06-10 06:23:48 +02:00
|
|
|
export interface IProcessOrderRefs {
|
2021-09-05 01:09:30 +02:00
|
|
|
stockMarket: IStockMarket;
|
2022-10-03 18:12:16 +02:00
|
|
|
symbolToStockMap: Record<string, Stock>;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search for all orders of a specific type and execute them if appropriate
|
|
|
|
* @param {Stock} stock - Stock for which orders should be processed
|
2023-06-12 06:34:20 +02:00
|
|
|
* @param {OrderType} orderType - Type of order to check (Limit/Stop buy/sell)
|
|
|
|
* @param {PositionType} posType - Long or short
|
2019-05-05 06:03:40 +02:00
|
|
|
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
|
|
|
|
*/
|
2021-09-05 01:09:30 +02:00
|
|
|
export function processOrders(
|
|
|
|
stock: Stock,
|
2023-06-12 06:34:20 +02:00
|
|
|
orderType: OrderType,
|
|
|
|
posType: PositionType,
|
2021-09-05 01:09:30 +02:00
|
|
|
refs: IProcessOrderRefs,
|
|
|
|
): void {
|
2023-05-05 09:55:59 +02:00
|
|
|
const orderBook = refs.stockMarket.Orders;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (orderBook == null) {
|
|
|
|
const orders: IOrderBook = {};
|
2022-01-16 01:45:03 +01:00
|
|
|
for (const name of Object.keys(refs.stockMarket)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
const stock = refs.stockMarket[name];
|
|
|
|
if (!(stock instanceof Stock)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
orders[stock.symbol] = [];
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
2023-05-05 09:55:59 +02:00
|
|
|
refs.stockMarket.Orders = orders;
|
2021-09-05 01:09:30 +02:00
|
|
|
return; // Newly created, so no orders to process
|
|
|
|
}
|
|
|
|
let stockOrders = orderBook[stock.symbol];
|
|
|
|
if (stockOrders == null || !(stockOrders.constructor === Array)) {
|
2021-09-09 05:47:34 +02:00
|
|
|
console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`);
|
2021-09-05 01:09:30 +02:00
|
|
|
stockOrders = [];
|
|
|
|
return;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
for (const order of stockOrders) {
|
|
|
|
if (order.type === orderType && order.pos === posType) {
|
|
|
|
switch (order.type) {
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.LimitBuy:
|
|
|
|
if (order.pos === PositionType.Long && stock.price <= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short && stock.price >= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
|
|
|
}
|
|
|
|
break;
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.LimitSell:
|
|
|
|
if (order.pos === PositionType.Long && stock.price >= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short && stock.price <= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
|
|
|
}
|
|
|
|
break;
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.StopBuy:
|
|
|
|
if (order.pos === PositionType.Long && stock.price >= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short && stock.price <= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
|
|
|
}
|
|
|
|
break;
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.StopSell:
|
|
|
|
if (order.pos === PositionType.Long && stock.price <= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short && stock.price >= order.price) {
|
2021-09-05 01:09:30 +02:00
|
|
|
executeOrder(/*66*/ order, refs);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.warn(`Invalid order type: ${order.type}`);
|
|
|
|
return;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a Stop or Limit Order.
|
|
|
|
* @param {Order} order - Order being executed
|
2019-06-10 00:12:33 +02:00
|
|
|
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
|
2019-05-05 06:03:40 +02:00
|
|
|
*/
|
2021-05-01 09:17:31 +02:00
|
|
|
function executeOrder(order: Order, refs: IProcessOrderRefs): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
const stock = refs.symbolToStockMap[order.stockSymbol];
|
|
|
|
if (!(stock instanceof Stock)) {
|
|
|
|
console.error(`Could not find stock for this order: ${order.stockSymbol}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const stockMarket = refs.stockMarket;
|
2023-05-05 09:55:59 +02:00
|
|
|
const orderBook = stockMarket.Orders;
|
2021-09-05 01:09:30 +02:00
|
|
|
const stockOrders = orderBook[stock.symbol];
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// 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 = {
|
|
|
|
suppressDialog: true,
|
|
|
|
};
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
let res = true;
|
|
|
|
let isBuy = false;
|
|
|
|
switch (order.type) {
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.LimitBuy:
|
|
|
|
case OrderType.StopBuy:
|
2021-09-05 01:09:30 +02:00
|
|
|
isBuy = true;
|
2023-06-12 06:34:20 +02:00
|
|
|
if (order.pos === PositionType.Long) {
|
2021-09-05 01:09:30 +02:00
|
|
|
res = buyStock(stock, order.shares, null, opts) && res;
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short) {
|
2021-09-05 01:09:30 +02:00
|
|
|
res = shortStock(stock, order.shares, null, opts) && res;
|
|
|
|
}
|
|
|
|
break;
|
2023-06-12 06:34:20 +02:00
|
|
|
case OrderType.LimitSell:
|
|
|
|
case OrderType.StopSell:
|
|
|
|
if (order.pos === PositionType.Long) {
|
2021-09-05 01:09:30 +02:00
|
|
|
res = sellStock(stock, order.shares, null, opts) && res;
|
2023-06-12 06:34:20 +02:00
|
|
|
} else if (order.pos === PositionType.Short) {
|
2021-09-05 01:09:30 +02:00
|
|
|
res = sellShort(stock, order.shares, null, opts) && res;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.warn(`Invalid order type: ${order.type}`);
|
|
|
|
return;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Position type, for logging/message purposes
|
2023-06-12 06:34:20 +02:00
|
|
|
const pos = order.pos === PositionType.Long ? "Long" : "Short";
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
if (res) {
|
|
|
|
for (let i = 0; i < stockOrders.length; ++i) {
|
|
|
|
if (order == stockOrders[i]) {
|
|
|
|
stockOrders.splice(i, 1);
|
2021-12-09 02:21:44 +01:00
|
|
|
if (!Settings.SuppressTIXPopup) {
|
|
|
|
dialogBoxCreate(
|
|
|
|
<>
|
|
|
|
{order.type} for {stock.symbol} @ <Money money={order.price} /> ({pos}) was filled (
|
2023-02-11 19:18:50 +01:00
|
|
|
{formatShares(Math.round(order.shares))} shares)
|
2021-12-09 02:21:44 +01:00
|
|
|
</>,
|
|
|
|
);
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
console.error("Could not find the following Order in Order Book: ");
|
|
|
|
console.error(order);
|
2022-03-11 16:32:48 +01:00
|
|
|
} else if (isBuy) {
|
2022-04-07 01:30:08 +02:00
|
|
|
dialogBoxCreate(
|
|
|
|
<>
|
|
|
|
Failed to execute {order.type} for {stock.symbol} @ <Money money={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
|
|
|
|
</>,
|
|
|
|
);
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|