mirror of
https://github.com/tig-pool-nk/tig-monorepo.git
synced 2026-02-21 17:37:21 +08:00
Add script to calc apy.
This commit is contained in:
parent
74c31623b9
commit
2364377174
112
tig-benchmarker/calc_apy.py
Normal file
112
tig-benchmarker/calc_apy.py
Normal file
@ -0,0 +1,112 @@
|
||||
import numpy as np
|
||||
import json
|
||||
import requests
|
||||
from common.structs import *
|
||||
from common.calcs import *
|
||||
|
||||
API_URL = "https://mainnet-api.tig.foundation"
|
||||
|
||||
deposit = input("Enter deposit in TIG (leave blank to fetch deposit from your player_id): ")
|
||||
if deposit != "":
|
||||
lock_period = input("Enter number of weeks to lock (longer lock will have higher APY): ")
|
||||
deposit = float(deposit)
|
||||
lock_period = float(lock_period)
|
||||
player_id = None
|
||||
else:
|
||||
player_id = input("Enter player_id: ").lower()
|
||||
|
||||
print("Fetching data...")
|
||||
block = Block.from_dict(requests.get(f"{API_URL}/get-block?include_data").json()["block"])
|
||||
opow_data = {
|
||||
x["player_id"]: OPoW.from_dict(x)
|
||||
for x in requests.get(f"{API_URL}/get-opow?block_id={block.id}").json()["opow"]
|
||||
}
|
||||
|
||||
factors = {
|
||||
benchmarker: {
|
||||
**{
|
||||
f: opow_data[benchmarker].block_data.num_qualifiers_by_challenge.get(f, 0)
|
||||
for f in block.data.active_ids["challenge"]
|
||||
},
|
||||
"weighted_deposit": opow_data[benchmarker].block_data.delegated_weighted_deposit.to_float()
|
||||
}
|
||||
for benchmarker in opow_data
|
||||
}
|
||||
|
||||
if player_id is None:
|
||||
blocks_till_round_ends = block.config["rounds"]["blocks_per_round"] - (block.details.height % block.config["rounds"]["blocks_per_round"])
|
||||
seconds_till_round_ends = blocks_till_round_ends * block.config["rounds"]["seconds_between_blocks"]
|
||||
weighted_deposit = calc_weighted_deposit(deposit, seconds_till_round_ends, lock_period * 604800)
|
||||
else:
|
||||
player_data = Player.from_dict(requests.get(f"{API_URL}/get-player-data?player_id={player_id}&block_id={block.id}").json()["player"])
|
||||
deposit = sum(x.to_float() for x in player_data.block_data.deposit_by_locked_period)
|
||||
weighted_deposit = player_data.block_data.weighted_deposit.to_float()
|
||||
for delegatee, fraction in player_data.block_data.delegatees.items():
|
||||
factors[delegatee]["weighted_deposit"] -= fraction * weighted_deposit
|
||||
|
||||
total_factors = {
|
||||
f: sum(factors[benchmarker][f] for benchmarker in opow_data)
|
||||
for f in list(block.data.active_ids["challenge"]) + ["weighted_deposit"]
|
||||
}
|
||||
reward_shares = {
|
||||
benchmarker: opow_data[benchmarker].block_data.reward_share
|
||||
for benchmarker in opow_data
|
||||
}
|
||||
|
||||
print("Optimising delegation by splitting into 100 chunks...")
|
||||
chunk = weighted_deposit / 100
|
||||
delegate = {}
|
||||
for i in range(100):
|
||||
print(f"Chunk {i + 1}: simulating delegation...")
|
||||
total_factors["weighted_deposit"] += chunk
|
||||
if len(delegate) == 10:
|
||||
potential_delegatees = list(delegate)
|
||||
else:
|
||||
potential_delegatees = [benchmarker for benchmarker in opow_data if opow_data[benchmarker].block_data.self_deposit.to_float() >= 10000]
|
||||
highest_apy_benchmarker = max(
|
||||
potential_delegatees,
|
||||
key=lambda delegatee: (
|
||||
calc_influence({
|
||||
benchmarker: {
|
||||
f: (factors[benchmarker][f] + chunk * (benchmarker == delegatee and f == "weighted_deposit")) / total_factors[f]
|
||||
for f in total_factors
|
||||
}
|
||||
for benchmarker in opow_data
|
||||
}, block.config["opow"])[delegatee] *
|
||||
reward_shares[delegatee] * chunk / (factors[delegatee]["weighted_deposit"] + chunk)
|
||||
)
|
||||
)
|
||||
print(f"Chunk {i + 1}: best delegatee is {highest_apy_benchmarker}")
|
||||
if highest_apy_benchmarker not in delegate:
|
||||
delegate[highest_apy_benchmarker] = 0
|
||||
delegate[highest_apy_benchmarker] += 1
|
||||
factors[highest_apy_benchmarker]["weighted_deposit"] += chunk
|
||||
|
||||
influences = calc_influence({
|
||||
benchmarker: {
|
||||
f: factors[benchmarker][f] / total_factors[f]
|
||||
for f in total_factors
|
||||
}
|
||||
for benchmarker in opow_data
|
||||
}, block.config["opow"])
|
||||
print("")
|
||||
|
||||
|
||||
print("Optimised delegation split:")
|
||||
reward_pool = block.config["rewards"]["distribution"]["opow"] * next(x["block_reward"] for x in reversed(block.config["rewards"]["schedule"]) if x["round_start"] <= block.details.round)
|
||||
deposit_chunk = deposit / 100
|
||||
total_reward = 0
|
||||
for delegatee, num_chunks in delegate.items():
|
||||
share_fraction = influences[delegatee] * reward_shares[delegatee] * (num_chunks * chunk) / factors[delegatee]["weighted_deposit"]
|
||||
reward = share_fraction * reward_pool
|
||||
total_reward += reward
|
||||
apy = reward * block.config["rounds"]["blocks_per_round"] * 52 / (num_chunks * deposit_chunk)
|
||||
print(f"{delegatee}: %delegated = {num_chunks}%, apy = {apy * 100:.2f}%")
|
||||
|
||||
print(f"average_apy = {total_reward * 10080 * 52 / deposit * 100:.2f}% on your deposit of {deposit} TIG")
|
||||
|
||||
print("")
|
||||
print("To set this delegation split, run the following command:")
|
||||
req = {"delegatees": {k: v / 100 for k, v in delegate.items()}}
|
||||
print("API_KEY=<YOUR API KEY HERE>")
|
||||
print(f"curl -H \"X-Api-Key: $API_KEY\" -X POST -d '{json.dumps(req)}' {API_URL}/set-delegatees")
|
||||
@ -1,68 +1,10 @@
|
||||
import numpy as np
|
||||
import json
|
||||
import requests
|
||||
from typing import List, Dict
|
||||
from common.structs import *
|
||||
from common.calcs import *
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def calc_influence(fractions, opow_config) -> Dict[str, float]:
|
||||
benchmarkers = list(fractions)
|
||||
factors = list(next(iter(fractions.values())))
|
||||
num_challenges = len(factors) - 1
|
||||
avg_qualifier_fractions = {
|
||||
benchmarker: sum(
|
||||
fractions[benchmarker][f]
|
||||
for f in factors
|
||||
if f != "weighted_deposit"
|
||||
) / num_challenges
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
deposit_fraction_cap = {
|
||||
benchmarker: avg_qualifier_fractions[benchmarker] * opow_config["max_deposit_to_qualifier_ratio"]
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
capped_fractions = {
|
||||
benchmarker: {
|
||||
**fractions[benchmarker],
|
||||
"weighted_deposit": min(
|
||||
fractions[benchmarker]["weighted_deposit"],
|
||||
deposit_fraction_cap[benchmarker]
|
||||
)
|
||||
}
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
avg_fraction = {
|
||||
benchmarker: np.mean(list(capped_fractions[benchmarker].values()))
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
var_fraction = {
|
||||
benchmarker: np.var(list(capped_fractions[benchmarker].values()))
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
imbalance = {
|
||||
benchmarker: (var_fraction[benchmarker] / np.square(avg_fraction[benchmarker]) / num_challenges) if avg_fraction[benchmarker] > 0 else 0
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
imbalance_penalty = {
|
||||
benchmarker: 1.0 - np.exp(-opow_config["imbalance_multiplier"] * imbalance[benchmarker])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
weighted_avg_fraction = {
|
||||
benchmarker: ((avg_qualifier_fractions[benchmarker] * num_challenges) + capped_fractions[benchmarker]["weighted_deposit"] * opow_config["deposit_multiplier"]) / (num_challenges + opow_config["deposit_multiplier"])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
unormalised_influence = {
|
||||
benchmarker: weighted_avg_fraction[benchmarker] * (1.0 - imbalance_penalty[benchmarker])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
total = sum(unormalised_influence.values())
|
||||
influence = {
|
||||
benchmarker: unormalised_influence[benchmarker] / total
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
return influence
|
||||
|
||||
API_URL = "https://mainnet-api.tig.foundation"
|
||||
|
||||
player_id = input("Enter player_id: ").lower()
|
||||
@ -140,12 +82,12 @@ print(f"reward = {reward['only_self_deposit']:.4f} TIG per block")
|
||||
print("")
|
||||
print("Scenario 2 (current self + delegated deposit)")
|
||||
print(f"%weighted_deposit = {fractions[player_id]['weighted_deposit'] * 100:.2f}%")
|
||||
print(f"reward = {reward['current']:.4f} TIG per block ({reward['current'] / reward['only_self_deposit'] * 100 - 100:.2f}% difference*)")
|
||||
print(f"reward = {reward['current']:.4f} TIG per block (recommended max reward_share = {100 - reward['only_self_deposit'] / reward['current'] * 100:.2f}%*)")
|
||||
print("")
|
||||
print(f"Scenario 3 (self + delegated deposit at parity)")
|
||||
print(f"%weighted_deposit = average %qualifiers = {average_fraction_of_qualifiers * 100:.2f}%")
|
||||
print(f"reward = {reward['at_parity']:.4f} TIG per block ({reward['at_parity'] / reward['only_self_deposit'] * 100 - 100:.2f}% difference*)")
|
||||
print(f"reward = {reward['at_parity']:.4f} TIG per block (recommended max reward_share = {100 - reward['only_self_deposit'] / reward['at_parity'] * 100:.2f}%*)")
|
||||
print("")
|
||||
print("*These are percentage differences in reward compared with relying only on self-deposit (Scenario 1).")
|
||||
print("*Recommend not setting reward_share above the max. You will not benefit from delegation (earn the same as Scenario 1 with zero delegation).")
|
||||
print("")
|
||||
print("Note: the imbalance penalty is such that your reward increases at a high rate when moving up to parity")
|
||||
102
tig-benchmarker/common/calcs.py
Normal file
102
tig-benchmarker/common/calcs.py
Normal file
@ -0,0 +1,102 @@
|
||||
import numpy as np
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
def calc_influence(fractions: Dict[str, Dict[str, float]], opow_config: dict) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate the influence of each benchmarker based on their fractions and the OPoW configuration.
|
||||
|
||||
Args:
|
||||
fractions: A dictionary of dictionaries, mapping benchmarker_ids to their fraction of each factor (challenges & weighted_deposit).
|
||||
opow_config: A dictionary containing configuration parameters for the calculation.
|
||||
|
||||
Returns:
|
||||
Dict[str, float]: A dictionary mapping each benchmarker_id to their calculated influence.
|
||||
"""
|
||||
benchmarkers = list(fractions)
|
||||
factors = list(next(iter(fractions.values())))
|
||||
num_challenges = len(factors) - 1
|
||||
avg_qualifier_fractions = {
|
||||
benchmarker: sum(
|
||||
fractions[benchmarker][f]
|
||||
for f in factors
|
||||
if f != "weighted_deposit"
|
||||
) / num_challenges
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
deposit_fraction_cap = {
|
||||
benchmarker: avg_qualifier_fractions[benchmarker] * opow_config["max_deposit_to_qualifier_ratio"]
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
capped_fractions = {
|
||||
benchmarker: {
|
||||
**fractions[benchmarker],
|
||||
"weighted_deposit": min(
|
||||
fractions[benchmarker]["weighted_deposit"],
|
||||
deposit_fraction_cap[benchmarker]
|
||||
)
|
||||
}
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
avg_fraction = {
|
||||
benchmarker: np.mean(list(capped_fractions[benchmarker].values()))
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
var_fraction = {
|
||||
benchmarker: np.var(list(capped_fractions[benchmarker].values()))
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
imbalance = {
|
||||
benchmarker: (var_fraction[benchmarker] / np.square(avg_fraction[benchmarker]) / num_challenges) if avg_fraction[benchmarker] > 0 else 0
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
imbalance_penalty = {
|
||||
benchmarker: 1.0 - np.exp(-opow_config["imbalance_multiplier"] * imbalance[benchmarker])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
weighted_avg_fraction = {
|
||||
benchmarker: ((avg_qualifier_fractions[benchmarker] * num_challenges) + capped_fractions[benchmarker]["weighted_deposit"] * opow_config["deposit_multiplier"]) / (num_challenges + opow_config["deposit_multiplier"])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
unormalised_influence = {
|
||||
benchmarker: weighted_avg_fraction[benchmarker] * (1.0 - imbalance_penalty[benchmarker])
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
total = sum(unormalised_influence.values())
|
||||
influence = {
|
||||
benchmarker: unormalised_influence[benchmarker] / total
|
||||
for benchmarker in benchmarkers
|
||||
}
|
||||
return influence
|
||||
|
||||
|
||||
def calc_weighted_deposit(deposit: float, seconds_till_round_end: int, lock_seconds: int) -> float:
|
||||
"""
|
||||
Calculate weighted deposit
|
||||
|
||||
Args:
|
||||
deposit: Amount to deposit
|
||||
seconds_till_round_end: Seconds remaining in current round
|
||||
lock_seconds: Total lock duration in seconds
|
||||
|
||||
Returns:
|
||||
Weighted deposit
|
||||
"""
|
||||
weighted_deposit = 0
|
||||
|
||||
if lock_seconds <= 0:
|
||||
return weighted_deposit
|
||||
|
||||
# Calculate first chunk (partial week)
|
||||
weighted_deposit += deposit * min(seconds_till_round_end, lock_seconds) // lock_seconds
|
||||
|
||||
remaining_seconds = lock_seconds - min(seconds_till_round_end, lock_seconds)
|
||||
weight = 2
|
||||
while remaining_seconds > 0:
|
||||
chunk_seconds = min(remaining_seconds, 604800)
|
||||
chunk = deposit * chunk_seconds // lock_seconds
|
||||
weighted_deposit += chunk * weight
|
||||
remaining_seconds -= chunk_seconds
|
||||
weight = min(weight + 1, 26)
|
||||
|
||||
return weighted_deposit
|
||||
@ -201,6 +201,7 @@ class OPoWBlockData(FromDict):
|
||||
num_qualifiers_by_challenge: Dict[str, int]
|
||||
cutoff: int
|
||||
delegated_weighted_deposit: PreciseNumber
|
||||
self_deposit: PreciseNumber
|
||||
delegators: Set[str]
|
||||
reward_share: float
|
||||
imbalance: PreciseNumber
|
||||
@ -221,13 +222,13 @@ class PlayerDetails(FromDict):
|
||||
class PlayerState(FromDict):
|
||||
total_fees_paid: PreciseNumber
|
||||
available_fee_balance: PreciseNumber
|
||||
delegatee: Optional[dict]
|
||||
delegatees: Optional[dict]
|
||||
votes: dict
|
||||
reward_share: Optional[dict]
|
||||
|
||||
@dataclass
|
||||
class PlayerBlockData(FromDict):
|
||||
delegatee: Optional[str]
|
||||
delegatees: Dict[str, float]
|
||||
reward_by_type: Dict[str, PreciseNumber]
|
||||
deposit_by_locked_period: List[PreciseNumber]
|
||||
weighted_deposit: PreciseNumber
|
||||
|
||||
Loading…
Reference in New Issue
Block a user