from abc import ABC, abstractmethod
from .exceptions import InsufficientCapitalError
[docs]class Fee(ABC):
"""
Abstract ``Callable`` that calculates the total cost and number of shares
given an asset price and the capital to be allocated to it.
It returns the total cost and the number of shares aquired for that cost.
"""
def __call__(self, price: float, capital: float = None, nshares: int = None):
if nshares is None:
shares = self.nshares(price, capital)
else:
shares = nshares
cost = self.cost(price, shares)
fee = cost - (price * shares)
if cost > capital:
raise InsufficientCapitalError(
f"Tried to buy {shares} shares at {price} with only {capital}."
)
return {
"nshares": shares,
"total": cost,
"fee": fee,
}
[docs] @abstractmethod
def nshares(self, price, capital):
pass
[docs] @abstractmethod
def cost(self, price, nshares):
pass
[docs]class FlatPerTrade(Fee):
def __init__(self, fee):
self.fee = fee
[docs] def nshares(self, price, capital):
return (capital - self.fee) // price
[docs] def cost(self, price, nshares):
return price * nshares + self.fee
[docs]class FlatPerShare(Fee):
def __init__(self, fee):
self.fee = fee
[docs] def nshares(self, price, capital):
return capital // (self.fee + price)
[docs] def cost(self, price, nshares):
return (price + self.fee) * nshares
[docs]class NoFee(Fee):
"""
Returns the number of shares possible to buy with given capital,
and calculates to total cost of buying said shares.
Example:
How many shares of an asset costing 10 can be bought using 415,
and what is the total cost::
>>> NoFee(10, 415)
41, 410
"""
[docs] def nshares(self, price, capital):
return capital // price
[docs] def cost(self, price, nshares):
return price * nshares