Русский
Русский
English
Статистика
Реклама

Как правильно и легко рассчитать прибыль на инвестиции или калькулятор ROI на python

Суть проблемы

Пусть у вас есть вложения активов в некую стратегию (даже если buy and hold), и вы хотите рассчитать ROI (return on investment).

Если вы не производили никаких выводов или депозитов, тогда легко рассчитать прибыль
по формуле:

\\ROI = NAV / initial \: investments\\

где NAV - текущая стоимость наших активов, а initial \: investments - исходная стоимость активов.

Однако если в период инвестиций вы делали операции по счету, то их, конечно, нужно учитывать, и тогда простой формулы ROI здесь недостаточно. Одним из способов расчета доходности на ивестиции является расчет перефоманса цены акции "виртуального" паевого фонда (ПИФ). Думаю, что он многим знаком, а если нет, то покажется тоже интуитивным и простым после последующего описания и примеров (надеюсь).

Немного формул

При первом депозите надо создать "виртуальный" паевой фонд, начальное количество акций (паёв) в котором равно депонированным активам N (в акциях) с ценой за акцию P=1

Любой депозит или вывод средств в момент времени t эквивалентен покупке или продаже акций по цене P_t . Далее меняем состояние ПИФа при изменении счета по следующему алгоритму:

  1. ПустьXактивов было добавлено к фонду в момент времени T , где
    X > 0 при депозите и X < 0 при выводе.

  2. В T_0 = T - \varepsilon ПИФ состоял из N акций с ценой P_{T_0} = {NAV}_{T_0} / N

  3. После выполнения транзакции, в момент времени T_1 = T + \varepsilon новое количество акций составит M = N + X / P_{T_0} а цена акции останется той же: P_{T_0} = P_{T_1} = NAV_{T_1} / M

Таким образом, для каждого момента времени t имеем:

  1. стоимость активов NAV_t

  2. количество виртуальных акций N_t

  3. цену одной акции P_t = NAV_t / N_t

В итоге, можно рассчитать доходность от начального момента времени
по формуле:

ROI = P_t / P_{t_0} - 1


Более того, также можно легко рассчитать ROI по этой формуле на любой период времени (t_0, t), t > t_0 , в чем и заключается суть данного метода.

Пример

Допустим мы положили 100$ в стратегию. Сразу отметим, что в этот момент времени "покупаем" 100 акций за 1$. Далее стратегия за какое-то время заработала 20% и наш баланс теперь стал 120$, а следовательно изменилась и цена акции, она стала 120$ / 100 = 1.2$ (количесвто акций не изменилось, потому что никаких новых вложений или выводов не было).

Пусть в этот же момент времени мы решили положить ещё 210$, чтобы увеличить абсолютный доход. Депозит эквивалентен увелечению акций на 210$ / 1.2$ = 175. Таким образом, цена акции осталась (120 + 210)$ / (100 + 175) = 1.2$, а стоимость активов изменилась. Спустя время стратегия заработала ещё 10% от нового баланса, то есть стоимость активов стала равна 363$, следовательно стоимость акции стала равна 363$ / 275 = 1,32$.

Посчитаем доходность с начального момента до момента депозита: (1.2 / 1 - 1) * 100 = 20%
Посчитаем доходность от момента депозита: (1.32 / 1.2 - 1) * 100 = 10%
Посчитаем общую доходность на ивестиции: (1.32 / 1 - 1) * 100 = 32%

Наконец-то про код

Здесь мы будем манипулировать тремя простыми сущностями.

  1. транзакция (Transaction)

  2. ивестор (Investor)

  3. ПИФ (ROICalculator)

Транзакция является структурой с двумя полями, где funding - это вывод или депозит с соответсвующим знаком (X из формул)

class Transaction:    '''    Transaction model.    timestamp: datetime.datetime - transaction timestamp    funding: float - deposit or withdrawal    {        deposit: +X in asset [U]        withdrawal: -X in asset [U]    }    '''    def __init__(self, timestamp: datetime, funding: float):        self.timestamp = timestamp        self.funding = funding

