Last active
April 2, 2024 03:21
-
-
Save NotoriousPyro/17c23e92079322f2bbc55b82cba5f28e to your computer and use it in GitHub Desktop.
Tests for a class implementing Jupiter's QuoteResponse for profitable quotes. Here is the test, you must implement the class... good luck!
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
// By Pyro @ www.sexonsol.com | |
// Price class, basic types and interface for PriceManager provided for you: | |
import { QuoteResponse } from '@jup-ag/api'; | |
import { BigNumber } from 'bignumber.js'; | |
enum WellKnownTokenMint { | |
USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", | |
USDCet = "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", | |
USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", | |
USDTet = "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", | |
UXD = "7kbnvuGBxxj8AG9qp8Scn56muWGaRaFqxg1FsRp3PaFT", | |
Solana = "So11111111111111111111111111111111111111112", | |
Sex = "DARpE2GaVrazeh6mopWXbTT1hV3EbNNvHrJMMqJXUm6i", | |
Bozo = "BoZoQQRAmYkr5iJhqo7DChAs7DPDwEZ5cv1vkYC9yzJG", | |
Tholana = "EKCW975DWdt1roK1NVQDf4QGfaGTcQPU5tFD1DMcMe9Q", | |
Fart = "Ek1zzALu4d4iPu7bwUL1TU4aCwNokecihX6y6aBEo6b4", | |
mSOL = "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", | |
stSOL = "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", | |
bSOL = "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1", | |
JitoSOL = "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", | |
JSOL = "7Q2afV64in6N6SeZsAAB81TJzwDoD6zpqmHkzi9Dcavn", | |
Infinity = "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", | |
WBTC = "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", | |
JLP = "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4" | |
} | |
export default class PriceHistory { | |
private prices: BigNumber[]; | |
private start: number; | |
private end: number; | |
private isFull: boolean; | |
constructor(private maxItems: number = 200) { | |
this.prices = new Array(maxItems); | |
this.start = 0; | |
this.end = 0; | |
this.isFull = false; | |
} | |
get length(): number { | |
return this.isFull ? this.maxItems : this.end; | |
} | |
get average(): BigNumber { | |
let sum = new BigNumber(0); | |
const count = this.length; | |
for (let i = 0; i < count; i++) { | |
sum = sum.plus(this.prices[i]); | |
} | |
return sum.div(count); | |
} | |
add(item: BigNumber) { | |
this.prices[this.end] = item; | |
this.end = (this.end + 1) % this.maxItems; | |
if (this.end === this.start) { | |
this.start = (this.start + 1) % this.maxItems; | |
this.isFull = true; | |
} | |
} | |
} | |
class Price { | |
constructor(args: Partial<Price>) { | |
Object.assign(this, args); | |
} | |
price: BigNumber; | |
priceHistory: PriceHistory; | |
quote: QuoteResponse; | |
get gain() { | |
return this.price.minus(this.priceHistory.average).dividedBy(this.price.plus(this.priceHistory.average).dividedBy(2)).multipliedBy(100); | |
} | |
} | |
type PriceData = Map<WellKnownTokenMint, Price> | |
interface PriceManager { | |
priceData: Map<WellKnownTokenMint, PriceData>; | |
sampleThreshold: BigNumber; | |
constructor(sampleThreshold: BigNumber): void; | |
addPrice(quote: QuoteResponse): Promise<void>; | |
isQuoteOutputGreaterThanMinimumRequired(quote: QuoteResponse, outputMintMinimumAmount: BigNumber): Promise<boolean>; | |
isProfitableVsPriceAverage (quote: QuoteResponse): Promise<boolean>; | |
} | |
// You can use the map inside addPrice, first function is given to you like so: | |
class PriceManager implements PriceManager { | |
priceData: Map<WellKnownTokenMint, PriceData>; | |
sampleThreshold: BigNumber; | |
constructor( | |
sampleThreshold: BigNumber = new BigNumber(10), | |
) { | |
this.priceData = new Map(); | |
this.sampleThreshold = sampleThreshold; | |
} | |
// Just call this every time you ask for a quote and stuff it in there | |
public addPrice = async ( | |
quote: QuoteResponse, | |
) => { | |
const price = new BigNumber(quote.otherAmountThreshold).dividedBy(new BigNumber(quote.inAmount)); // buy div sell | |
if (!this.priceData.has(quote.inputMint as WellKnownTokenMint)) { | |
this.priceData.set(quote.inputMint as WellKnownTokenMint, new Map()); | |
} | |
if (!this.priceData.get(quote.inputMint as WellKnownTokenMint).has(quote.outputMint as WellKnownTokenMint)) { | |
const priceHistory = new PriceHistory(); | |
priceHistory.add(price); | |
this.priceData.get(quote.inputMint as WellKnownTokenMint).set( | |
quote.outputMint as WellKnownTokenMint, | |
new Price({ | |
price, | |
priceHistory, | |
quote, | |
}) | |
); | |
return Promise.resolve(); | |
} | |
const priceData = this.priceData | |
.get(quote.inputMint as WellKnownTokenMint) | |
.get(quote.outputMint as WellKnownTokenMint) | |
; | |
priceData.priceHistory.add(price); | |
priceData.price = price; | |
priceData.quote = quote; | |
this.priceData | |
.get(quote.inputMint as WellKnownTokenMint) | |
.set(quote.outputMint as WellKnownTokenMint, priceData) | |
; | |
return Promise.resolve(); | |
} | |
} | |
// End impl code | |
// Begin test code | |
import { QuoteResponse } from "@jup-ag/api"; | |
import { PriceManager } from "../src/lib/managers/price"; | |
import BigNumber from "bignumber.js"; | |
describe("PriceManager", () => { | |
it("should sell prices higher than average and not buy it", async () => { | |
const priceManager = new PriceManager(new BigNumber(2)); | |
const quote: QuoteResponse = { | |
inputMint: "SEX", | |
outputMint: "WSOL", | |
inAmount: "30000", | |
otherAmountThreshold: "6000000", | |
outAmount: "6010000", | |
swapMode: "ExactIn", | |
slippageBps: 0, | |
priceImpactPct: "0", | |
routePlan: [], | |
}; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
// This number is basically the minimum you would accept for an order, it's best to compare this to otherAmountThreshold in your priceManager impl | |
// As this is the minimum you will receive before your slippage is hit. Checking the amount you expect to receive is higher than this is a good | |
// way to ensure profitability. For these tests, they're all set to otherAmountThreshold. | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(false); // First quote, should not be profitable | |
// Increase out amount (profit) - result: true | |
quote.otherAmountThreshold = "6100000"; | |
quote.outAmount = "6200000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // Should be profitable vs average | |
// Increase out amount (profit) - result: true | |
quote.otherAmountThreshold = "6200000"; | |
quote.outAmount = "6300000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // Increased again, should be profitable again | |
// Decrease out amount (loss) - result: false | |
quote.otherAmountThreshold = "5950000"; | |
quote.outAmount = "6000000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(false); // The output amount has gone below average, should not be profitable | |
// Increase out amount (profit) - result: true | |
quote.otherAmountThreshold = "6200000"; | |
quote.outAmount = "6300000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // Increased again, should be profitable again | |
}); | |
it("should buy prices lower than average and not sell it", async () => { | |
const priceManager = new PriceManager(new BigNumber(2)); | |
const quote: QuoteResponse = { | |
inputMint: "WSOL", | |
outputMint: "SEX", | |
inAmount: "6000000", | |
otherAmountThreshold: "30100", | |
outAmount: "30200", | |
swapMode: "ExactIn", | |
slippageBps: 0, | |
priceImpactPct: "0", | |
routePlan: [], | |
}; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(false); // First order, should not be profitable | |
// Decrease spend (profit) - result: true | |
quote.inAmount = "5950000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // Should be profitable | |
// Increase spend (loss) - result: false | |
quote.inAmount = "6100000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(false); // The input amount has gone above average, should not be profitable | |
// Increase out threshold (profit) - result: true | |
quote.otherAmountThreshold = "34000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // The output amount has gone above average, should be profitable | |
}); | |
it("should buy the cheap side and sell the expensive side", async () => { | |
const priceManager = new PriceManager(new BigNumber(2)); | |
const quote: QuoteResponse = { | |
inputMint: "SEX", | |
outputMint: "WSOL", | |
inAmount: "30000", | |
otherAmountThreshold: "6000000", | |
outAmount: "6100000", | |
swapMode: "ExactIn", | |
slippageBps: 0, | |
priceImpactPct: "0", | |
routePlan: [], | |
}; | |
const quote2: QuoteResponse = { | |
inputMint: "WSOL", | |
outputMint: "SEX", | |
inAmount: "6000000", | |
otherAmountThreshold: "30000", | |
outAmount: "30100", | |
swapMode: "ExactIn", | |
slippageBps: 0, | |
priceImpactPct: "0", | |
routePlan: [], | |
}; | |
// Quote 1: SELL | |
await priceManager.addPrice(quote); | |
// Increase out amount (profit) - result: true | |
quote.otherAmountThreshold = "6100000"; | |
quote.outAmount = "6200000"; | |
await priceManager.addPrice(quote); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote, | |
new BigNumber(quote.otherAmountThreshold), | |
)).toBe(true); // Should be profitable | |
// Quote 2: BUY | |
await priceManager.addPrice(quote2); | |
quote2.otherAmountThreshold = "30100"; | |
quote2.outAmount = "30200"; | |
// Decrease spend (profit) - result: true | |
await priceManager.addPrice(quote2); | |
expect(await priceManager.isQuoteOutputGreaterThanMinimumRequired( | |
quote2, | |
new BigNumber(quote2.otherAmountThreshold), | |
)).toBe(true); // Should be profitable | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment