UNPKG

@symmetry-hq/baskets-sdk

Version:

Software Development Kit for interacting with Symmetry Baskets Program

391 lines (390 loc) 16.9 kB
"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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.fetchDatabase = fetchDatabase; exports.defaultBasketState = defaultBasketState; exports.calculateStats = calculateStats; exports.updateTokenStats = updateTokenStats; exports.selectTokens = selectTokens; exports.refilter = refilter; exports.calculateWeight = calculateWeight; exports.generateWeights = generateWeights; exports.combineRules = combineRules; exports.addTargetAssets = addTargetAssets; exports.removeTargetAssets = removeTargetAssets; exports.updateTargetAssets = updateTargetAssets; exports.reweight = reweight; exports.rebalance = rebalance; exports.updateBasketState = updateBasketState; exports.updateCurrentWeights = updateCurrentWeights; exports.simulate = simulate; const config_1 = require("./config"); function fetchDatabase(program, database) { return __awaiter(this, void 0, void 0, function* () { let data = yield program.account.database.fetch(database, "confirmed"); let processedData = []; for (let i = 0; i < config_1.NUM_OF_TOKENS_IN_ASSET_POOL; i++) { let index = data.data[i].index.toNumber(); let currentTokenData = []; while (true) { let dataPoint = { price: data.data[i].price[index].toNumber(), circulatingSupply: data.data[i].circulatingSupply[index].toNumber(), volume: data.data[i].volume[index].toNumber(), }; currentTokenData.push(dataPoint); index += 1; if (index == config_1.NUM_OF_DAYS_IN_DATABASE) index = 0; if (index == data.data[i].index.toNumber()) break; } processedData.push(currentTokenData); } return processedData; }); } function defaultBasketState() { let array = [1000000]; let token = [0]; let amount = [100000000]; for (let i = 1; i < 20; i++) { amount.push(0); array.push(0); token.push(0); } return { currentCompToken: token, currentCompAmount: amount, currentCompWeight: Object.assign([], array), targetWeight: Object.assign([], array), numOfTokens: 1, weightSum: 1000000, basketWorth: 100000000, lastRefilterTime: 0, lastReweightTime: 0, lastRebalanceTime: 0, singleRuleAssets: new Array(), ruleWeights: Object.assign([], array), ruleAssets: Object.assign([], token), numRuleTokens: 1, }; } function calculateStats(data, days, index) { let result = [0, 0, 0]; let startingDay = index - days + 1; let firstPrice = 0, lastPrice = 0; if (startingDay < 0) startingDay = 0; for (let i = startingDay; i <= index; i++) { if (data[i].price == 0) continue; if (firstPrice == 0) { firstPrice = data[i].price; } lastPrice = data[i].price; result[1] += data[i].volume; result[2] += data[i].circulatingSupply * data[i].price / 1000000; } result[0] = lastPrice * config_1.WEIGHT_MULTIPLIER / firstPrice; return result; } function updateTokenStats(data, index) { let tokenStats = { stats: [], }; let daysArray = [1, 7, 30, 91, 182, 365]; for (let tokenId = 0; tokenId < config_1.NUM_OF_TOKENS_IN_ASSET_POOL; tokenId++) { let statsForToken = []; for (let i = 0; i < 6; i++) { let result = calculateStats(data[tokenId], daysArray[i], index); statsForToken.push({ days: daysArray[i], performance: result[0], volume: result[1], mcap: result[2], }); } tokenStats.stats.push(statsForToken); } return tokenStats; } function selectTokens(createBasketParams, rule, tokenStats) { let ruleTokens = new Array(20); if (rule.filterBy == 0) { ruleTokens[0] = rule.fixedAsset; return ruleTokens; } let allTokens = []; for (let i = 0; i < config_1.NUM_OF_TOKENS_IN_ASSET_POOL; i++) { if (createBasketParams.assetPool.indexOf(i) == -1) continue; if (rule.excludeAssets.indexOf(i) != -1) continue; if (tokenStats.stats[i][0].volume == 0) continue; let tokenStatsInfo = tokenStats.stats[i][rule.filterDays]; let x; switch (rule.filterBy) { case 1: x = tokenStatsInfo.mcap; break; case 2: x = tokenStatsInfo.volume; break; default: x = tokenStatsInfo.performance; } allTokens.push({ value: x, id: i, }); } allTokens.sort((a, b) => 0 - (a.value > b.value ? -1 : 1)); for (let i = 0; i < rule.numAssets; i++) { switch (rule.sortBy) { case config_1.SortBy.AscendingOrder: ruleTokens[i] = allTokens[i].id; break; default: ruleTokens[i] = allTokens[allTokens.length - i - 1].id; } } return ruleTokens; } function refilter(createBasketParams, tokenStats, basketState) { basketState.singleRuleAssets = []; for (let i = 0; i < createBasketParams.rules.length; i++) { let rule = createBasketParams.rules[i]; let ruleTokens = selectTokens(createBasketParams, rule, tokenStats); basketState.singleRuleAssets.push(ruleTokens); } } function calculateWeight(rawWeight, expo, weightBy) { let multiplier = 1; if (weightBy == 3) multiplier = config_1.WEIGHT_MULTIPLIER; return Math.floor(Math.pow(rawWeight / multiplier, expo / config_1.EXPO_DIVIDER) * multiplier); } function generateWeights(tokenStats, rule, ruleAssets) { let ruleWeights = []; for (let i = 0; i < rule.numAssets; i++) { let token = ruleAssets[i]; let tokenStatsInfo = tokenStats.stats[token][rule.weightDays]; let rawWeight = 1000; if (rule.filterBy != 0) { switch (rule.weightBy) { case 1: rawWeight = tokenStatsInfo.mcap; break; case 2: rawWeight = tokenStatsInfo.volume; break; default: rawWeight = tokenStatsInfo.performance; } } let assetWeight = calculateWeight(rawWeight, rule.weightExpo, rule.weightBy); ruleWeights.push({ token, assetWeight }); } return ruleWeights; } function combineRules(basketState, ruleAssets, createBasketParams) { basketState.ruleAssets = new Array(20).fill(0); basketState.ruleWeights = new Array(20).fill(0); basketState.numRuleTokens = 1; for (let i = 0; i < createBasketParams.rules.length; i++) { let sumOfWeights = 0; for (let j = 0; j < ruleAssets[i].length; j++) sumOfWeights += ruleAssets[i][j].assetWeight; for (let j = 0; j < ruleAssets[i].length; j++) { let token = ruleAssets[i][j].token; let optionIndex = basketState.ruleAssets.indexOf(token); let index = 0; switch (optionIndex) { case -1: basketState.ruleAssets[basketState.numRuleTokens] = token; basketState.numRuleTokens += 1; index = basketState.numRuleTokens - 1; break; default: index = optionIndex; } let x = BigInt(config_1.WEIGHT_MULTIPLIER) * BigInt(ruleAssets[i][j].assetWeight) * BigInt(createBasketParams.rules[i].totalWeight) / BigInt(sumOfWeights); let weight = Math.floor(Number(x)); basketState.ruleWeights[index] += weight; } } } function addTargetAssets(basketState) { let i = 0; while (i < basketState.numRuleTokens) { let optionIndex = basketState.currentCompToken.indexOf(basketState.ruleAssets[i]); if (optionIndex != -1) { basketState.targetWeight[optionIndex] = basketState.ruleWeights[i]; } else { if (basketState.numOfTokens < config_1.COMBINED_TOKENS_IN_A_BASKET) { basketState.currentCompAmount[basketState.numOfTokens] = 0; basketState.currentCompToken[basketState.numOfTokens] = basketState.ruleAssets[i]; basketState.targetWeight[basketState.numOfTokens] = basketState.ruleWeights[i]; basketState.numOfTokens += 1; } } i++; } } function removeTargetAssets(basketState) { let i = 1; while (i < basketState.numOfTokens) { let optionIndex = basketState.ruleAssets.indexOf(basketState.currentCompToken[i]); if (optionIndex == -1) { if (basketState.currentCompAmount[i] == 0) { basketState.numOfTokens -= 1; let lastTokenIndex = basketState.numOfTokens; // we write lastToken data on this index basketState.currentCompAmount[i] = basketState.currentCompAmount[lastTokenIndex]; basketState.currentCompToken[i] = basketState.currentCompToken[lastTokenIndex]; basketState.targetWeight[i] = basketState.targetWeight[lastTokenIndex]; // after we moved data of lastToken on index i, we can make everything 0 on lastTokenIndex basketState.targetWeight[lastTokenIndex] = 0; basketState.currentCompToken[lastTokenIndex] = 0; basketState.currentCompAmount[lastTokenIndex] = 0; // since now we have the lastToken data on this index, we shouldn't // increase i in the while loop. because lastToken wasn't processed yet i = i - 1; } else { basketState.targetWeight[i] = 0; } } i++; } } function updateTargetAssets(basketState) { removeTargetAssets(basketState); addTargetAssets(basketState); basketState.weightSum = 0; basketState.ruleWeights.forEach((element) => { basketState.weightSum += element; }); } function reweight(createBasketParams, tokenStats, basketState) { basketState.ruleAssets = new Array(20); basketState.ruleWeights = new Array(20); basketState.weightSum = 0; let ruleAssets = []; for (let i = 0; i < createBasketParams.rules.length; i++) { ruleAssets.push(generateWeights(tokenStats, createBasketParams.rules[i], basketState.singleRuleAssets[i])); } combineRules(basketState, ruleAssets, createBasketParams); updateTargetAssets(basketState); } function rebalance(createBasketParams, basketState, data, day, tokenList) { let sell = []; let buy = []; for (let i = 0; i < basketState.numOfTokens; i++) { let currentPercentage = basketState.currentCompWeight[i]; let targetPercentage = Math.floor(basketState.targetWeight[i] * config_1.WEIGHT_MULTIPLIER / basketState.weightSum); // checking rebalance threshold if (currentPercentage <= Math.floor(targetPercentage * (config_1.BPS_DIVIDER + createBasketParams.rebalanceThreshold) / config_1.BPS_DIVIDER) && currentPercentage >= Math.floor(targetPercentage * (config_1.BPS_DIVIDER - createBasketParams.rebalanceThreshold) / config_1.BPS_DIVIDER)) continue; // swapping if (currentPercentage > targetPercentage) { if (i == 0) continue; sell.push(i); } else { buy.push(i); } } for (let ii = 0; ii < sell.length; ii++) { let i = sell[ii]; let currentPercentage = basketState.currentCompWeight[i]; let targetPercentage = Math.floor(basketState.targetWeight[i] * (config_1.WEIGHT_MULTIPLIER / basketState.weightSum)); let amountToSell = Math.min(Math.floor(basketState.currentCompAmount[i] * (currentPercentage - targetPercentage) / currentPercentage), basketState.currentCompAmount[i]); let toUsdcAmount = Math.floor(amountToSell / 1000 * data[basketState.currentCompToken[i]][day].price * 950 / Math.pow(10, tokenList.list[basketState.currentCompToken[i]].decimals)); basketState.currentCompAmount[i] -= amountToSell; basketState.currentCompAmount[0] += toUsdcAmount; } for (let ii = 0; ii < buy.length; ii++) { let i = buy[ii]; let currentPercentage = basketState.currentCompWeight[i]; let targetPercentage = Math.floor(basketState.targetWeight[i] * (config_1.WEIGHT_MULTIPLIER / basketState.weightSum)); let amountToSpend = Math.min(Math.floor(basketState.basketWorth * ((targetPercentage - currentPercentage) / config_1.WEIGHT_MULTIPLIER)), basketState.currentCompAmount[0]); let toTokenAmount = Math.floor(amountToSpend * 950 * Math.pow(10, tokenList[basketState.currentCompToken[i]].decimals) / data[basketState.currentCompToken[i]][day].price / 1000); basketState.currentCompAmount[i] += toTokenAmount; basketState.currentCompAmount[0] -= amountToSpend; } } function updateBasketState(createBasketParams, basketState, tokenStats, database, day, tokenList) { if (basketState.lastRefilterTime + createBasketParams.refilterInterval <= day * 24 * 60 * 60) { refilter(createBasketParams, tokenStats, basketState); basketState.lastRefilterTime = day * 24 * 60 * 60; } if (basketState.lastReweightTime + createBasketParams.reweightInterval <= day * 24 * 60 * 60) { reweight(createBasketParams, tokenStats, basketState); basketState.lastReweightTime = day * 24 * 60 * 60; } if (basketState.lastRebalanceTime + createBasketParams.rebalanceInterval <= day * 24 * 60 * 60) { updateCurrentWeights(database, day, basketState, tokenList); rebalance(createBasketParams, basketState, database, day, tokenList); basketState.lastRebalanceTime = day * 24 * 60 * 60; } } function updateCurrentWeights(data, day, basketState, tokenList) { basketState.basketWorth = 0; for (let i = 0; i < config_1.COMBINED_TOKENS_IN_A_BASKET; i++) { basketState.basketWorth += Math.floor(basketState.currentCompAmount[i] * data[basketState.currentCompToken[i]][day].price / Math.pow(10, tokenList[basketState.currentCompToken[i]].decimals)); } for (let i = 0; i < config_1.COMBINED_TOKENS_IN_A_BASKET; i++) { let x = BigInt(config_1.WEIGHT_MULTIPLIER) * BigInt(basketState.currentCompAmount[i]) * BigInt(data[basketState.currentCompToken[i]][day].price) / BigInt(basketState.basketWorth) / BigInt(Math.pow(10, tokenList[basketState.currentCompToken[i]].decimals)); basketState.currentCompWeight[i] = Math.floor(Number(x)); } } function simulate(program, database, tokenList, createBasketParams, simulationDays) { return __awaiter(this, void 0, void 0, function* () { if (simulationDays > 365) simulationDays = 365; let data = yield fetchDatabase(program, database); let tokenListData = yield program.account.tokenList.fetch(tokenList, "confirmed"); let basketState = yield defaultBasketState(); let simulationData = []; for (let i = config_1.NUM_OF_DAYS_IN_DATABASE - simulationDays; i < config_1.NUM_OF_DAYS_IN_DATABASE; i++) { let tokenStats = yield updateTokenStats(data, i); yield updateCurrentWeights(data, i, basketState, tokenListData); yield updateBasketState(createBasketParams, basketState, tokenStats, data, i, tokenListData); simulationData.push({ price: basketState.basketWorth, currentCompAmount: Object.assign([], basketState.currentCompAmount), currentCompToken: Object.assign([], basketState.currentCompToken), }); } return simulationData; }); }