Далее, модель инвестора - самая важная в рамках использования. Для расчетов нам важно иметь:

  1. начальный депозит

  2. дату первых инвестиций

  3. список транзакций

Cамое главное - переопределить метод доступа к балансу по временной метке. Best practice здесь запрос к БД или pandas.DataFrame

Transactions = List[Transaction]class Investor(ABC):    '''    Investor model.    1. Attributes    investment_timestamp: datetime.datetime - investment timestamp (deposit timestamp)    deposit: float - deposit amount in asset [U]    transactions: Transactions - list of transactions with fundings and timestamp    2. get_nav_by_timestamp - investor's net asset value    '''    def __init__(self, investment_timestamp: datetime, deposit: float, transactions: Transactions, *args, **kwargs):        self.investment_timestamp = investment_timestamp        self.deposit = deposit        # sort transactions by timestamp        # from first transaction to last        #        # EXCEPT DEPOSIT TRANSACTION        #        self.transactions = sorted(            transactions, key=lambda x: x.timestamp, reverse=False)    @abstractmethod    def get_nav_by_timestamp(self, timestamp: datetime) -> float:        '''returns NAV'''        raise NotImplementedError

И последнее - сам ROICalculator. В целом, он полностью повторяет алгоритм, описанный выше, сохраняя состояние ПИФа в атрибуты объекта, что позволяет достаточно быстро рассчитывать share price на любой момент времени t даже на больших данных с большим количеством движений по счету (проверял на боевых данных).

class ROICalculator:    '''    ROICalculator.    1. Create virtual pif __init_pif     {        init shares = deposit quantity of asset[U]        share price = 1    }    2. System go through 3 conditions while getting funding    {        Let funding X[U] was added to virtual pif at T;        T - transaction timestamp,        T0 = T - eps - timestamp before transaction        T1 = T + eps - timestamp after transaction        pif consisted of N SHARES with share price P_0[U] = NAV_T0[U] / N.        Add X[U] to virtual pif: M = N + X[U] / P_0[U],        where M - new shares amount        Update share price P[U] = NAV_T1[U] / M    }    '''    def __init__(self, investor: Investor, eps_hours=1):        # eps is used while getting nav_before        # and nav_after transaction        self.investor = investor        self.eps_hours = eps_hours        self.__init_pif()    def __init_pif(self):        self.shares = self.investor.deposit        self.share_price = 1    def __calculate_shares(self, funding: float):        self.shares += funding / self.share_price    def __calculate_share_price(self, nav: float):        self.share_price = nav / self.shares    def __calculate_shares_by_timestamp(self, timestamp: datetime):        # create virtual pif each time calculating shares        self.__init_pif()        for transaction in self.investor.transactions:            if transaction.timestamp > timestamp:                break            # 1 condition: before transaction            # T0            timestamp_before_transtaction = transaction.timestamp - \                timedelta(hours=self.eps_hours)            if timestamp_before_transtaction < self.investor.investment_timestamp:                nav_before = self.investor.deposit            # NAV_T0            try:                nav_before = self.investor.get_nav_by_timestamp(                    timestamp_before_transtaction)            except Exception as e:                print(e)            # P0 = NAV_T0 / N            self.__calculate_share_price(nav_before)            # 2 condition: add funding to virtual pif            # shares = M            self.__calculate_shares(transaction.funding)            # T1            timestamp_after_transtaction = transaction.timestamp + \                timedelta(hours=self.eps_hours)            # NAV_T            try:                nav_after = self.investor.get_nav_by_timestamp(                    timestamp_after_transtaction)            except Exception as e:                print(e)            # update share price            # P[U] = NAV_T1[U] / M            self.__calculate_share_price(nav_after)    def __calculate_share_price_by_timestamp(self, timestamp: datetime):        # update shares N in self.shares        self.__calculate_shares_by_timestamp(timestamp)        # get NAV from data        nav = self.investor.get_nav_by_timestamp(timestamp)        # update share_price in self.share_price        self.__calculate_share_price(nav)    def get_share_price_perfomance(self, t0: datetime, t: datetime) -> float:        '''        t  - end_timestamp        t0 - start_timestamp, t > t0        t = datetime.utcnow(), t0 = investment_timestamp to get ROI        '''        self.__calculate_share_price_by_timestamp(t)        # fix share_price at t        k = self.share_price        self.__calculate_share_price_by_timestamp(t0)        # fix share_price at t0        k0 = self.share_price        return k / k0 - 1

