-
Notifications
You must be signed in to change notification settings - Fork 510
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add batch order from Manifest (#114)
# Pull Request Description ## Related Issue ## Changes Made This PR adds the following changes: <!-- List the key changes made in this PR --> - Adds batch order functionality from Manifest for easier market making ## Implementation Details <!-- Provide technical details about the implementation --> - Allow pattern based prompts for easier quote production ## Transaction executed by agent <!-- If applicable, provide example usage, transactions, or screenshots --> Example transaction: https://solscan.io/tx/64P3YHsC4Zh9GKmT6EubHfy8pEc2vYNPYF8NApb8P4LD57to1WisQfQZ7vFhwup3UgJqtGCmiRy7TW6VCrkmNTHP ## Prompt Used <!-- If relevant, include the prompt or configuration used -->  ## Additional Notes <!-- Any additional information that reviewers should know --> ## Checklist - [x] I have tested these changes locally - [x] I have updated the documentation - [x] I have added a transaction link - [x] I have added the prompt used to test it
- Loading branch information
Showing
4 changed files
with
278 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { | ||
PublicKey, | ||
Transaction, | ||
sendAndConfirmTransaction, | ||
TransactionInstruction, | ||
} from "@solana/web3.js"; | ||
import { SolanaAgentKit } from "../index"; | ||
import { | ||
ManifestClient, | ||
WrapperPlaceOrderParamsExternal, | ||
} from "@cks-systems/manifest-sdk"; | ||
import { OrderType } from "@cks-systems/manifest-sdk/client/ts/src/wrapper/types/OrderType"; | ||
|
||
export interface OrderParams { | ||
quantity: number; | ||
side: string; | ||
price: number; | ||
} | ||
|
||
interface BatchOrderPattern { | ||
side: string; | ||
totalQuantity?: number; | ||
priceRange?: { | ||
min?: number; | ||
max?: number; | ||
}; | ||
spacing?: { | ||
type: "percentage" | "fixed"; | ||
value: number; | ||
}; | ||
numberOfOrders?: number; | ||
individualQuantity?: number; | ||
} | ||
|
||
/** | ||
* Generates an array of orders based on the specified pattern | ||
*/ | ||
export function generateOrdersfromPattern( | ||
pattern: BatchOrderPattern, | ||
): OrderParams[] { | ||
const orders: OrderParams[] = []; | ||
|
||
// Random number of orders if not specified, max of 8 | ||
const numOrders = pattern.numberOfOrders || Math.ceil(Math.random() * 8); | ||
|
||
// Calculate price points | ||
const prices: number[] = []; | ||
if (pattern.priceRange) { | ||
const { min, max } = pattern.priceRange; | ||
if (min && max) { | ||
// Generate evenly spaced prices | ||
for (let i = 0; i < numOrders; i++) { | ||
if (pattern.spacing?.type === "percentage") { | ||
const factor = 1 + pattern.spacing.value / 100; | ||
prices.push(min * Math.pow(factor, i)); | ||
} else { | ||
const step = (max - min) / (numOrders - 1); | ||
prices.push(min + step * i); | ||
} | ||
} | ||
} else if (min) { | ||
// Generate prices starting from min with specified spacing | ||
for (let i = 0; i < numOrders; i++) { | ||
if (pattern.spacing?.type === "percentage") { | ||
const factor = 1 + pattern.spacing.value / 100; | ||
prices.push(min * Math.pow(factor, i)); | ||
} else { | ||
prices.push(min + (pattern.spacing?.value || 0.01) * i); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Calculate quantities | ||
let quantities: number[] = []; | ||
if (pattern.totalQuantity) { | ||
const individualQty = pattern.totalQuantity / numOrders; | ||
quantities = Array(numOrders).fill(individualQty); | ||
} else if (pattern.individualQuantity) { | ||
quantities = Array(numOrders).fill(pattern.individualQuantity); | ||
} | ||
|
||
// Generate orders | ||
for (let i = 0; i < numOrders; i++) { | ||
orders.push({ | ||
side: pattern.side, | ||
price: prices[i], | ||
quantity: quantities[i], | ||
}); | ||
} | ||
|
||
return orders; | ||
} | ||
|
||
/** | ||
* Validates that sell orders are not priced below buy orders | ||
* @param orders Array of order parameters to validate | ||
* @throws Error if orders are crossed | ||
*/ | ||
function validateNoCrossedOrders(orders: OrderParams[]): void { | ||
// Find lowest sell and highest buy prices | ||
let lowestSell = Number.MAX_SAFE_INTEGER; | ||
let highestBuy = 0; | ||
|
||
orders.forEach((order) => { | ||
if (order.side === "Sell" && order.price < lowestSell) { | ||
lowestSell = order.price; | ||
} | ||
if (order.side === "Buy" && order.price > highestBuy) { | ||
highestBuy = order.price; | ||
} | ||
}); | ||
|
||
// Check if orders cross | ||
if (lowestSell <= highestBuy) { | ||
throw new Error( | ||
`Invalid order prices: Sell order at ${lowestSell} is lower than or equal to Buy order at ${highestBuy}. Orders cannot cross.`, | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Place batch orders using Manifest | ||
* @param agent SolanaAgentKit instance | ||
* @param marketId Public key for the manifest market | ||
* @param quantity Amount to trade in tokens | ||
* @param side Buy or Sell | ||
* @param price Price in tokens ie. SOL/USDC | ||
* @returns Transaction signature | ||
*/ | ||
export async function batchOrder( | ||
agent: SolanaAgentKit, | ||
marketId: PublicKey, | ||
orders: OrderParams[], | ||
): Promise<string> { | ||
try { | ||
validateNoCrossedOrders(orders); | ||
|
||
const mfxClient = await ManifestClient.getClientForMarket( | ||
agent.connection, | ||
marketId, | ||
agent.wallet, | ||
); | ||
|
||
const placeParams: WrapperPlaceOrderParamsExternal[] = orders.map( | ||
(order) => ({ | ||
numBaseTokens: order.quantity, | ||
tokenPrice: order.price, | ||
isBid: order.side === "Buy", | ||
lastValidSlot: 0, | ||
orderType: OrderType.Limit, | ||
clientOrderId: Number(Math.random() * 10000), | ||
}), | ||
); | ||
|
||
const batchOrderIx: TransactionInstruction = await mfxClient.batchUpdateIx( | ||
placeParams, | ||
[], | ||
true, | ||
); | ||
|
||
const signature = await sendAndConfirmTransaction( | ||
agent.connection, | ||
new Transaction().add(batchOrderIx), | ||
[agent.wallet], | ||
); | ||
|
||
return signature; | ||
} catch (error: any) { | ||
throw new Error(`Batch Order failed: ${error.message}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters