-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #76 from Bella-DeFinTech/main
Release v0.1.5
- Loading branch information
Showing
10 changed files
with
260 additions
and
6 deletions.
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
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,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. |
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
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,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, | ||
}); | ||
}); | ||
}); | ||
}); |