Created
November 13, 2022 00:28
-
-
Save ilap/42864638691c7a956ac39589f8068a46 to your computer and use it in GitHub Desktop.
Cardano's RSS Simulation - Pledge Model
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Python implementation of Lars's Haskell based `pledge_model`. | |
# https://github.com/brunjlar/pledging-model | |
# | |
# Install: | |
# python -m pip install numpy argparse | |
import random | |
import numpy as np | |
import argparse | |
def add_script_arguments(parser): | |
parser.add_argument("--n", nargs="?", type=int, default=3500, help="The number of players (natural number). Default is 3500.") | |
parser.add_argument("--k", nargs="+", type=int, default=100, help="The desired nr. of pools (natural number). Default is 500.") | |
parser.add_argument("--epoch", nargs="+", type=int, default=5, help="Days per epoch (natural number). Default is 5.") | |
parser.add_argument("--ada-total", nargs="+", type=int, default=31112483745, help="Total ADA in circulation (default: 31112483745)") | |
parser.add_argument("--ada-supply", nargs="+", type=int, default=45000000000, help="The max supply of ADA (default: 45000000000)") | |
parser.add_argument("--expansion", nargs="+", type=float, default=0.0012, help="The monetary expansion per epoch (default: 0.0012)") | |
parser.add_argument("--treasury", nargs="+", type=float, default=0.1, help="The treasury ratio (default: 0.1)") | |
parser.add_argument("--rate", nargs="+", type=float, default=0.5, help="The exchange rate ($/ada) (default: 0.5)") | |
parser.add_argument("--min-cost", nargs="+", type=int, default=0, help="The min cost per year ($) (default: 0)") | |
parser.add_argument("--scale", nargs="+", type=float, default=8684, help="The Weibull scale (default: 8684)") | |
parser.add_argument("--shape", nargs="+", type=float, default=2, help="The Weibull shape (default: 2)") | |
parser.add_argument("--pareto", nargs="+", type=float, default=1.16, help="Pareto alpha (default: 1.16)") | |
parser.add_argument("--whale", nargs="+", type=float, default=0.0005, help="The relative whale threshold (default: 0.0005)") | |
parser.add_argument("--a0", nargs="+", type=float, default=0.3, help="The pledge influence (default: 0.3)") | |
class Player: | |
def __init__(self, stake, cost): | |
self.stake = stake | |
self.cost = cost | |
def __str__(self): | |
return "Player(%f, %0.15f)" % (self.stake, self.cost) | |
class GameConfig: | |
def __init__(self): | |
self.n = 35000 | |
self.k = 200 | |
self.epoch = 5 | |
self.total = 31112483745 | |
self.supply = 45000000000 | |
self.expansion = 0.0012 | |
self.treasury = 0.1 | |
self.rate = 0.08 | |
self.minCostPerYear = 0 | |
self.weibullScale = 8684 | |
self.weibullShape = 2 | |
self.paretoAlpha = 1.16 | |
self.whaleThresholds = 0.0005 | |
self.a0 = 0.3 | |
cfg = GameConfig() | |
def paretoWeight(alpha, w): | |
rng = random.random() | |
# DEBUG: rng = float(w / 100) | |
return round(np.reciprocal(1 - rng) ** np.reciprocal(alpha)) | |
def relativeToDollarsPerYear(r): | |
a = adaRewardsPerEpoch() | |
e = epochsPerYear() | |
x = cfg.rate | |
return r * a * e * x | |
z0 = lambda : 1 / cfg.k | |
stakeToAda = lambda s : s * cfg.total | |
stakeToUsd = lambda s: stakeToAda(s) * cfg.rate | |
def sampleRelCost(): | |
n = cfg.n | |
sc = cfg.weibullScale | |
sh = cfg.weibullShape | |
mc = cfg.minCostPerYear | |
wb = map(lambda x: sc * x, np.random.weibull(sh, n)) | |
cs = map(lambda x :mc if x <= mc else x, wb) | |
return map(dollarsPerYearToRelative, cs) | |
def dollarsPerYearToRelative(d): | |
e = epochsPerYear() | |
r = adaRewardsPerEpoch() | |
a = dollarsToAda(d) | |
return a / e / r | |
epochsPerYear = lambda: 365 / cfg.epoch | |
def adaRewardsPerEpoch(): | |
t = cfg.treasury | |
e = cfg.expansion | |
s = cfg.supply | |
c = cfg.total | |
return (1 - t) * e * (s - c) | |
dollarsToAda = lambda d : d / cfg.rate | |
def satPoolRewards(lam): | |
z = 1 / cfg.k #z0() | |
a = cfg.a0 | |
l = min(lam, z) | |
beta = z | |
return 1 / (1 + a) * (beta + l * a) | |
poolPotential = lambda p:satPoolRewards(p.stake) - p.cost | |
margin = lambda p, q: 1 - poolPotential(q) / poolPotential(p) | |
def operatorProfit(p, m): | |
r = satPoolRewards(p.stake) | |
pp = poolPotential(p) | |
z = z0() | |
x = m * r ; pp | |
r = (pp - x) * (p.stake / z) | |
return x + r | |
def mkPlayer(): | |
a0 = cfg.a0 | |
z = 1 / cfg.k | |
ws = list(map(lambda x: paretoWeight(cfg.paretoAlpha, x), list(range(1, cfg.n+1)))) | |
# DEBUG: ws = list(range(1, cfg.n+1)) | |
cs = list(sampleRelCost()) | |
potential = lambda lam, c : (z + a0 * lam) / (1 + a0) - c | |
q = sum(ws) | |
wss = list(map(lambda w: w / q, ws)) | |
wcs = list(zip(wss, cs)) | |
pwcs = sorted(wcs, key = lambda x: -potential(x[0], x[1]), reverse = False) | |
return map(lambda w: Player(w[0], w[1]), pwcs) | |
def main(): | |
print("Pledge Modeling") | |
parser = argparse.ArgumentParser(description="Lars' pledge modelling") | |
add_script_arguments(parser) | |
args = parser.parse_args() | |
players = list(mkPlayer()) | |
nonWhales = list(filter(lambda p: p.stake < cfg.whaleThresholds, players)) # remove whales from list of players | |
z = z0() | |
spp = lambda p: [p] if p.stake < z else [Player(z, p.cost )] + spp(Player(p.stake - z, p.cost)) | |
nonWhales1 = [] | |
for player in nonWhales: | |
res = spp(player) | |
nonWhales1.extend(res) | |
k = cfg.k | |
operators1 = nonWhales1[:k+1] # k+1 nr. of nodes | |
loser = operators1[-1] | |
operators = operators1[:-1] | |
oms = map(lambda x: (x, margin(x, loser)), operators) | |
richest = max(operators, key=lambda x: x.stake) | |
poorest = min(operators, key=lambda x: x.stake) | |
middle = operators[k // 2] | |
def sybilProtectionStake(player): | |
k = cfg.k | |
mc = dollarsPerYearToRelative(cfg.minCostPerYear) | |
a0 = cfg.a0 | |
l = player.stake | |
return (l - (player.cost - mc) * (1 + 1 / a0)) * k / 2 | |
sybil = sybilProtectionStake(middle) | |
richestA = stakeToAda(richest.stake) | |
poorestA = stakeToAda(poorest.stake) | |
sybilA = stakeToAda(sybil) | |
richestD = stakeToUsd(richest.stake) | |
poorestD = stakeToUsd(poorest.stake) | |
sybilD = stakeToUsd(sybil) | |
rewards = adaRewardsPerEpoch() | |
e = epochsPerYear() | |
c = cfg.total | |
r = adaRewardsPerEpoch() | |
i = 0 | |
print( "pool pledge (ada) cost per year ($) pool rewards per epoch (ada) potential pool profit per epoch (ada) margin operator profit per epoch (ada) ROI (percent))\n") | |
for (p1, m) in oms: | |
stake = stakeToAda(p1.stake) | |
cost = relativeToDollarsPerYear(p1.cost) | |
spr = r * satPoolRewards(p1.stake) | |
ppp = r * poolPotential(p1) | |
op = r * operatorProfit(p1, m) | |
roi = 100 * op * e / (p1.stake * c) | |
i += 1 | |
print("%6d %11.0f %5.f %8.0f %8.0f %8.6f %8.0f %5.2f" % (i, stake, cost, spr, ppp, m, op, roi)) | |
print() | |
print("number of ada holders: %11d" % cfg.n) | |
print("number of pools: %11d" % cfg.k) | |
print("days per epoch: %13.1f" % cfg.epoch) | |
print("ada in circulation: %11.0f" % cfg.total) | |
print("max supply of ada: %11.0f" % cfg.supply) | |
print("monetary expansion: %16.4f" % cfg.expansion) | |
print("treasury ratio: %16.4f" % cfg.treasury) | |
print("exchange rate ($/ada): %16.4f" % cfg.rate) | |
print("min cost per year ($): %11.0f" % cfg.minCostPerYear) | |
print("Weibull scale: %11.0f" % cfg.weibullScale) | |
print("Weibull shape: %14.2f" % cfg.weibullShape) | |
print("Pareto alpha: %14.2f" % cfg.paretoAlpha) | |
print("Whale threshold: %16.4f" % cfg.whaleThresholds) | |
print("pledge influence: %14.2f" % cfg.a0) | |
print() | |
print("number of whales: %11d" % (cfg.n - len(nonWhales))) | |
print("rewards per epoch (ada) : %11.0f" % rewards) | |
print("richest pool operator stake (ada) : %11.0f" % richestA) | |
print("poorest pool operator stake (ada) : %11.0f" % poorestA) | |
print("sybil attacker min stake (ada) : %11.0f" % sybilA) | |
print("richest pool operator stake ($) : %11.0f" % richestD) | |
print("poorest pool operator stake ($) : %11.0f" % poorestD) | |
print("sybil attacker min stake ($) : %11.0f" % sybilD) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment