cancel batching
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
22d82f69b4
commit
5b26fad649
|
@ -8,6 +8,9 @@ import {
|
|||
getMultipleAccounts,
|
||||
getTokenBySymbol,
|
||||
GroupConfig,
|
||||
makeCancelPerpOrderInstruction,
|
||||
makeCancelSpotOrderInstruction,
|
||||
makeSettleFundsInstruction,
|
||||
MangoAccount,
|
||||
MangoClient,
|
||||
MangoGroup,
|
||||
|
@ -15,6 +18,7 @@ import {
|
|||
PerpMarket,
|
||||
PerpMarketLayout,
|
||||
PerpOrder,
|
||||
QUOTE_INDEX,
|
||||
} from "@blockworks-foundation/mango-client";
|
||||
import { Market, Orderbook } from "@project-serum/serum";
|
||||
import { Order } from "@project-serum/serum/lib/market";
|
||||
|
@ -32,6 +36,7 @@ import os from "os";
|
|||
import { OrderInfo } from "types";
|
||||
import { logger, zipDict } from "./utils";
|
||||
import BN from "bn.js";
|
||||
import { Transaction } from "@solana/web3.js";
|
||||
|
||||
class MangoSimpleClient {
|
||||
constructor(
|
||||
|
@ -422,15 +427,37 @@ class MangoSimpleClient {
|
|||
public async cancelAllOrders(): Promise<void> {
|
||||
const allMarkets = await this.fetchAllMarkets();
|
||||
const orders = (await this.fetchAllBidsAndAsks(true)).flat();
|
||||
// todo combine multiple cancels into one transaction
|
||||
await Promise.all(
|
||||
|
||||
const transactions = await Promise.all(
|
||||
orders.map((orderInfo) =>
|
||||
this.cancelOrder(
|
||||
this.buildCancelOrderTransaction(
|
||||
orderInfo,
|
||||
allMarkets[orderInfo.market.account.publicKey.toBase58()]
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
let i, j;
|
||||
// assuming we can fit 10 cancel order transactions in a solana transaction
|
||||
// we could switch to computing actual transactionSize every time we add an
|
||||
// instruction and use a dynamic chunk size
|
||||
const chunk = 10;
|
||||
const transactionsToSend: Transaction[] = [];
|
||||
|
||||
for (i = 0, j = transactions.length; i < j; i += chunk) {
|
||||
const transactionsChunk = transactions.slice(i, i + chunk);
|
||||
const transactionToSend = new Transaction();
|
||||
for (const transaction of transactionsChunk) {
|
||||
for (const instruction of transaction.instructions) {
|
||||
transactionToSend.add(instruction);
|
||||
}
|
||||
}
|
||||
transactionsToSend.push(transactionToSend);
|
||||
}
|
||||
|
||||
for (const transaction of transactionsToSend) {
|
||||
await this.client.sendTransaction(transaction, this.owner, []);
|
||||
}
|
||||
}
|
||||
|
||||
public async cancelOrder(orderInfo: OrderInfo, market?: Market | PerpMarket) {
|
||||
|
@ -479,6 +506,55 @@ class MangoSimpleClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async buildCancelOrderTransaction(
|
||||
orderInfo: OrderInfo,
|
||||
market?: Market | PerpMarket
|
||||
): Promise<Transaction> {
|
||||
if (orderInfo.market.config.kind === "perp") {
|
||||
const perpMarketConfig = getMarketByBaseSymbolAndKind(
|
||||
this.mangoGroupConfig,
|
||||
orderInfo.market.config.baseSymbol,
|
||||
"perp"
|
||||
);
|
||||
if (market === undefined) {
|
||||
market = await this.mangoGroup.loadPerpMarket(
|
||||
this.connection,
|
||||
perpMarketConfig.marketIndex,
|
||||
perpMarketConfig.baseDecimals,
|
||||
perpMarketConfig.quoteDecimals
|
||||
);
|
||||
}
|
||||
return this.buildCancelPerpOrderInstruction(
|
||||
this.mangoGroup,
|
||||
this.mangoAccount,
|
||||
this.owner,
|
||||
market as PerpMarket,
|
||||
orderInfo.order as PerpOrder
|
||||
);
|
||||
} else {
|
||||
const spotMarketConfig = getMarketByBaseSymbolAndKind(
|
||||
this.mangoGroupConfig,
|
||||
orderInfo.market.config.baseSymbol,
|
||||
"spot"
|
||||
);
|
||||
if (market === undefined) {
|
||||
market = await Market.load(
|
||||
this.connection,
|
||||
spotMarketConfig.publicKey,
|
||||
undefined,
|
||||
this.mangoGroupConfig.serumProgramId
|
||||
);
|
||||
}
|
||||
return await this.buildCancelSpotOrderTransaction(
|
||||
this.mangoGroup,
|
||||
this.mangoAccount,
|
||||
this.owner,
|
||||
market as Market,
|
||||
orderInfo.order as Order
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async getOrderByOrderId(orderId: string): Promise<OrderInfo[]> {
|
||||
const orders = (await this.fetchAllBidsAndAsks(true)).flat();
|
||||
const orderInfos = orders.filter(
|
||||
|
@ -580,6 +656,102 @@ class MangoSimpleClient {
|
|||
}));
|
||||
}
|
||||
|
||||
private buildCancelPerpOrderInstruction(
|
||||
mangoGroup: MangoGroup,
|
||||
mangoAccount: MangoAccount,
|
||||
owner: Account,
|
||||
perpMarket: PerpMarket,
|
||||
order: PerpOrder,
|
||||
invalidIdOk = false // Don't throw error if order is invalid
|
||||
): Transaction {
|
||||
const instruction = makeCancelPerpOrderInstruction(
|
||||
this.mangoGroupConfig.mangoProgramId,
|
||||
mangoGroup.publicKey,
|
||||
mangoAccount.publicKey,
|
||||
owner.publicKey,
|
||||
perpMarket.publicKey,
|
||||
perpMarket.bids,
|
||||
perpMarket.asks,
|
||||
order,
|
||||
invalidIdOk
|
||||
);
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.add(instruction);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private async buildCancelSpotOrderTransaction(
|
||||
mangoGroup: MangoGroup,
|
||||
mangoAccount: MangoAccount,
|
||||
owner: Account,
|
||||
spotMarket: Market,
|
||||
order: Order
|
||||
): Promise<Transaction> {
|
||||
const transaction = new Transaction();
|
||||
const instruction = makeCancelSpotOrderInstruction(
|
||||
this.mangoGroupConfig.mangoProgramId,
|
||||
mangoGroup.publicKey,
|
||||
owner.publicKey,
|
||||
mangoAccount.publicKey,
|
||||
spotMarket.programId,
|
||||
spotMarket.publicKey,
|
||||
spotMarket["_decoded"].bids,
|
||||
spotMarket["_decoded"].asks,
|
||||
order.openOrdersAddress,
|
||||
mangoGroup.signerKey,
|
||||
spotMarket["_decoded"].eventQueue,
|
||||
order
|
||||
);
|
||||
transaction.add(instruction);
|
||||
|
||||
const dexSigner = await PublicKey.createProgramAddress(
|
||||
[
|
||||
spotMarket.publicKey.toBuffer(),
|
||||
spotMarket["_decoded"].vaultSignerNonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
spotMarket.programId
|
||||
);
|
||||
|
||||
const marketIndex = mangoGroup.getSpotMarketIndex(spotMarket.publicKey);
|
||||
if (!mangoGroup.rootBankAccounts.length) {
|
||||
await mangoGroup.loadRootBanks(this.connection);
|
||||
}
|
||||
const baseRootBank = mangoGroup.rootBankAccounts[marketIndex];
|
||||
const quoteRootBank = mangoGroup.rootBankAccounts[QUOTE_INDEX];
|
||||
const baseNodeBank = baseRootBank?.nodeBankAccounts[0];
|
||||
const quoteNodeBank = quoteRootBank?.nodeBankAccounts[0];
|
||||
|
||||
if (!baseNodeBank || !quoteNodeBank) {
|
||||
throw new Error("Invalid or missing node banks");
|
||||
}
|
||||
|
||||
// todo what is a makeSettleFundsInstruction?
|
||||
const settleFundsInstruction = makeSettleFundsInstruction(
|
||||
this.mangoGroupConfig.mangoProgramId,
|
||||
mangoGroup.publicKey,
|
||||
mangoGroup.mangoCache,
|
||||
owner.publicKey,
|
||||
mangoAccount.publicKey,
|
||||
spotMarket.programId,
|
||||
spotMarket.publicKey,
|
||||
mangoAccount.spotOpenOrders[marketIndex],
|
||||
mangoGroup.signerKey,
|
||||
spotMarket["_decoded"].baseVault,
|
||||
spotMarket["_decoded"].quoteVault,
|
||||
mangoGroup.tokens[marketIndex].rootBank,
|
||||
baseNodeBank.publicKey,
|
||||
mangoGroup.tokens[QUOTE_INDEX].rootBank,
|
||||
quoteNodeBank.publicKey,
|
||||
baseNodeBank.vault,
|
||||
quoteNodeBank.vault,
|
||||
dexSigner
|
||||
);
|
||||
transaction.add(settleFundsInstruction);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private roundRobinClusterUrl() {
|
||||
if (process.env.CLUSTER_URL) {
|
||||
return;
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
import { Config, GroupConfig } from "@blockworks-foundation/mango-client";
|
||||
import { I80F48 } from "@blockworks-foundation/mango-client/lib/src/fixednum";
|
||||
import { CustomValidator } from "express-validator";
|
||||
/// logging related
|
||||
import pino from "pino";
|
||||
import { Transaction, Connection, Account } from "@solana/web3.js";
|
||||
|
||||
/// solana related
|
||||
|
||||
export async function transactionSize(
|
||||
connection: Connection,
|
||||
singleTransaction: Transaction,
|
||||
owner: Account
|
||||
) {
|
||||
singleTransaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash()
|
||||
).blockhash;
|
||||
singleTransaction.setSigners(owner.publicKey);
|
||||
singleTransaction.sign(this.owner);
|
||||
return singleTransaction.serialize().length;
|
||||
}
|
||||
|
||||
/// mango related
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from decimal import Decimal
|
||||
from threading import Timer
|
||||
from typing import List
|
||||
|
||||
import httpx
|
||||
|
@ -39,41 +41,41 @@ class MangoServiceV3Client:
|
|||
else:
|
||||
self.BASE_URL = "http://localhost:3000/api"
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_open_positions(self) -> List[Position]:
|
||||
response = httpx.get(f"{self.BASE_URL}/positions", timeout=10.0)
|
||||
return parse_obj_as(List[Position], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_balances(self) -> List[Balance]:
|
||||
response = httpx.get(f"{self.BASE_URL}/wallet/balances", timeout=10.0)
|
||||
return parse_obj_as(List[Balance], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_markets(self) -> List[Market]:
|
||||
response = httpx.get(f"{self.BASE_URL}/markets", timeout=10.0)
|
||||
return parse_obj_as(List[Market], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_market_by_market_name(self, market_name: str) -> List[Market]:
|
||||
response = httpx.get(f"{self.BASE_URL}/markets/{market_name}", timeout=10.0)
|
||||
return parse_obj_as(List[Market], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_orderboook(self, market_name: str, depth: int = 30) -> Orderbook:
|
||||
response = httpx.get(
|
||||
f"{self.BASE_URL}/markets/{market_name}/orderbook?depth={depth}"
|
||||
)
|
||||
return parse_obj_as(Orderbook, json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_trades(self, market_name: str) -> List[Trade]:
|
||||
response = httpx.get(
|
||||
f"{self.BASE_URL}/markets/{market_name}/trades", timeout=10.0
|
||||
)
|
||||
return parse_obj_as(List[Trade], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_candles(
|
||||
self, market_name: str, resolution: int, start_time: int, end_time: int
|
||||
) -> List[Candle]:
|
||||
|
@ -82,19 +84,19 @@ class MangoServiceV3Client:
|
|||
)
|
||||
return parse_obj_as(List[Candle], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_orders(self,) -> List[Order]:
|
||||
response = httpx.get(f"{self.BASE_URL}/orders", timeout=10.0)
|
||||
return parse_obj_as(List[Order], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def get_orders_by_market_name(self, market_name: str) -> List[Order]:
|
||||
response = httpx.get(
|
||||
f"{self.BASE_URL}/orders?market={market_name}", timeout=10.0
|
||||
)
|
||||
return parse_obj_as(List[Order], json.loads(response.text)["result"])
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def place_order(self, order: PlaceOrder) -> None:
|
||||
response = httpx.post(
|
||||
f"{self.BASE_URL}/orders", json=order.dict(by_alias=True), timeout=10.0
|
||||
|
@ -104,17 +106,17 @@ class MangoServiceV3Client:
|
|||
# List[BadRequestError], json.loads(response.text)["errors"]
|
||||
# )
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def cancel_order_by_client_id(self, client_id):
|
||||
response = httpx.delete(
|
||||
f"{self.BASE_URL}/orders/by_client_id/{client_id}", timeout=10.0
|
||||
)
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def cancel_order_by_order_id(self, order_id):
|
||||
response = httpx.delete(f"{self.BASE_URL}/orders/{order_id}", timeout=10.0)
|
||||
|
||||
@delayed(os.environ["DELAY"])
|
||||
@delayed(2)
|
||||
def cancel_all_orders(self):
|
||||
response = httpx.delete(f"{self.BASE_URL}/orders", timeout=10.0)
|
||||
|
||||
|
|
Loading…
Reference in New Issue