def _get_effective_share_info(self, share_token_items, total_share_token_amount, db_session): effective_share_dict = {} position_holder_dict = {} share_token_dict = {} total_effective_share_amount = Decimal(0) for item in share_token_items: share_token_dict[item.holder] = item.balance token_map = self._get_token_map(db_session) perp_addr = token_map[self._share_token_address].get('perp_addr') amm_proxy_addr = token_map[self._share_token_address].get('amm_proxy_addr') position_items = db_session.query(PositionBalance)\ .filter(PositionBalance.perpetual_address == perp_addr)\ .all() for item in position_items: position_holder_dict[item.holder] = item.balance amm_position = position_holder_dict[amm_proxy_addr] for holder, holder_position_in_margin_account in position_holder_dict.items(): holder_share_token_amount = share_token_dict.get(holder) if holder_share_token_amount == Decimal(0) or holder_share_token_amount is None: continue holder_position_in_amm = Wad.from_number(amm_position) * Wad.from_number(holder_share_token_amount) / Wad.from_number(total_share_token_amount) holder_portfolio_position = holder_position_in_amm + Wad.from_number(holder_position_in_margin_account) imbalance_rate = abs(holder_portfolio_position / holder_position_in_amm) imbalance_rate = Decimal(str(imbalance_rate)) if imbalance_rate <= Decimal(0.1): holder_effective_share = holder_share_token_amount elif imbalance_rate >= Decimal(0.9): holder_effective_share = holder_share_token_amount * Decimal(0.1) else: holder_effective_share = holder_share_token_amount * (Decimal(89/80) - imbalance_rate * Decimal(9/8)) effective_share_dict[holder] = holder_effective_share total_effective_share_amount += holder_effective_share return effective_share_dict, total_effective_share_amount
def _get_keeper_liquidate_amount(self, keeper_account): markPrice = self.perp.markPrice() availableMargin = self.perp.getAvailableMargin(keeper_account) if config.INVERSE: markPrice = Wad.from_number(1)/markPrice amount = int(availableMargin * Wad.from_number(config.LEVERAGE) * markPrice) amount = math.floor(amount/config.LOT_SIZE)*config.LOT_SIZE return Wad.from_number(amount)
def _get_holder_reward_weight(self, block_number, pool_value_info, db_session): holder_amms_weight_dict = {} holder_amms_reward_dict = {} amms_pool_total_reward = Decimal(0) for pool_name in pool_value_info.keys(): total_share_token_amount = pool_value_info[pool_name][ 'total_share_token_amount'] if total_share_token_amount == 0: continue share_token_items = pool_value_info[pool_name]['share_token_items'] pool_type = pool_value_info[pool_name]['pool_type'] pool_reward = pool_value_info[pool_name]['pool_reward'] amms_pool_total_reward += Decimal(str(pool_reward)) for item in share_token_items: holder = item.holder if item.balance == Decimal(0): continue # amm pool, use effective share after xia_rebalance_hard_fork block number if pool_type == 'AMM' and block_number >= self._xia_rebalance_hard_fork_block_number: total_effective_share_amount = pool_value_info[pool_name][ 'total_effective_share_amount'] holder_effective_share_amount = pool_value_info[pool_name][ 'effective_share_dict'].get(holder, Decimal(0)) wad_reward = pool_reward * Wad.from_number( holder_effective_share_amount) / Wad.from_number( total_effective_share_amount) reward = Decimal(str(wad_reward)) if holder not in holder_amms_reward_dict: holder_amms_reward_dict[holder] = reward else: holder_amms_reward_dict[holder] += reward self._save_theory_mining_reward('AMM', holder_amms_reward_dict, db_session) holder_mcb_balance_dict = self._get_holder_mcb_balance(db_session) total_holder_reward_weight = Decimal(0) for holder, reward in holder_amms_reward_dict.items(): holder_reward_percent = reward / amms_pool_total_reward holder_mcb_balance = holder_mcb_balance_dict.get( holder, Decimal(0)) reward_factor = self._get_holder_reward_factor( holder, reward, holder_mcb_balance) total_holder_reward_weight += holder_reward_percent * reward_factor for holder, reward in holder_amms_reward_dict.items(): holder_mcb_balance = holder_mcb_balance_dict.get( holder, Decimal(0)) reward_factor = self._get_holder_reward_factor( holder, reward, holder_mcb_balance) holder_amms_weight_dict[ holder] = reward_factor / total_holder_reward_weight return holder_amms_weight_dict
def disperse_token(self, token: Address, addresses: list, amounts: list, user: Address, gasPrice: int): amounts_toWad = [] for amount in amounts: amounts_toWad.append(Wad.from_number(amount).value) tx_hash = self.contract.functions.disperseToken( token.address, addresses, amounts_toWad).transact({ 'from': user.address, 'gasPrice': gasPrice }) return tx_hash
def _get_pool_usd_value(self, block_number, pool_name, pool_share_token_address, inverse, db_session): amm_usd_value = Decimal(0) amm_position = Decimal(0) token_map = self._get_token_map(pool_share_token_address, db_session) perp_addr = token_map[pool_share_token_address].get('perp_addr') amm_proxy_addr = token_map[pool_share_token_address].get( 'amm_proxy_addr') amm_proxy_item = db_session.query(PositionBalance)\ .filter(PositionBalance.perpetual_address == perp_addr)\ .filter(PositionBalance.holder == amm_proxy_addr)\ .first() if amm_proxy_item: amm_position = amm_proxy_item.balance if inverse: amm_usd_value = abs(Wad.from_number(amm_position)) else: chain_link_price = self._get_chain_link_price( block_number, pool_name, db_session) # vanilla contract amm_usd_value = abs( Wad.from_number(amm_position) * Wad.from_number(chain_link_price)) return amm_usd_value
def _close_position_in_AMM(self): margin_account = self.perp.getMarginAccount(self.keeper_account) size = int(margin_account.size) if size < config.POSITION_LIMIT: return deadline = int(time.time()) + config.DEADLINE amm_available_margin = self.AMM.current_available_margin() self.logger.info(f"amm_available_margin:{amm_available_margin}") amm_position_size = self.AMM.position_size() self.logger.info(f"amm_position_size:{amm_position_size}") trade_side = PositionSide.LONG if margin_account.side == PositionSide.SHORT else PositionSide.SHORT try: trade_price = compute_AMM_price(amm_available_margin, amm_position_size, trade_side, margin_account.size) self.logger.info(f"compute_price:{trade_price}") except Exception as e: self.logger.fatal(f"compute amm price failed. error:{e}") return trade_price = trade_price*Wad.from_number(1 - config.PRICE_SLIPPAGE) if trade_side == PositionSide.SHORT else trade_price*Wad.from_number(1 + config.PRICE_SLIPPAGE) tx_hash = None try: if trade_side == PositionSide.LONG: tx_hash = self.AMM.buy(margin_account.size, trade_price, deadline, self.keeper_account, self.gas_price) else: tx_hash = self.AMM.sell(margin_account.size, trade_price, deadline, self.keeper_account, self.gas_price) self.logger.info(f"close in AMM success. price:{trade_price} size{margin_account.size}") # wait transaction times is 1, cause amm transaction deadline is 120s, if wait timeout, transaction will fail, no need to add gas price transaction_status = self._wait_transaction_receipt(tx_hash, 1) if transaction_status: self.logger.info(f"close position in AMM success. price:{trade_price} size:{margin_account.size}") else: self.logger.info(f"close position in AMM fail. price:{trade_price} amount:{margin_account.size}") except Exception as e: self.logger.fatal(f"close position in AMM failed. price:{trade_price} size:{margin_account.size} error:{e}")
def sync(self, watcher_id, block_number, block_hash, db_session): """Sync data""" if block_number < self._begin_block or block_number > self._end_block: self._logger.info(f'block_number {block_number} not in mining window!') return result = db_session.query(TokenBalance)\ .filter(TokenBalance.token == self._share_token_address)\ .with_entities( func.sum(TokenBalance.balance) ).first() if result[0] is None: self._logger.warning(f'opps, token_balance is empty!') return total_share_token_amount = result[0] # get all immature summary items immature_summary_dict = {} immature_summary_items = db_session.query(ImmatureMiningRewardSummary)\ .filter(ImmatureMiningRewardSummary.mining_round == self._mining_round)\ .all() for item in immature_summary_items: immature_summary_dict[item.holder] = item share_token_items = db_session.query(TokenBalance)\ .filter(TokenBalance.token == self._share_token_address)\ .with_entities( TokenBalance.holder, TokenBalance.balance ).all() self._logger.info(f'sync mining reward, block_number:{block_number}, holders:{len(share_token_items)}') # check rebalance_hard_fork block number if block_number >= self._rebalance_hard_fork_block_number: effective_share_dict, total_effective_share_amount = self._get_effective_share_info(share_token_items, total_share_token_amount, db_session) for item in share_token_items: holder = item.holder if block_number >= self._rebalance_hard_fork_block_number: holder_effective_share_amount = effective_share_dict.get(holder, Decimal(0)) wad_reward = Wad.from_number(self._reward_per_block) * Wad.from_number(holder_effective_share_amount) / Wad.from_number(total_effective_share_amount) reward = Decimal(str(wad_reward)) else: holder_share_token_amount = Decimal(item.balance) wad_reward = Wad.from_number(self._reward_per_block) * Wad.from_number(holder_share_token_amount) / Wad.from_number(total_share_token_amount) reward = Decimal(str(wad_reward)) immature_mining_reward = ImmatureMiningReward() immature_mining_reward.block_number = block_number immature_mining_reward.mining_round = self._mining_round immature_mining_reward.holder = holder immature_mining_reward.mcb_balance = reward db_session.add(immature_mining_reward) # update immature_mining_reward_summaries table, simulated materialized view if holder not in immature_summary_dict.keys(): immature_summary_item = ImmatureMiningRewardSummary() immature_summary_item.mining_round = self._mining_round immature_summary_item.holder = holder immature_summary_item.mcb_balance = reward else: immature_summary_item = immature_summary_dict[holder] immature_summary_item.mcb_balance += reward db_session.add(immature_summary_item)
def _get_calculate_liquidate_amount(self, address): markPrice = self.perp.markPrice() cal_amount = int(self.perp.calculateLiquidateAmount(address, markPrice)) cal_amount = math.ceil(cal_amount/config.LOT_SIZE)*config.LOT_SIZE return Wad.from_number(cal_amount)
def _get_effective_share_info(self, block_number, pool_share_token_address, share_token_items, total_share_token_amount, db_session): effective_share_dict = {} position_holder_dict = {} share_token_dict = {} total_effective_share_amount = Decimal(0) for item in share_token_items: share_token_dict[item.holder] = item.balance if block_number >= self._zhou_begin_block_number: # period from ZHOU, pool_effective_usd_value is pool_usd_value effective_share_dict = share_token_dict total_effective_share_amount = total_share_token_amount return effective_share_dict, total_effective_share_amount token_map = self._get_token_map(pool_share_token_address, db_session) perp_addr = token_map[pool_share_token_address].get('perp_addr') amm_proxy_addr = token_map[pool_share_token_address].get( 'amm_proxy_addr') position_items = db_session.query(PositionBalance)\ .filter(PositionBalance.perpetual_address == perp_addr)\ .all() for item in position_items: position_holder_dict[item.holder] = item.balance amm_position = position_holder_dict.get(amm_proxy_addr, Decimal(0)) for holder, holder_position_in_margin_account in position_holder_dict.items( ): holder_share_token_amount = share_token_dict.get(holder) if holder_share_token_amount == Decimal( 0) or holder_share_token_amount is None: continue holder_position_in_amm = Wad.from_number( amm_position) * Wad.from_number( holder_share_token_amount) / Wad.from_number( total_share_token_amount) holder_portfolio_position = holder_position_in_amm + Wad.from_number( holder_position_in_margin_account) imbalance_rate = abs(holder_portfolio_position / holder_position_in_amm) imbalance_rate = Decimal(str(imbalance_rate)) if self._mining_round == 'XIA': if imbalance_rate <= Decimal(0.1): holder_effective_share = holder_share_token_amount elif imbalance_rate >= Decimal(0.9): holder_effective_share = holder_share_token_amount * Decimal( 0.1) else: holder_effective_share = holder_share_token_amount * ( Decimal(89 / 80) - imbalance_rate * Decimal(9 / 8)) elif self._mining_round == 'SHANG': if imbalance_rate <= Decimal(0.2): holder_effective_share = holder_share_token_amount elif imbalance_rate >= Decimal(0.9): holder_effective_share = holder_share_token_amount * Decimal( 0.1) else: holder_effective_share = holder_share_token_amount * ( Decimal(44 / 35) - imbalance_rate * Decimal(9 / 7)) effective_share_dict[holder] = holder_effective_share total_effective_share_amount += holder_effective_share return effective_share_dict, total_effective_share_amount
def _calculate_pools_reward(self, block_number, pool_info, pool_reward_percent, db_session): pool_value_info = self._get_pool_value_info(block_number, pool_info, pool_reward_percent, db_session) self._logger.info( f'sync mining reward, block_number:{block_number}, pools:{",".join(pool_info.keys())}' ) holder_amms_weight_dict = {} holder_weight_dict = {} if block_number >= self._qin_begin_block_number: holder_amms_weight_dict = self._get_holder_amms_reward_weight( block_number, pool_value_info, db_session) elif block_number >= self._zhou_begin_block_number: holder_weight_dict = self._get_holder_reward_weight( block_number, pool_value_info, db_session) for pool_name in pool_value_info.keys(): if block_number >= self._qin_begin_block_number: holder_weight_dict = holder_amms_weight_dict.get(pool_name, {}) # get all immature summary items of pool_name immature_summary_dict = {} immature_summary_items = db_session.query(ImmatureMiningRewardSummary)\ .filter(ImmatureMiningRewardSummary.mining_round == self._mining_round)\ .filter(ImmatureMiningRewardSummary.pool_name == pool_name)\ .all() for item in immature_summary_items: immature_summary_dict[item.holder] = item total_share_token_amount = pool_value_info[pool_name][ 'total_share_token_amount'] if total_share_token_amount == 0: self._logger.warning( f'opps, pool:{pool_name}, share_token total amount is zero, skip it!' ) continue share_token_items = pool_value_info[pool_name]['share_token_items'] pool_type = pool_value_info[pool_name]['pool_type'] pool_reward = pool_value_info[pool_name]['pool_reward'] for item in share_token_items: holder = item.holder if item.balance == Decimal(0): continue # amm pool, use effective share after xia_rebalance_hard_fork block number if pool_type == 'AMM' and block_number >= self._xia_rebalance_hard_fork_block_number: holder_weight = holder_weight_dict.get(holder, Decimal(1)) total_effective_share_amount = pool_value_info[pool_name][ 'total_effective_share_amount'] holder_effective_share_amount = pool_value_info[pool_name][ 'effective_share_dict'].get(holder, Decimal(0)) wad_reward = Wad.from_number( holder_weight) * pool_reward * Wad.from_number( holder_effective_share_amount) / Wad.from_number( total_effective_share_amount) reward = Decimal(str(wad_reward)) else: holder_share_token_amount = Decimal(item.balance) wad_reward = pool_reward * Wad.from_number( holder_share_token_amount) / Wad.from_number( total_share_token_amount) reward = Decimal(str(wad_reward)) immature_mining_reward = ImmatureMiningReward() immature_mining_reward.block_number = block_number immature_mining_reward.pool_name = pool_name immature_mining_reward.mining_round = self._mining_round immature_mining_reward.holder = holder immature_mining_reward.mcb_balance = reward db_session.add(immature_mining_reward) # update immature_mining_reward_summaries table, simulated materialized view if holder not in immature_summary_dict.keys(): immature_summary_item = ImmatureMiningRewardSummary() immature_summary_item.mining_round = self._mining_round immature_summary_item.pool_name = pool_name immature_summary_item.holder = holder immature_summary_item.mcb_balance = reward else: immature_summary_item = immature_summary_dict[holder] immature_summary_item.mcb_balance += reward db_session.add(immature_summary_item)
def _get_pool_value_info(self, block_number, pool_info, pool_reward_percent, db_session): if self._mining_round == 'QIN' and block_number < self._qin_reduce_reward_block_number: self._reward_per_block = 2 elif self._mining_round == 'QIN' and block_number >= self._qin_reduce_reward_block_number: self._reward_per_block = 0.2 # support vote https://vote.mcdex.io/mainnet/proposal/14, blocknumber >=11601000 && blocknumber <11685000 reward 0.1875 elif block_number >= 11601000 and block_number < 11685000: self._reward_per_block = 0.1875 # update uniswap_pool_proportion, every block update self._update_uniswap_pool_proportion(pool_info, db_session) pool_value_info = {} pools_total_effective_value = Wad(0) for pool_name in pool_info.keys(): pool_share_token_address = pool_info[pool_name].get( 'pool_share_token_address') if pool_name not in pool_value_info.keys(): pool_value_info[pool_name] = {} pool_value_info[pool_name][ 'pool_share_token_address'] = pool_share_token_address # use for amm pools reward distribute amm_pool_proportion = pool_info[pool_name].get( 'amm_pool_proportion', 1) pool_value_info[pool_name][ 'amm_pool_proportion'] = amm_pool_proportion # use for uniswap pools reward distribute uniswap_pool_proportion = pool_info[pool_name].get( 'uniswap_pool_proportion', 1) pool_value_info[pool_name][ 'uniswap_pool_proportion'] = uniswap_pool_proportion pool_type = pool_info[pool_name].get('pool_type') pool_contract_inverse = pool_info[pool_name].get( 'pool_contract_inverse', True) pool_value_info[pool_name]['pool_type'] = pool_type total_share_token_amount = self._get_total_share_token_amount( pool_share_token_address, db_session) pool_value_info[pool_name][ 'total_share_token_amount'] = total_share_token_amount share_token_items = self._get_share_token_items( pool_share_token_address, db_session) pool_value_info[pool_name]['share_token_items'] = share_token_items if pool_type == 'AMM' and block_number >= self._xia_rebalance_hard_fork_block_number: # pool_type is AMM, use effective share calc reward: # 1) period XIA and block_number >=_xia_rebalance_hard_fork_block_number; # 2) period SHANG; # 3) FROM period ZHOU, effective share eq to holder share effective_share_dict, total_effective_share_amount = self._get_effective_share_info( block_number, pool_share_token_address, share_token_items, total_share_token_amount, db_session) pool_value_info[pool_name][ 'effective_share_dict'] = effective_share_dict pool_value_info[pool_name][ 'total_effective_share_amount'] = total_effective_share_amount pool_usd_value = self._get_pool_usd_value( block_number, pool_name, pool_share_token_address, pool_contract_inverse, db_session) if total_share_token_amount != 0: pool_effective_usd_value = pool_usd_value * Wad.from_number( total_effective_share_amount) / Wad.from_number( total_share_token_amount) else: self._logger.warning( f'opps, pool:{pool_name}, share_token total amount is zero, skip it!' ) pool_effective_usd_value = Wad(0) pool_value_info[pool_name][ 'pool_effective_usd_value'] = pool_effective_usd_value pools_total_effective_value += pool_effective_usd_value else: # include two case: # 1) pool_type is UNISWAP; # 2) pool_type is AMM and block number before _xia_rebalance_hard_fork_block_number; pool_reward = Wad.from_number( pool_reward_percent) * Wad.from_number( self._reward_per_block) * Wad.from_number( uniswap_pool_proportion) pool_value_info[pool_name]['pool_reward'] = pool_reward # update AMM pool reward if pool_type == 'AMM' and block_number >= self._xia_rebalance_hard_fork_block_number: for pool_name in pool_value_info.keys(): amm_pool_proportion = pool_value_info[pool_name].get( 'amm_pool_proportion', 1) pool_effective_usd_value = pool_value_info[pool_name][ 'pool_effective_usd_value'] if block_number >= self._qin_begin_block_number: pool_reward = Wad.from_number( pool_reward_percent) * Wad.from_number( self._reward_per_block) * Wad.from_number( amm_pool_proportion) else: pool_reward = Wad.from_number( pool_reward_percent) * Wad.from_number( self._reward_per_block ) * pool_effective_usd_value / Wad.from_number( pools_total_effective_value) pool_value_info[pool_name]['pool_reward'] = pool_reward return pool_value_info