"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniV3MM = exports.defaultConfig = void 0;
var typechain_1 = require("../../typechain");
var uniV3Range_1 = require("./uniV3Range");
var uniV3Utils_1 = require("./uniV3Utils");
var ethers_1 = require("ethers");
var uniV3_1 = require("../uniV3");
var formatUnits = ethers_1.utils.formatUnits;
var MA_TIME_INTERVAL = 6 * 30 * 60; // 30 minutes
exports.defaultConfig = {
    mainRangeOffset: 0.08,
    secondaryRangeOffset: 0.2,
    rebalanceWeight: 0.2,
    mainRangeShare: 0.05,
    secRangeShare: 0.05 * 2,
};
var UniV3MM = /** @class */ (function () {
    function UniV3MM(vault, wallet, feeTier, // uniswap fee tier
    chain, options, useMovingAvg) {
        if (options === void 0) { options = exports.defaultConfig; }
        if (useMovingAvg === void 0) { useMovingAvg = true; }
        var _this = this;
        this.vault = vault;
        this.wallet = wallet;
        this.feeTier = feeTier;
        this.chain = chain;
        this.useMovingAvg = useMovingAvg;
        this.lastTimestamp = 0;
        this.ranges = [];
        this.inventory = [0, 0]; // [base, quote]
        this.allocated = [0, 0]; // [base, quote]
        this.movingAvg = 0;
        this.ma_interval = MA_TIME_INTERVAL;
        this.isRebalancing = false;
        this.getRanges = function (price) {
            var _a = _this, feeTier = _a.feeTier, secondaryRangeOffset = _a.secondaryRangeOffset, mainRangeOffset = _a.mainRangeOffset, rebalanceWeight = _a.rebalanceWeight, prevReb = _a.prevReb;
            var mainLow = price * (1 - mainRangeOffset);
            var mainHigh = price / (1 - mainRangeOffset);
            var secRangeLowPreTick = price * (1 - secondaryRangeOffset);
            var secRangeHighPreTick = price / (1 - secondaryRangeOffset);
            var secRangeLow = (0, uniV3Utils_1.priceToTickPrice)(secRangeLowPreTick, feeTier);
            var secRangeHigh = (0, uniV3Utils_1.priceToTickPrice)(secRangeHighPreTick, feeTier);
            var rangeDiff = secondaryRangeOffset - mainRangeOffset;
            var rebRange = mainRangeOffset + rangeDiff * rebalanceWeight;
            var rebalanceLowPreTick = prevReb * (1 - rebRange);
            var rebalanceHighPreTick = prevReb / (1 - rebRange);
            var rebalanceLow = (0, uniV3Utils_1.priceToTickPrice)(rebalanceLowPreTick, feeTier);
            var rebalanceHigh = (0, uniV3Utils_1.priceToTickPrice)(rebalanceHighPreTick, feeTier);
            return {
                mainLow: (0, uniV3Utils_1.priceToTickPrice)(mainLow, feeTier),
                mainHigh: (0, uniV3Utils_1.priceToTickPrice)(mainHigh, feeTier),
                secRangeLow: secRangeLow,
                secRangeHigh: secRangeHigh,
                rebalanceLow: rebalanceLow,
                rebalanceHigh: rebalanceHigh,
            };
        };
        this.getPrice = function (twapInterval) { return __awaiter(_this, void 0, void 0, function () {
            var _a, reverse, dec0, dec1, pool, sqrtPriceX96_1, sqrtPrice_1, price_1, secondsAgos, _b, tickCumulatives, sqrtPriceX96, price;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _a = this, reverse = _a.reverse, dec0 = _a.dec0, dec1 = _a.dec1;
                        return [4 /*yield*/, this.getUniV3Pool()];
                    case 1:
                        pool = _c.sent();
                        if (!(twapInterval == null || twapInterval == 0)) return [3 /*break*/, 3];
                        return [4 /*yield*/, pool.slot0()];
                    case 2:
                        sqrtPriceX96_1 = (_c.sent()).sqrtPriceX96;
                        sqrtPrice_1 = parseFloat(formatUnits(sqrtPriceX96_1, 0));
                        price_1 = Math.pow(sqrtPrice_1, 2) / Math.pow(2, 192);
                        return [2 /*return*/, reverse ? 1 / price_1 : price_1];
                    case 3:
                        secondsAgos = [twapInterval, 0];
                        return [4 /*yield*/, pool.observe(secondsAgos)];
                    case 4:
                        _b = __read.apply(void 0, [_c.sent(), 1]), tickCumulatives = _b[0];
                        sqrtPriceX96 = (0, uniV3_1.getSqrtRatioAtTick)(tickCumulatives[1].sub(tickCumulatives[0]).div(twapInterval).toNumber());
                        price = (0, uniV3_1.sqrtPrice)([dec0, dec1], sqrtPriceX96).toSignificant({
                            reverse: reverse,
                            decimalPlaces: 3,
                        });
                        return [2 /*return*/, price];
                }
            });
        }); };
        this.getBidAsk = function (price) {
            if (price === void 0) { price = _this.price; }
            var bid = price * (1 - _this.mainRangeOffset);
            var ask = price / (1 - _this.mainRangeOffset);
            return { bid: bid, ask: ask };
        };
        this.mainRangeOffset = options.mainRangeOffset;
        this.secondaryRangeOffset = options.secondaryRangeOffset;
        this.rebalanceWeight = options.rebalanceWeight;
        this.mainRangeShare = options.mainRangeShare;
        this.secRangeShare = options.secRangeShare;
        this.rebalanceLow = options.secondaryRangeOffset;
        this.rebalanceHigh = options.secondaryRangeOffset;
    }
    UniV3MM.prototype.init = function (baseToken, price) {
        return __awaiter(this, void 0, void 0, function () {
            var _a, _b, t0, t1, _c, _d, _e, _f;
            return __generator(this, function (_g) {
                switch (_g.label) {
                    case 0:
                        _a = this;
                        return [4 /*yield*/, this.vault.token0()];
                    case 1:
                        _a.token0 = _g.sent();
                        _b = this;
                        return [4 /*yield*/, this.vault.token1()];
                    case 2:
                        _b.token1 = _g.sent();
                        this.reverse = this.token0 !== baseToken;
                        return [4 /*yield*/, typechain_1.ERC20__factory.connect(this.token0, this.wallet)];
                    case 3:
                        t0 = _g.sent();
                        return [4 /*yield*/, typechain_1.ERC20__factory.connect(this.token1, this.wallet)];
                    case 4:
                        t1 = _g.sent();
                        _c = this;
                        return [4 /*yield*/, t0.decimals()];
                    case 5:
                        _c.dec0 = _g.sent();
                        _d = this;
                        return [4 /*yield*/, t1.decimals()];
                    case 6:
                        _d.dec1 = _g.sent();
                        _e = this;
                        if (!(price != null)) return [3 /*break*/, 7];
                        _f = price;
                        return [3 /*break*/, 9];
                    case 7: return [4 /*yield*/, this.getPrice(0)];
                    case 8:
                        _f = _g.sent();
                        _g.label = 9;
                    case 9:
                        _e.price = _f;
                        return [4 /*yield*/, this.updateRanges()];
                    case 10:
                        _g.sent();
                        if (!(this.ranges.length > 0)) return [3 /*break*/, 12];
                        return [4 /*yield*/, this.syncFromDB()];
                    case 11:
                        _g.sent();
                        _g.label = 12;
                    case 12: return [2 /*return*/];
                }
            });
        });
    };
    UniV3MM.prototype.getClosePositionParams = function () {
        return __awaiter(this, void 0, void 0, function () {
            var burnRanges;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.updateRanges()];
                    case 1:
                        _a.sent();
                        burnRanges = __spreadArray([], __read(this.ranges), false);
                        this.ranges = [];
                        return [4 /*yield*/, this.getRebalanceParams(burnRanges)];
                    case 2: return [2 /*return*/, _a.sent()];
                }
            });
        });
    };
    UniV3MM.prototype.rebalanceRanges = function () {
        return __awaiter(this, void 0, void 0, function () {
            var price, _a, mainLow, mainHigh, secRangeLow, secRangeHigh, totals, burnRanges, _b, base, quote, maxBase, maxQuote, mainLp, extraBase, extraQuote, _c, secBaseValue, secQuoteValue, low, mainRange, high, rebalanceParams, success;
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        // this can happen async so we want to ensure a flag is set
                        if (this.isRebalancing)
                            return [2 /*return*/];
                        this.isRebalancing = true;
                        price = this.price;
                        return [4 /*yield*/, this.updateRanges()];
                    case 1:
                        _d.sent();
                        _a = this.getRanges(price), mainLow = _a.mainLow, mainHigh = _a.mainHigh, secRangeLow = _a.secRangeLow, secRangeHigh = _a.secRangeHigh;
                        return [4 /*yield*/, this.getBalances()];
                    case 2:
                        totals = (_d.sent()).totals;
                        this.inventory = totals;
                        burnRanges = __spreadArray([], __read(this.ranges), false);
                        this.ranges = [];
                        _b = __read((0, uniV3Utils_1.tokensForStrategy)(mainLow, mainHigh, 1, price), 2), base = _b[0], quote = _b[1];
                        maxBase = this.inventory[0] / base;
                        maxQuote = this.inventory[1] / quote;
                        mainLp = Math.min(maxBase, maxQuote) * this.mainRangeShare;
                        extraBase = (this.inventory[0] - mainLp * base) * 0.99;
                        extraQuote = (this.inventory[1] - mainLp * quote) * 0.99;
                        _c = this.getRangeAmounts(mainLp, extraBase, extraQuote), secBaseValue = _c.secBaseValue, secQuoteValue = _c.secQuoteValue;
                        low = new uniV3Range_1.UniV3Range(this, secQuoteValue, secRangeLow, mainLow);
                        mainRange = new uniV3Range_1.UniV3Range(this, mainLp, mainLow, mainHigh);
                        high = new uniV3Range_1.UniV3Range(this, secBaseValue, mainHigh, secRangeHigh);
                        // const lFull = new UniV3Range(
                        //   this,
                        //   extraQuote - secQuoteValue,
                        //   MIN_PRICE,
                        //   secRangeLow
                        // );    // const hFull = new UniV3Range(
                        //   this,
                        //   extraBase * price - secBaseValue,
                        //   secRangeHigh,
                        //   MAX_PRICE
                        // );
                        this.ranges = [low, mainRange, high];
                        this.logRanges();
                        return [4 /*yield*/, this.getRebalanceParams(burnRanges)];
                    case 3:
                        rebalanceParams = _d.sent();
                        return [4 /*yield*/, this.executeRebalance(rebalanceParams)];
                    case 4:
                        success = _d.sent();
                        if (!success) return [3 /*break*/, 6];
                        this.prevReb = this.price;
                        return [4 /*yield*/, this.syncToDB()];
                    case 5:
                        _d.sent();
                        return [3 /*break*/, 7];
                    case 6:
                        // undo range update if tx failed
                        this.ranges = __spreadArray([], __read(burnRanges), false);
                        _d.label = 7;
                    case 7:
                        this.isRebalancing = false;
                        return [2 /*return*/, rebalanceParams];
                }
            });
        });
    };
    UniV3MM.prototype.getRangeAmounts = function (mainLp, extraBase, extraQuote) {
        var _a = this, price = _a.price, secRangeShare = _a.secRangeShare;
        var secBaseValue = (extraBase * price * secRangeShare) / 2;
        var secQuoteValue = (extraQuote * secRangeShare) / 2;
        return { secBaseValue: secBaseValue, secQuoteValue: secQuoteValue };
    };
    UniV3MM.prototype.getCurrentRange = function (direction, price) {
        return this.ranges.find(function (range) {
            if (direction === 1)
                return range.minRange <= price && price < range.maxRange;
            else
                return range.minRange < price && price <= range.maxRange;
        });
    };
    UniV3MM.prototype.update = function () {
        return __awaiter(this, void 0, void 0, function () {
            var _a, _b, price, useMovingAvg, movingAvg, _c, _d, rebalanceLow, rebalanceHigh;
            return __generator(this, function (_e) {
                switch (_e.label) {
                    case 0:
                        if (this.isRebalancing)
                            return [2 /*return*/];
                        _a = this;
                        return [4 /*yield*/, this.getPrice(0)];
                    case 1:
                        _a.price = _e.sent();
                        _b = this, price = _b.price, useMovingAvg = _b.useMovingAvg;
                        if (!useMovingAvg) return [3 /*break*/, 3];
                        return [4 /*yield*/, this.updateMovingAvg()];
                    case 2:
                        _c = _e.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        _c = price;
                        _e.label = 4;
                    case 4:
                        movingAvg = _c;
                        if (!(this.ranges.length == 0)) return [3 /*break*/, 9];
                        // make check if the position has been opened from front end
                        return [4 /*yield*/, this.updateRanges()];
                    case 5:
                        // make check if the position has been opened from front end
                        _e.sent();
                        if (!(this.ranges.length == 0)) return [3 /*break*/, 6];
                        return [2 /*return*/];
                    case 6: return [4 /*yield*/, this.updateMovingAvg(true)];
                    case 7:
                        _e.sent();
                        return [4 /*yield*/, this.initPosition()];
                    case 8:
                        _e.sent();
                        _e.label = 9;
                    case 9: return [4 /*yield*/, this.getRebalancePoints()];
                    case 10:
                        _d = _e.sent(), rebalanceLow = _d.rebalanceLow, rebalanceHigh = _d.rebalanceHigh;
                        if (!(movingAvg < rebalanceLow || movingAvg > rebalanceHigh)) return [3 /*break*/, 13];
                        // console.log('rebalancing', price, rebalanceLow, rebalanceHigh);
                        return [4 /*yield*/, this.updateRanges()];
                    case 11:
                        // console.log('rebalancing', price, rebalanceLow, rebalanceHigh);
                        _e.sent();
                        // if the position was closed from the front end
                        // and there are no more ranges, abort
                        if (this.ranges.length === 0)
                            return [2 /*return*/];
                        return [4 /*yield*/, this.rebalanceRanges()];
                    case 12:
                        _e.sent();
                        _e.label = 13;
                    case 13: return [2 /*return*/];
                }
            });
        });
    };
    UniV3MM.prototype.updateMovingAvg = function (reInit) {
        if (reInit === void 0) { reInit = false; }
        return __awaiter(this, void 0, void 0, function () {
            var timestamp, price, time;
            return __generator(this, function (_a) {
                timestamp = Math.round(Date.now() / 1000);
                price = this.price;
                // reset moving avg
                if (reInit)
                    return [2 /*return*/, (this.movingAvg = price)];
                time = Math.min(MA_TIME_INTERVAL, timestamp - this.lastTimestamp);
                this.movingAvg =
                    (this.movingAvg * (MA_TIME_INTERVAL - time)) / MA_TIME_INTERVAL +
                        (price * time) / MA_TIME_INTERVAL;
                this.lastTimestamp = timestamp;
                return [2 /*return*/, this.movingAvg];
            });
        });
    };
    UniV3MM.prototype.getRebalancePoints = function (prevReb) {
        return __awaiter(this, void 0, void 0, function () {
            var rangeDiffLow, rangeDiffHigh, rebRangeLow, rebRangeHigh, rebalanceLow, rebalanceHigh;
            return __generator(this, function (_a) {
                prevReb = prevReb != null ? prevReb : this.prevReb;
                rangeDiffLow = this.rebalanceLow - this.mainRangeOffset;
                rangeDiffHigh = this.rebalanceHigh - this.mainRangeOffset;
                rebRangeLow = this.mainRangeOffset + rangeDiffLow * this.rebalanceWeight;
                rebRangeHigh = this.mainRangeOffset + rangeDiffHigh * this.rebalanceWeight;
                rebalanceLow = prevReb * (1 - rebRangeLow);
                rebalanceHigh = prevReb / (1 - rebRangeHigh);
                return [2 /*return*/, {
                        rebalanceLow: rebalanceLow,
                        rebalanceHigh: rebalanceHigh,
                    }];
            });
        });
    };
    UniV3MM.prototype.logRanges = function () {
        console.log('price ', this.price);
        this.ranges.forEach(function (range) {
            console.log('~~~~~');
            console.log('min max', range.minRange, range.maxRange);
            console.log(' base quote', range.base, range.quote);
        });
        console.log('~~~~~');
    };
    /// Abstract METHODS
    UniV3MM.prototype.syncToDB = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                return [2 /*return*/];
            });
        });
    };
    UniV3MM.prototype.syncFromDB = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                this.prevReb = this.price;
                return [2 /*return*/];
            });
        });
    };
    return UniV3MM;
}());
exports.UniV3MM = UniV3MM;
//# sourceMappingURL=uniV3MM.js.map