diff --git a/SUMMARY.md b/SUMMARY.md index d7b5a32..347cae6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -27,6 +27,7 @@ - [Fetching all the data of a certain pool from Ethereum](docs/fetching-all-the-data-of-a-certain-pool-from-ethereum.md) - [Getting a pool instance with the data fetched](docs/getting-a-pool-instance-with-the-data-fetched.md) + - [Loading and streaming events into a pool](docs/loading-and-streaming-events-into-a-pool.md) - (Advanced)For a better user experience as a state machine @@ -46,7 +47,7 @@ - [Uniswap-v3-Strategy-Backtest](https://github.com/Bella-DeFinTech/uniswap-v3-simulator/tree/main/examples/Uniswap-v3-Strategy-Backtest) -- Uniswap-v3-Risk-Analysis(for some reason, not published yet) +- Uniswap-v3-Risk-Analysis(will be available soon) ## Contributing diff --git a/docs/fetching-all-the-data-of-a-certain-pool-from-ethereum.md b/docs/fetching-all-the-data-of-a-certain-pool-from-ethereum.md index 9b9fe70..8080745 100644 --- a/docs/fetching-all-the-data-of-a-certain-pool-from-ethereum.md +++ b/docs/fetching-all-the-data-of-a-certain-pool-from-ethereum.md @@ -32,6 +32,14 @@ This means, there is more information added on top of the mainnet events, and th An example of the whole process will be: ```typescript +import { + EndBlockTypeWhenRecover, + EventDataSourceType, + SimulationDataManager, + SimulatorClient, + SQLiteSimulationDataManager, +} from "@bella-defintech/uniswap-v3-simulator"; + // 1. Instantiate a SimulationDataManager // this is for handling the internal data (snapshots, roadmaps, etc.) let simulationDataManager: SimulationDataManager = diff --git a/docs/how-tuner-library-works.md b/docs/how-tuner-library-works.md index 84b6d18..68a9465 100644 --- a/docs/how-tuner-library-works.md +++ b/docs/how-tuner-library-works.md @@ -14,7 +14,7 @@ The overall design of the simulator consists of several components (rough depend |\_\_ **`SimulatorRoadMapManager`** - _To take snapshots and do state-change roadmap tracking_ - |\_\_ **`InternalDBManager`** - _To persist snapshots and roadmaps using SQLite_ + |\_\_ **`SimulationDataManager`** - _To persist snapshots and roadmaps using SQLite_ There are 2 abstraction layers of the library: diff --git a/docs/installing-tuner.md b/docs/installing-tuner.md index 8245f53..c3f428f 100644 --- a/docs/installing-tuner.md +++ b/docs/installing-tuner.md @@ -5,3 +5,7 @@ ```bash yarn add @bella-defintech/uniswap-v3-simulator ``` + +```bash +yarn upgrade @bella-defintech/uniswap-v3-simulator +``` diff --git a/docs/loading-and-streaming-events-into-a-pool.md b/docs/loading-and-streaming-events-into-a-pool.md new file mode 100644 index 0000000..e6e76fd --- /dev/null +++ b/docs/loading-and-streaming-events-into-a-pool.md @@ -0,0 +1,131 @@ +### Loading and streaming events into a pool + +Suppose a quant developer is going to backtest some strategy on mainnet pool. + +This includes the following steps: + +1. Using mainnet data to initialize a core pool +2. Replaying events up to a certain block as a checkpoint +3. Do some interaction as the strategy asks as interpolation to real transactions +4. Replaying events until the next checkpoint +5. Repeat step3-4 until events are run out +6. Evaluate performance of the strategy + +Tuner has offered an example project called `uniswap-v3-bot`. It similarly follow the process above to build a strategy platform for backtesting, dry-run and run, where a user adds a new strategy by implementing some callback interfaces(`trigger`, `cache`, `act` and `evaluate`). See [Uniswap-v3-Strategy-Backtest](https://github.com/Bella-DeFinTech/uniswap-v3-simulator/tree/main/examples/Uniswap-v3-Strategy-Backtest). + +When getting a core pool instance by `clientInstance.initCorePoolFromMainnet` or `clientInstance.recoverFromMainnetEventDBFile`, Tuner has already automatically replayed events from the first record to the last one in `endBlock`. For replaying following events, you can load them by `EventDBManager`, and decide how to use them on your own. + +An example of streaming process will be: + +```typescript +import { + ConfigurableCorePool, + EventDBManager, + EventType, + SimulationDataManager, + SimulatorClient, + SQLiteSimulationDataManager, +} from "@bella-defintech/uniswap-v3-simulator"; +import { LiquidityEvent } from "@bella-defintech/uniswap-v3-simulator/dist/entity/LiquidityEvent"; +import { SwapEvent } from "@bella-defintech/uniswap-v3-simulator/dist/entity/SwapEvent"; +import JSBI from "jsbi"; + +// the database name containing downloaded-and-pre-processed mainnet events +let mainnetEventDBFilePath = + "events_0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8.db"; + +// build a client instance +let simulationDataManager: SimulationDataManager = + await SQLiteSimulationDataManager.buildInstance( + "Your file path to save the internal data" + ); +let clientInstance: SimulatorClient = new SimulatorClient( + simulationDataManager +); + +// Specify an endBlock number +// the SimulatorClient will replay events up to that block +let endBlock0 = 12374077; + +// get a pool instance +let configurableCorePool: ConfigurableCorePool = + await clientInstance.recoverFromMainnetEventDBFile( + mainnetEventDBFilePath, + endBlock0 + ); + +// get an EventDBManager instance to load events +let eventDB = await EventDBManager.buildInstance(mainnetEventDBFilePath); + +// get and sort event by block number +let events: (LiquidityEvent | SwapEvent)[] = []; +let startBlock = 1000, + endBlock = 2000; +let mintEvents: LiquidityEvent[] = + await eventDB.getLiquidityEventsByBlockNumber( + EventType.MINT, + startBlock, + endBlock + ); +let burnEvents: LiquidityEvent[] = + await eventDB.getLiquidityEventsByBlockNumber( + EventType.BURN, + startBlock, + endBlock + ); +let swapEvents: SwapEvent[] = await eventDB.getSwapEventsByBlockNumber( + startBlock, + endBlock +); +events.push(...mintEvents); +events.push(...burnEvents); +events.push(...swapEvents); +events.sort(function (a, b) { + return a.blockNumber == b.blockNumber + ? a.logIndex - b.logIndex + : a.blockNumber - b.blockNumber; +}); + +// replay events +for (let index = 0; index < events.length; index++) { + // avoid stack overflow for the possible recovering operation + if (index % 1000 == 0) { + configurableCorePool.takeSnapshot(""); + } + + let event = events[index]; + switch (event.type) { + case EventType.MINT: + await configurableCorePool.mint( + event.recipient, + event.tickLower, + event.tickUpper, + event.liquidity + ); + break; + case EventType.BURN: + await configurableCorePool.burn( + event.msgSender, + event.tickLower, + event.tickUpper, + event.liquidity + ); + break; + case EventType.SWAP: + let zeroForOne: boolean = JSBI.greaterThan(event.amount0, JSBI.BigInt(0)) + ? true + : false; + await configurableCorePool.swap( + zeroForOne, + event.amountSpecified + //,event.sqrt_price_x96 + ); + break; + default: + // @ts-ignore: ExhaustiveCheck + const exhaustiveCheck: never = event; + } +} +``` + +Note: Tuner doesn't export `LiquidityEvent` | `SwapEvent` directly but you can sitll use them. For external data(event logs), we recommend you to implement your `EventDBManager` to load and manage event logs, according to your context. diff --git a/docs/quick-start.md b/docs/quick-start.md index 0706c17..3dafd7f 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -1,6 +1,12 @@ ### Quick Start ```typescript +import { + SimulationDataManager, + SimulatorClient, + SQLiteSimulationDataManager, +} from "@bella-defintech/uniswap-v3-simulator"; + // 1. Instantiate a SimulationDataManager // this is for handling the internal data (snapshots, roadmaps, etc.) let simulationDataManager: SimulationDataManager = @@ -26,7 +32,7 @@ let endBlock = 12374077; // 3. This method helps you: // Download event data of a certain Uniswap V3 pool from mainnet // Pre-process the data to figure out the inputs of swap events -await clientInstance.initCorePoolFromMainnetPool( +await clientInstance.initCorePoolFromMainnet( poolName, poolAddress, "afterDeployment" diff --git a/package.json b/package.json index 463e233..8fa5513 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bella-defintech/uniswap-v3-simulator", - "version": "0.1.4", + "version": "0.1.5", "description": "the 'Tuner', a Uniswap V3 Simulator", "keywords": [ "uniswap", diff --git a/src/manager/TickManager.ts b/src/manager/TickManager.ts index 48c3e67..8497a6c 100644 --- a/src/manager/TickManager.ts +++ b/src/manager/TickManager.ts @@ -71,7 +71,7 @@ export class TickManager { ): { nextTick: number; initialized: boolean } { const sortedTicks = this.getSortedTicks(); let compressed = Math.floor(tick / tickSpacing); // matches rounding in the code - if (tick < 0 && tick % tickSpacing != 0) compressed--; + // if (tick < 0 && tick % tickSpacing != 0) compressed--; if (lte) { const wordPos = compressed >> 8; const minimum = (wordPos << 8) * tickSpacing; diff --git a/test/TestSubgraph.test.ts b/test/TestSubgraph.test.ts index 9f47b69..8bffff5 100644 --- a/test/TestSubgraph.test.ts +++ b/test/TestSubgraph.test.ts @@ -6,7 +6,7 @@ chai.use(chaiAsPromised); describe("Test Uniswap v3 Subgraph", function () { const APIURL = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"; - it.only("can download events", async function () { + it("can download events", async function () { const query = gql` query { pool(id: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8") { diff --git a/test/TickManager.test.ts b/test/TickManager.test.ts new file mode 100644 index 0000000..0104a6d --- /dev/null +++ b/test/TickManager.test.ts @@ -0,0 +1,104 @@ +import { Tick } from "../src/model/Tick"; +import { TickMath } from "../src/util/TickMath"; +import { expect } from "./shared/expect"; +import { TickManager } from "../src/manager/TickManager"; + +describe("TickManager", () => { + describe("#getNextInitializedTick", () => { + let highTick: Tick; + let lowTick: Tick; + let midTick: Tick; + let ticks: Tick[]; + let tickManager: TickManager = new TickManager(); + + beforeEach(() => { + lowTick = new Tick(TickMath.MIN_TICK + 1); + midTick = new Tick(0); + highTick = new Tick(TickMath.MAX_TICK - 1); + + ticks = [lowTick, midTick, highTick]; + + for (let tick of ticks) { + tickManager.set(tick); + } + }); + + it("lte = true", () => { + expect(tickManager.getNextInitializedTick(-257, 1, true)).to.eql({ + nextTick: -512, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-256, 1, true)).to.eql({ + nextTick: -256, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-1, 1, true)).to.eql({ + nextTick: -256, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(0, 1, true)).to.eql({ + nextTick: 0, + initialized: true, + }); + expect(tickManager.getNextInitializedTick(1, 1, true)).to.eql({ + nextTick: 0, + initialized: true, + }); + expect(tickManager.getNextInitializedTick(255, 1, true)).to.eql({ + nextTick: 0, + initialized: true, + }); + expect(tickManager.getNextInitializedTick(256, 1, true)).to.eql({ + nextTick: 256, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(257, 1, true)).to.eql({ + nextTick: 256, + initialized: false, + }); + }); + + it("lte = false", () => { + expect(tickManager.getNextInitializedTick(-215041, 60, false)).to.eql({ + nextTick: -199740, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-257, 1, false)).to.eql({ + nextTick: -1, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-256, 1, false)).to.eql({ + nextTick: -1, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-2, 1, false)).to.eql({ + nextTick: -1, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(-1, 1, false)).to.eql({ + nextTick: 0, + initialized: true, + }); + expect(tickManager.getNextInitializedTick(0, 1, false)).to.eql({ + nextTick: 255, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(1, 1, false)).to.eql({ + nextTick: 255, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(254, 1, false)).to.eql({ + nextTick: 255, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(255, 1, false)).to.eql({ + nextTick: 511, + initialized: false, + }); + expect(tickManager.getNextInitializedTick(256, 1, false)).to.eql({ + nextTick: 511, + initialized: false, + }); + }); + }); +});