Как можно использовать

Допустим, вы положили средства в лендинговую стратегию с доходом около 0.05% в день на инвестированные средства. Это означает, что наш P&L на стоимость активов будет рассчитываться как:

periods_i - days \: between \: transaction_i \: and\: transaction_{i-1}, \\ investments_i - total \: investments \: in \: the \: period_iPnL_t = 0.005 * \sum_{i=1}^n investments_i * period_i, \: n - transactions \: number \: before \: t

Это нужно для правильного определения доступа к балансам по временной метке.

Пусть 2020/1/1 было депонировано 100$, а 2020/4/1, было депонировано ещё 200$, тогда, с учетом описанной выше формулы получаем такую модель инвестора:

class ExampleInvestor(Investor):    '''    Simple lending (static) strategy with 0.05% profit daily    on investments without reinvestment    '''    def __init__(self, investment_timestamp, deposit, transactions):        super().__init__(investment_timestamp, deposit, transactions)    def lending_assets(self, timestamp):        # before transaction        if timestamp <= datetime(2020, 4, 1):            return 100        # after transaction        else:            return 300    def get_nav_by_timestamp(self, timestamp):        '''        NAV = investments + PnL        daily PnL = 0.0005 * investments =>        total PnL = 0.0005 * sum(invesmetns_i * period_i)        '''        if timestamp < datetime(2020, 4, 1):            pnl = 0.0005 * \                self.lending_assets(timestamp) * \                (timestamp - self.investment_timestamp).days            return self.lending_assets(timestamp) + pnl        elif timestamp > datetime(2020, 4, 1):            # redefine investments_i and daily PnL            transaction_timestamp = datetime(2020, 4, 1)            acc_pnl_before_transaction = 0.0005 * self.lending_assets(                transaction_timestamp) * (transaction_timestamp - self.investment_timestamp).days            pnl =  0.0005 * self.lending_assets(timestamp) * (timestamp - transaction_timestamp).days +\                acc_pnl_before_transaction            return self.lending_assets(timestamp) + pnl

Определим модель инвестора:

transaction = Transaction(datetime(2020, 4, 1), funding=200)investor = ExampleInvestor(investment_timestamp=datetime(2020, 1, 1),                           deposit=100, transactions=[transaction])

Создадим модель ПИФа:

pif = ROICalculator(investor)

И теперь при помощи метода get_share_price_perfomance можем получить ROI на любой период времени. В качестве примера посчитаем 1D%, MTD% и YTD% до и после депозита и получим:

1D return on 2020-03-31 = 0.05 %MTD return on 2020-03-31 = 1.51 %YTD return on 2020-03-31 = 4.50 %1D return on 2020-04-30 = 0.05 %MTD return on 2020-04-30 = 1.44 %YTD return on 2020-04-30 = 6.01 %

Делюсь кодом в надежде на то, что это кому-нибудь ещё пригодится и пару часов моих выходных не прошли впустую. Лично у меня получилось очень удачно совместить эту небольшую модель с API бирж, а также используя известную питоновскую ORM - sqlalchemy для доступа к балансам.

Источник: habr.com
К списку статей
Опубликовано: 14.11.2020 16:05:59
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Финансы в it

Roi

Финансы

Доходность

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru