Module src.TokenLab.simulationcomponents.pricingclasses
Created on Wed Nov 23 11:54:33 2022
@author: stylianoskampakis
Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Nov 23 11:54:33 2022
@author: stylianoskampakis
"""
import numpy as np
import scipy
from scipy.stats import binom,norm
from typing import Union,TypeVar,Callable
from typing import List, Dict,Union,Tuple
from baseclasses import Controller,TokenEconomy,AddOn
import pandas as pd
from tqdm import tqdm
from matplotlib import pyplot as plt
import time
from addons import AddOn_RandomNoise, AddOn_RandomNoiseProportional
class HoldingTimeController(Controller):
"""
Abstract class from which all HoldingTime controllers should inherit.
"""
def __init__(self):
super(HoldingTimeController,self).__init__()
self.holding_time=None
def get_holding_time(self)->float:
return self.holding_time
def execute(self)->float:
pass
class HoldingTime_Constant(HoldingTimeController):
"""
Constant holding time
"""
def __init__(self,holding_time:float):
"""
Parameters
----------
holding_time : float
The holding time. Needs to be positive.
Raises
------
Exception
If holding_time<0 then raise Exception.
Returns
-------
None.
"""
super(HoldingTime_Constant,self).__init__()
if holding_time<=0:
raise Exception('Holding time needs to be a positive real value!')
self.holding_time=holding_time
class HoldingTime_Stochastic(HoldingTimeController):
"""
Uses a probability distribution and samples a random holding time per iteration.
"""
def __init__(self,holding_time_dist:scipy.stats=scipy.stats.halfnorm,holding_time_params:Dict={'loc':0,'scale':1},
minimum:float=0.1):
"""
Parameters
----------
holding_time_dist : scipy.stats, optional
A scipy distribution to sample the holding time from. The default is scipy.stats.halfnorm.
holding_time_params : Dict, optional
The parameters of the distribution. The default is {'loc':0,'scale':1}.
minimum : float, optional
The minimum possible holding time. If the sampled holding time is <1, then it reverts to minimum. The default is 0.1.
Returns
-------
None.
"""
self.distribution=holding_time_dist
self.dist_params=holding_time_params
self.minimum=minimum
self.execute()
def execute(self)->float:
"""
Returns
-------
float
The holding time.
"""
seed=int(int(time.time())*np.random.rand())
self.holding_time=self.distribution.rvs(size=1,**self.dist_params,random_state=seed)[0]
if self.holding_time<self.minimum:
self.holding_time=self.minimum
return self.holding_time
class HoldingTime_Adaptive(HoldingTimeController):
"""
Recalculates holding time based on transaction volume and price.
It includes minimum and maximum values. If these are not set, then the calculations can get out of hand
into unrealistic values. The maximum value of 12 is set based on the assumption that the unit of time is a month.
Hence, the maximum holding time (by default) is set to 1 year.
"""
def __init__(self,holding_time,noise_addon:AddOn=None,minimum:float=0.01,maximum:float=12):
self.dependencies={TokenEconomy:None}
super(HoldingTime_Adaptive,self).__init__()
if holding_time<=0:
raise Exception('Holding time needs to be a positive real value!')
self.holding_time=holding_time
self._noise_addon=noise_addon
self.minimum=minimum
self.maximum=maximum
def execute(self)->float:
tokeneconomy=self.dependencies[TokenEconomy]
holding_time=tokeneconomy.price*tokeneconomy.transactions_volume_in_tokens/(tokeneconomy.transactions_value_in_fiat++0.000000001)
if self._noise_addon!=None:
dummy=self._noise_addon.apply(**{'value':price_new})
if dummy+holding_time>0:
holding_time=dummy+holding_time
if holding_time<self.minimum:
holding_time=self.minimum
elif holding_time>self.maximum:
holding_time=self.maximum
self.holding_time=holding_time
class PriceFunctionController(Controller):
"""
Abstract class for price controllers.
"""
def __init__(self):
super(PriceFunctionController,self).__init__()
self.dependencies={TokenEconomy:None}
self.price=None
def execute(self)->float:
iteration+=1
pass
def get_price(self)->float:
return self.price
class PriceFunction_EOE(PriceFunctionController):
"""
Simple implementation of equation of exchange. Can also implement noise through AddOns.
If the AddOns make the price go negative, then the price goes back to simple equation of exchange before
noise w as added.
"""
def __init__(self,noise_addon:AddOn=None,smoothing_param:float=1):
"""
Parameters
----------
noise_AddOn: Optional, if this is used, then the noise is added to the price.
If the price is negative, then the noise add-on is ignored, and the returned price
is the price without the noise.
smoothing_param: Float. If set below 1, then this applied a weighted average between the new price
and the old price. This causes an anchoring effect, since in practice the new price would
be anchored to some extent to the previous one. A smoothing_param of 0.9, for exaple
calculates the final price as 0.9*newprice+0.1*old_price
"""
super(PriceFunction_EOE,self).__init__()
self._noise_addon=noise_addon
if smoothing_param>1 or smoothing_param<0:
raise Exception('Smoothing param must be in [0,1]')
else:
self.smoothing_param=smoothing_param
def execute(self)->float:
tokeneconomy=self.dependencies[TokenEconomy]
transaction_volume_in_fiat=tokeneconomy.transactions_value_in_fiat
holding_time=tokeneconomy.holding_time
#supply_of_tokens=tokeneconomy.transactions_value_in_tokens
supply_of_tokens=tokeneconomy.supply
#noise adjustment
price_new=holding_time*transaction_volume_in_fiat/supply_of_tokens
price_new=self.smoothing_param*price_new+(1-self.smoothing_param)*tokeneconomy.price
if self._noise_addon!=None:
price_new_2=self._noise_addon.apply(**{'value':price_new})
else:
price_new_2 = -1
if price_new_2<0:
self.price=price_new
else:
self.price=price_new_2
self.iteration+=1
Classes
class HoldingTimeController
-
Abstract class from which all HoldingTime controllers should inherit.
Expand source code
class HoldingTimeController(Controller): """ Abstract class from which all HoldingTime controllers should inherit. """ def __init__(self): super(HoldingTimeController,self).__init__() self.holding_time=None def get_holding_time(self)->float: return self.holding_time def execute(self)->float: pass
Ancestors
- baseclasses.Controller
Subclasses
Methods
def execute(self) ‑> float
-
Expand source code
def execute(self)->float: pass
def get_holding_time(self) ‑> float
-
Expand source code
def get_holding_time(self)->float: return self.holding_time
class HoldingTime_Adaptive (holding_time, noise_addon: baseclasses.AddOn = None, minimum: float = 0.01, maximum: float = 12)
-
Recalculates holding time based on transaction volume and price.
It includes minimum and maximum values. If these are not set, then the calculations can get out of hand into unrealistic values. The maximum value of 12 is set based on the assumption that the unit of time is a month. Hence, the maximum holding time (by default) is set to 1 year.
Expand source code
class HoldingTime_Adaptive(HoldingTimeController): """ Recalculates holding time based on transaction volume and price. It includes minimum and maximum values. If these are not set, then the calculations can get out of hand into unrealistic values. The maximum value of 12 is set based on the assumption that the unit of time is a month. Hence, the maximum holding time (by default) is set to 1 year. """ def __init__(self,holding_time,noise_addon:AddOn=None,minimum:float=0.01,maximum:float=12): self.dependencies={TokenEconomy:None} super(HoldingTime_Adaptive,self).__init__() if holding_time<=0: raise Exception('Holding time needs to be a positive real value!') self.holding_time=holding_time self._noise_addon=noise_addon self.minimum=minimum self.maximum=maximum def execute(self)->float: tokeneconomy=self.dependencies[TokenEconomy] holding_time=tokeneconomy.price*tokeneconomy.transactions_volume_in_tokens/(tokeneconomy.transactions_value_in_fiat++0.000000001) if self._noise_addon!=None: dummy=self._noise_addon.apply(**{'value':price_new}) if dummy+holding_time>0: holding_time=dummy+holding_time if holding_time<self.minimum: holding_time=self.minimum elif holding_time>self.maximum: holding_time=self.maximum self.holding_time=holding_time
Ancestors
- HoldingTimeController
- baseclasses.Controller
Methods
def execute(self) ‑> float
-
Expand source code
def execute(self)->float: tokeneconomy=self.dependencies[TokenEconomy] holding_time=tokeneconomy.price*tokeneconomy.transactions_volume_in_tokens/(tokeneconomy.transactions_value_in_fiat++0.000000001) if self._noise_addon!=None: dummy=self._noise_addon.apply(**{'value':price_new}) if dummy+holding_time>0: holding_time=dummy+holding_time if holding_time<self.minimum: holding_time=self.minimum elif holding_time>self.maximum: holding_time=self.maximum self.holding_time=holding_time
class HoldingTime_Constant (holding_time: float)
-
Constant holding time
Parameters
holding_time
:float
- The holding time. Needs to be positive.
Raises
Exception
- If holding_time<0 then raise Exception.
Returns
None.
Expand source code
class HoldingTime_Constant(HoldingTimeController): """ Constant holding time """ def __init__(self,holding_time:float): """ Parameters ---------- holding_time : float The holding time. Needs to be positive. Raises ------ Exception If holding_time<0 then raise Exception. Returns ------- None. """ super(HoldingTime_Constant,self).__init__() if holding_time<=0: raise Exception('Holding time needs to be a positive real value!') self.holding_time=holding_time
Ancestors
- HoldingTimeController
- baseclasses.Controller
class HoldingTime_Stochastic (holding_time_dist:
= <scipy.stats._continuous_distns.halfnorm_gen object>, holding_time_params: Dict = {'loc': 0, 'scale': 1}, minimum: float = 0.1) -
Uses a probability distribution and samples a random holding time per iteration.
Parameters
holding_time_dist
:scipy.stats
, optional- A scipy distribution to sample the holding time from. The default is scipy.stats.halfnorm.
holding_time_params
:Dict
, optional- The parameters of the distribution. The default is {'loc':0,'scale':1}.
minimum
:float
, optional- The minimum possible holding time. If the sampled holding time is <1, then it reverts to minimum. The default is 0.1.
Returns
None.
Expand source code
class HoldingTime_Stochastic(HoldingTimeController): """ Uses a probability distribution and samples a random holding time per iteration. """ def __init__(self,holding_time_dist:scipy.stats=scipy.stats.halfnorm,holding_time_params:Dict={'loc':0,'scale':1}, minimum:float=0.1): """ Parameters ---------- holding_time_dist : scipy.stats, optional A scipy distribution to sample the holding time from. The default is scipy.stats.halfnorm. holding_time_params : Dict, optional The parameters of the distribution. The default is {'loc':0,'scale':1}. minimum : float, optional The minimum possible holding time. If the sampled holding time is <1, then it reverts to minimum. The default is 0.1. Returns ------- None. """ self.distribution=holding_time_dist self.dist_params=holding_time_params self.minimum=minimum self.execute() def execute(self)->float: """ Returns ------- float The holding time. """ seed=int(int(time.time())*np.random.rand()) self.holding_time=self.distribution.rvs(size=1,**self.dist_params,random_state=seed)[0] if self.holding_time<self.minimum: self.holding_time=self.minimum return self.holding_time
Ancestors
- HoldingTimeController
- baseclasses.Controller
Methods
def execute(self) ‑> float
-
Returns
float
- The holding time.
Expand source code
def execute(self)->float: """ Returns ------- float The holding time. """ seed=int(int(time.time())*np.random.rand()) self.holding_time=self.distribution.rvs(size=1,**self.dist_params,random_state=seed)[0] if self.holding_time<self.minimum: self.holding_time=self.minimum return self.holding_time
class PriceFunctionController
-
Abstract class for price controllers.
Expand source code
class PriceFunctionController(Controller): """ Abstract class for price controllers. """ def __init__(self): super(PriceFunctionController,self).__init__() self.dependencies={TokenEconomy:None} self.price=None def execute(self)->float: iteration+=1 pass def get_price(self)->float: return self.price
Ancestors
- baseclasses.Controller
Subclasses
Methods
def execute(self) ‑> float
-
Expand source code
def execute(self)->float: iteration+=1 pass
def get_price(self) ‑> float
-
Expand source code
def get_price(self)->float: return self.price
class PriceFunction_EOE (noise_addon: baseclasses.AddOn = None, smoothing_param: float = 1)
-
Simple implementation of equation of exchange. Can also implement noise through AddOns.
If the AddOns make the price go negative, then the price goes back to simple equation of exchange before noise w as added.
Parameters
noise_AddOn: Optional, if this is used, then the noise is added to the price. If the price is negative, then the noise add-on is ignored, and the returned price is the price without the noise.
smoothing_param
:Float. If set below 1, then this applied a weighted average between the new price
and the old price. This causes an anchoring effect, since in practice the new price would be anchored to some extent to the previous one. A smoothing_param of 0.9, for exaple calculates the final price as 0.9newprice+0.1old_price
Expand source code
class PriceFunction_EOE(PriceFunctionController): """ Simple implementation of equation of exchange. Can also implement noise through AddOns. If the AddOns make the price go negative, then the price goes back to simple equation of exchange before noise w as added. """ def __init__(self,noise_addon:AddOn=None,smoothing_param:float=1): """ Parameters ---------- noise_AddOn: Optional, if this is used, then the noise is added to the price. If the price is negative, then the noise add-on is ignored, and the returned price is the price without the noise. smoothing_param: Float. If set below 1, then this applied a weighted average between the new price and the old price. This causes an anchoring effect, since in practice the new price would be anchored to some extent to the previous one. A smoothing_param of 0.9, for exaple calculates the final price as 0.9*newprice+0.1*old_price """ super(PriceFunction_EOE,self).__init__() self._noise_addon=noise_addon if smoothing_param>1 or smoothing_param<0: raise Exception('Smoothing param must be in [0,1]') else: self.smoothing_param=smoothing_param def execute(self)->float: tokeneconomy=self.dependencies[TokenEconomy] transaction_volume_in_fiat=tokeneconomy.transactions_value_in_fiat holding_time=tokeneconomy.holding_time #supply_of_tokens=tokeneconomy.transactions_value_in_tokens supply_of_tokens=tokeneconomy.supply #noise adjustment price_new=holding_time*transaction_volume_in_fiat/supply_of_tokens price_new=self.smoothing_param*price_new+(1-self.smoothing_param)*tokeneconomy.price if self._noise_addon!=None: price_new_2=self._noise_addon.apply(**{'value':price_new}) else: price_new_2 = -1 if price_new_2<0: self.price=price_new else: self.price=price_new_2 self.iteration+=1
Ancestors
- PriceFunctionController
- baseclasses.Controller
Methods
def execute(self) ‑> float
-
Expand source code
def execute(self)->float: tokeneconomy=self.dependencies[TokenEconomy] transaction_volume_in_fiat=tokeneconomy.transactions_value_in_fiat holding_time=tokeneconomy.holding_time #supply_of_tokens=tokeneconomy.transactions_value_in_tokens supply_of_tokens=tokeneconomy.supply #noise adjustment price_new=holding_time*transaction_volume_in_fiat/supply_of_tokens price_new=self.smoothing_param*price_new+(1-self.smoothing_param)*tokeneconomy.price if self._noise_addon!=None: price_new_2=self._noise_addon.apply(**{'value':price_new}) else: price_new_2 = -1 if price_new_2<0: self.price=price_new else: self.price=price_new_2 self.iteration+=1