2026-02-27 09:00:22 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
|
|
|
|
import { LiquidityManager } from "../../src/LiquidityManager.sol";
|
|
|
|
|
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
|
2026-02-27 12:02:29 +00:00
|
|
|
import { FormatLib } from "./FormatLib.sol";
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
import { PositionTracker } from "./PositionTracker.sol";
|
|
|
|
|
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
|
|
|
|
|
import { IUniswapV3Pool } from "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
|
|
|
|
import { console2 } from "forge-std/console2.sol";
|
2026-02-27 09:00:22 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @title StrategyExecutor
|
|
|
|
|
* @notice Drives the KrAIken recenter loop during event replay.
|
|
|
|
|
*
|
|
|
|
|
* Called by EventReplayer after every block advancement. Triggers
|
|
|
|
|
* LiquidityManager.recenter() once per `recenterInterval` blocks and logs:
|
|
|
|
|
* - Block number and timestamp
|
|
|
|
|
* - Pre/post positions (tickLower, tickUpper, liquidity) for Floor, Anchor, Discovery
|
|
|
|
|
* - Fees collected (WETH + KRK) as the delta of feeDestination's balances
|
|
|
|
|
* - Revert reason on failure (logged and skipped — replay never halts)
|
|
|
|
|
*
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
* Position tracking and P&L metrics are delegated to PositionTracker, which is
|
|
|
|
|
* notified on every block (for time-in-range) and on each successful recenter
|
|
|
|
|
* (for position lifecycle and fee/IL accounting).
|
|
|
|
|
*
|
2026-03-14 09:15:48 +00:00
|
|
|
* Access model: recenter() is permissionless — no special access grant is required.
|
|
|
|
|
* EventReplayer advances block.timestamp via vm.warp, so the 60-second cooldown and
|
|
|
|
|
* the 300-second TWAP window pass normally during simulation.
|
2026-02-27 09:00:22 +00:00
|
|
|
*
|
|
|
|
|
* TODO(#319): The negligible-impact assumption means we replay historical events
|
|
|
|
|
* as-is without accounting for KrAIken's own liquidity affecting swap outcomes.
|
|
|
|
|
* A future enhancement should add a feedback loop that adjusts replayed swap
|
|
|
|
|
* amounts based on KrAIken's active tick ranges.
|
|
|
|
|
*/
|
|
|
|
|
contract StrategyExecutor {
|
2026-02-27 12:02:29 +00:00
|
|
|
using FormatLib for uint256;
|
|
|
|
|
using FormatLib for int256;
|
|
|
|
|
|
2026-02-27 09:00:22 +00:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Configuration (immutable)
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
LiquidityManager public immutable lm;
|
|
|
|
|
IERC20 public immutable wethToken;
|
|
|
|
|
IERC20 public immutable krkToken;
|
|
|
|
|
address public immutable feeDestination;
|
|
|
|
|
/// @notice Minimum block gap between recenter attempts.
|
|
|
|
|
uint256 public immutable recenterInterval;
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
/// @notice Position tracker — records lifecycle, fees, and P&L for each position.
|
|
|
|
|
PositionTracker public immutable tracker;
|
2026-02-27 09:00:22 +00:00
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Runtime state
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
2026-02-27 12:02:29 +00:00
|
|
|
/// @notice Block of the last recenter attempt (successful or not).
|
|
|
|
|
/// Initialised to block.number at construction so the first recenter
|
|
|
|
|
/// attempt is deferred by recenterInterval blocks rather than firing
|
|
|
|
|
/// immediately on the first observed historical block.
|
2026-02-27 09:00:22 +00:00
|
|
|
uint256 public lastRecenterBlock;
|
|
|
|
|
uint256 public totalRecenters;
|
|
|
|
|
uint256 public failedRecenters;
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Constructor
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
LiquidityManager _lm,
|
|
|
|
|
IERC20 _wethToken,
|
|
|
|
|
IERC20 _krkToken,
|
|
|
|
|
address _feeDestination,
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
uint256 _recenterInterval,
|
|
|
|
|
IUniswapV3Pool _pool,
|
|
|
|
|
bool _token0isWeth
|
2026-02-27 09:00:22 +00:00
|
|
|
) {
|
|
|
|
|
lm = _lm;
|
|
|
|
|
wethToken = _wethToken;
|
|
|
|
|
krkToken = _krkToken;
|
|
|
|
|
feeDestination = _feeDestination;
|
|
|
|
|
recenterInterval = _recenterInterval;
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
tracker = new PositionTracker(_pool, _token0isWeth);
|
2026-02-27 12:02:29 +00:00
|
|
|
// Defer the first recenter attempt by recenterInterval blocks so we don't
|
|
|
|
|
// try to recenter before any meaningful price movement has occurred.
|
|
|
|
|
lastRecenterBlock = block.number;
|
2026-02-27 09:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Main entry point (called by EventReplayer after each block advancement)
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @notice Attempt a recenter if enough blocks have elapsed since the last one.
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
* Always notifies the tracker of the current block for time-in-range accounting.
|
2026-02-27 09:00:22 +00:00
|
|
|
* @param blockNum Current block number (as advanced by EventReplayer via vm.roll).
|
|
|
|
|
*/
|
|
|
|
|
function maybeRecenter(uint256 blockNum) external {
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
// Always notify the tracker so time-in-range is counted for every observed block.
|
|
|
|
|
tracker.notifyBlock(blockNum);
|
|
|
|
|
|
2026-02-27 09:00:22 +00:00
|
|
|
if (blockNum - lastRecenterBlock < recenterInterval) return;
|
|
|
|
|
|
|
|
|
|
// Snapshot pre-recenter positions.
|
|
|
|
|
(uint128 fLiqPre, int24 fLoPre, int24 fHiPre) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
|
|
|
|
(uint128 aLiqPre, int24 aLoPre, int24 aHiPre) = lm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
|
|
|
|
(uint128 dLiqPre, int24 dLoPre, int24 dHiPre) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
|
|
|
|
|
|
|
|
|
// Snapshot fee destination balances to compute fees collected during this recenter.
|
|
|
|
|
uint256 wethPre = wethToken.balanceOf(feeDestination);
|
|
|
|
|
uint256 krkPre = krkToken.balanceOf(feeDestination);
|
|
|
|
|
|
|
|
|
|
// Always advance lastRecenterBlock, even on failure, to avoid hammering a
|
|
|
|
|
// persistently failing condition on every subsequent block.
|
|
|
|
|
lastRecenterBlock = blockNum;
|
|
|
|
|
|
|
|
|
|
bool success;
|
2026-03-12 18:21:27 +00:00
|
|
|
bool isUp;
|
2026-02-27 09:00:22 +00:00
|
|
|
string memory failReason;
|
|
|
|
|
|
2026-03-12 18:21:27 +00:00
|
|
|
try lm.recenter() returns (bool _isUp) {
|
2026-02-27 09:00:22 +00:00
|
|
|
success = true;
|
2026-03-12 18:21:27 +00:00
|
|
|
isUp = _isUp;
|
2026-02-27 09:00:22 +00:00
|
|
|
totalRecenters++;
|
|
|
|
|
} catch Error(string memory reason) {
|
|
|
|
|
failReason = reason;
|
|
|
|
|
failedRecenters++;
|
|
|
|
|
} catch {
|
|
|
|
|
failReason = "unknown revert";
|
|
|
|
|
failedRecenters++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
2026-02-27 12:02:29 +00:00
|
|
|
console2.log(string.concat("[recenter SKIP @ block ", blockNum.str(), "] reason: ", failReason));
|
2026-02-27 09:00:22 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Snapshot post-recenter positions.
|
|
|
|
|
(uint128 fLiqPost, int24 fLoPost, int24 fHiPost) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
|
|
|
|
(uint128 aLiqPost, int24 aLoPost, int24 aHiPost) = lm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
|
|
|
|
(uint128 dLiqPost, int24 dLoPost, int24 dHiPost) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
|
|
|
|
|
|
|
|
|
uint256 feesWeth = wethToken.balanceOf(feeDestination) - wethPre;
|
|
|
|
|
uint256 feesKrk = krkToken.balanceOf(feeDestination) - krkPre;
|
|
|
|
|
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
// Record recenter in position tracker.
|
|
|
|
|
tracker.recordRecenter(
|
|
|
|
|
PositionTracker.PositionSnapshot({
|
|
|
|
|
floorLiq: fLiqPre,
|
|
|
|
|
floorLo: fLoPre,
|
|
|
|
|
floorHi: fHiPre,
|
|
|
|
|
anchorLiq: aLiqPre,
|
|
|
|
|
anchorLo: aLoPre,
|
|
|
|
|
anchorHi: aHiPre,
|
|
|
|
|
discLiq: dLiqPre,
|
|
|
|
|
discLo: dLoPre,
|
|
|
|
|
discHi: dHiPre
|
|
|
|
|
}),
|
|
|
|
|
PositionTracker.PositionSnapshot({
|
|
|
|
|
floorLiq: fLiqPost,
|
|
|
|
|
floorLo: fLoPost,
|
|
|
|
|
floorHi: fHiPost,
|
|
|
|
|
anchorLiq: aLiqPost,
|
|
|
|
|
anchorLo: aLoPost,
|
|
|
|
|
anchorHi: aHiPost,
|
|
|
|
|
discLiq: dLiqPost,
|
|
|
|
|
discLo: dLoPost,
|
|
|
|
|
discHi: dHiPost
|
|
|
|
|
}),
|
|
|
|
|
feesWeth,
|
|
|
|
|
feesKrk,
|
|
|
|
|
blockNum,
|
|
|
|
|
block.timestamp
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-27 09:00:22 +00:00
|
|
|
_logRecenter(
|
|
|
|
|
blockNum,
|
2026-03-12 18:21:27 +00:00
|
|
|
isUp,
|
2026-03-12 19:12:46 +00:00
|
|
|
aLiqPre == 0, // bootstrap: no anchor existed before this recenter
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
fLiqPre,
|
|
|
|
|
fLoPre,
|
|
|
|
|
fHiPre,
|
|
|
|
|
aLiqPre,
|
|
|
|
|
aLoPre,
|
|
|
|
|
aHiPre,
|
|
|
|
|
dLiqPre,
|
|
|
|
|
dLoPre,
|
|
|
|
|
dHiPre,
|
|
|
|
|
fLiqPost,
|
|
|
|
|
fLoPost,
|
|
|
|
|
fHiPost,
|
|
|
|
|
aLiqPost,
|
|
|
|
|
aLoPost,
|
|
|
|
|
aHiPost,
|
|
|
|
|
dLiqPost,
|
|
|
|
|
dLoPost,
|
|
|
|
|
dHiPost,
|
2026-02-27 09:00:22 +00:00
|
|
|
feesWeth,
|
|
|
|
|
feesKrk
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Summary
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @notice Print a summary of all recenter activity. Call at end of replay.
|
|
|
|
|
*/
|
|
|
|
|
function logSummary() external view {
|
|
|
|
|
(uint128 fLiq, int24 fLo, int24 fHi) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
|
|
|
|
(uint128 aLiq, int24 aLo, int24 aHi) = lm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
|
|
|
|
(uint128 dLiq, int24 dLo, int24 dHi) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
|
|
|
|
|
|
|
|
|
console2.log("=== KrAIken Strategy Summary ===");
|
|
|
|
|
console2.log("Total recenters: ", totalRecenters);
|
|
|
|
|
console2.log("Failed recenters: ", failedRecenters);
|
|
|
|
|
console2.log("Recenter interval:", recenterInterval, "blocks");
|
2026-02-27 12:02:29 +00:00
|
|
|
console2.log(string.concat("Final Floor: tick [", int256(fLo).istr(), ", ", int256(fHi).istr(), "] liq=", uint256(fLiq).str()));
|
|
|
|
|
console2.log(string.concat("Final Anchor: tick [", int256(aLo).istr(), ", ", int256(aHi).istr(), "] liq=", uint256(aLiq).str()));
|
|
|
|
|
console2.log(string.concat("Final Discovery: tick [", int256(dLo).istr(), ", ", int256(dHi).istr(), "] liq=", uint256(dLiq).str()));
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
|
2026-02-27 12:02:29 +00:00
|
|
|
// Use lastNotifiedBlock from the tracker as the authoritative final block —
|
|
|
|
|
// it reflects the last block actually processed by the replay, which may be
|
|
|
|
|
// up to recenterInterval blocks later than lastRecenterBlock.
|
|
|
|
|
tracker.logFinalSummary(tracker.lastNotifiedBlock());
|
2026-02-27 09:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// Internal helpers
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
function _logRecenter(
|
|
|
|
|
uint256 blockNum,
|
2026-03-12 18:21:27 +00:00
|
|
|
bool isUp,
|
2026-03-12 19:12:46 +00:00
|
|
|
bool isBootstrap,
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
uint128 fLiqPre,
|
|
|
|
|
int24 fLoPre,
|
|
|
|
|
int24 fHiPre,
|
|
|
|
|
uint128 aLiqPre,
|
|
|
|
|
int24 aLoPre,
|
|
|
|
|
int24 aHiPre,
|
|
|
|
|
uint128 dLiqPre,
|
|
|
|
|
int24 dLoPre,
|
|
|
|
|
int24 dHiPre,
|
|
|
|
|
uint128 fLiqPost,
|
|
|
|
|
int24 fLoPost,
|
|
|
|
|
int24 fHiPost,
|
|
|
|
|
uint128 aLiqPost,
|
|
|
|
|
int24 aLoPost,
|
|
|
|
|
int24 aHiPost,
|
|
|
|
|
uint128 dLiqPost,
|
|
|
|
|
int24 dLoPost,
|
|
|
|
|
int24 dHiPost,
|
2026-02-27 09:00:22 +00:00
|
|
|
uint256 feesWeth,
|
|
|
|
|
uint256 feesKrk
|
|
|
|
|
)
|
|
|
|
|
internal
|
|
|
|
|
view
|
|
|
|
|
{
|
2026-03-12 19:12:46 +00:00
|
|
|
console2.log(string.concat("=== Recenter #", totalRecenters.str(), " @ block ", blockNum.str(), " direction=", isBootstrap ? "BOOTSTRAP" : (isUp ? "UP" : "DOWN"), " ==="));
|
2026-02-27 12:02:29 +00:00
|
|
|
console2.log(string.concat(" Floor pre: tick [", int256(fLoPre).istr(), ", ", int256(fHiPre).istr(), "] liq=", uint256(fLiqPre).str()));
|
|
|
|
|
console2.log(string.concat(" Anchor pre: tick [", int256(aLoPre).istr(), ", ", int256(aHiPre).istr(), "] liq=", uint256(aLiqPre).str()));
|
|
|
|
|
console2.log(string.concat(" Disc pre: tick [", int256(dLoPre).istr(), ", ", int256(dHiPre).istr(), "] liq=", uint256(dLiqPre).str()));
|
|
|
|
|
console2.log(string.concat(" Floor post: tick [", int256(fLoPost).istr(), ", ", int256(fHiPost).istr(), "] liq=", uint256(fLiqPost).str()));
|
|
|
|
|
console2.log(string.concat(" Anchor post: tick [", int256(aLoPost).istr(), ", ", int256(aHiPost).istr(), "] liq=", uint256(aLiqPost).str()));
|
|
|
|
|
console2.log(string.concat(" Disc post: tick [", int256(dLoPost).istr(), ", ", int256(dHiPost).istr(), "] liq=", uint256(dLiqPost).str()));
|
|
|
|
|
console2.log(string.concat(" Fees WETH: ", feesWeth.str(), " Fees KRK: ", feesKrk.str()));
|
2026-02-27 09:00:22 +00:00
|
|
|
}
|
|
|
|
|
}
|