Post
Topic
Board Кодеры
Merits 4 from 1 user
Topic OP
DCA-стратегия в BTC на React и Recharts
by
Herr Kaufmann
on 03/02/2020, 22:55:44 UTC
⭐ Merited by Ratimov (4)
Перевод статьи: How To Build a Bitcoin Dollar Cost Average Chart With React and Recharts

Как создать график инвестиционной стратегии DCA в биткоин, используя React и Recharts

Строим графики, чтобы разобраться с нашими финансовыми вложениями


Примечание: не рассматривайте эту статью как совет по инвестированию. Статья предназначена исключительно для образовательных целей. Better Programming, его персонал и владельцы не несут ответственности за ваши инвестиционные решения.

Recharts - это графическая библиотека, которая представляет из себя множество декларативных React-компонентов для построения графиков с помощью D3. Доступны десять настраиваемых типов графика, а также вспомогательные компоненты.

В этом туториале мы создадим несколько AreaCharts для отображения стоимости портфеля, общей суммы монет и общей суммы инвестиций за определенный временной период, на котором осуществляем dollar cost averaging биткоина.

Посмотрите на www.cryptodca.org интерактивный пример графика, который мы будем строить. Посетите GitHub проекта, чтобы узнать больше.

.     .     .

Разделы

  •    Построение графика Dollar Cost Averaging (DCA)
  •    Начало работы
  •    История цен, полученная с помощью API CoinGecko
  •    Получение данных
  •    Расчет итоговых значений
  •    Создание массива для графика
  •    Recharts графика
  •    Recharts всплывающей подсказки
  •    Recharts точек
  •    Recharts оси Y и оси X
  •    Recharts нескольких типов данных
  •    Recharts адаптации размера экрана
  •    Вывод

.     .     .

Построение Графика Dollar Cost Averaging

Dollar Cost Averaging (DCA) - это инвестиционная стратегия, для которой характерно пополнение портфеля одним и тем же активом на одну и ту же сумму в долларах через регулярные временные интервалы с целью снижения краткосрочной волатильности.

Например, инвестиция в размере $ 200 в конкретную ценную бумагу или криптовалюту каждый месяц означает, что вы будете покупать больше единиц актива, когда его цена низкая, и меньше, когда цена выше. Читайте статью Investopedia о DCA, чтобы узнать больше.

Построение графика стоимости  DCA биткоина конкретного счета с течением времени требует, чтобы мы рассчитали общую стоимость счета на каждом интервале в течение определенного периода времени.

Например, если взять за интервал месяц, а за период времени - два года, то нам нужно вычислить общую стоимость счета 24 раза. Чтобы вычислить общую стоимость на определенном интервале, нам нужно умножить общее количество накопленных монет за период на цену монеты в момент покупки.

Количество накопленных за период монет может быть рассчитана путем деления суммы инвестирования на цену монеты в момент совершения покупки для каждого интервала времени.

Давайте проиллюстрируем это примером, скажем, мы планируем покупать биткоин на сумму $ 200 каждый месяц с января 2016 по май 2016.

Количество монет за первый месяц легко рассчитать, просто берем Сумму для Инвестирования ($ 200) и делим на Цену Монеты ($ 434.33) на 1 января 2016 года.

Посчитать общую стоимость тоже легко, просто возьмите Количество Монет, умноженное на текущую Цену Монеты, за первый месяц она должна равняться сумме вложенных средств ($ 200).

Code:
// amountToInvest / coinPrice
200 / 434.33  ~= .46 // Amount of Coin for the first month (Количество Монет за первый месяц)

// amountOfCoin * coinPrice
.46 * 434.33 ~= 200  // Total Value (Общая Стоимость)

Расчет Количества Монет за второй месяц немного отличается.

Во-первых, как в прошлом месяце, делим сумму инвестиций на цену монеты текущего месяца ($ 371,04). Затем добавляем это значение к сумме монет предыдущего месяца (.46).

Code:
// amountToInvest / coinPrice
200 / 371.04  ~= .54 // Amount of Coin bought in the second month (Количество Монет, купленных во втором месяце)

// amountOfCoin for second month + amountOfCoin for first month
.54 + .46 = 1 // Total Accumulated Amount of Coin (Общее Накопленное Количество Монет)

Чтобы рассчитать Общую Стоимость на второй месяц, мы берем Общее Накопленное Количество Монет и умножаем на текущую Цену Монеты.

Code:
// Total Accumulated Amount of Coin * coinPrice
1 * 371.04 = 371.04

Проделав то же самое для последующих месяцев, получаем следующую таблицу:

Месяц     Цена монеты     Всего Инвестировано     Количество Монет     Общая Стоимость

1            434.33               200                               .46                           200
2            371.04               400                               1                              371.04
3            424.49               600                               1.47                         624.00
4            416.75               800                               1.95                         811.20
5            452.59               1000                             2.39                         1081.69

Код для вычисления этих значений может выглядеть примерно так.

Code:
for (let i = 0; i < numOfDays; i += freqInDays) {
  const coinPrice = priceArr[i].price;
  coinAmount += amountToInvest / coinPrice;
  totalInvested += amountToInvest;
  const total = coinAmount * coinPrice;
  dataArr.push({
    TotalInvested: totalInvested,
    CoinAmount: coinAmount,
    CoinPrice: coinPrice,
    Total: total,
    date: priceArr[i].date,
  });
}
calculate.values.js размещен на GitHub

numOfDays - общее количество дней за период времени. В нашем случае получается 121 день между январем и маем 2016.

freqInDays - это временной интервал, через который осуществляется покупка, в данном случае он составляет 30 дней.

priceArr - это массив объектов с ценами биткойна и датами, соответствующими этой цене.

amountToInvest - это сумма в долларах, которая будет инвестирована за временной интервал, в данном случае это $ 200.

coinAmount - это общее количество монет, накопленных к текущему моменту.

totalInvested - это общая сумма инвестиций к текущему моменту.

total - общая стоимость портфеля в долларах США.

Для этих четырех значений: TotalInvested, CoinAmount, CoinPrice и Total, - мы хотим построить график изменения во времени.

freqInDays, amountToInvest и numOfDays будут задаваться пользователем, а цены биткоина, priceArr, будут взяты из API CoinGecko.

.     .     .

Начало работы

Создаем новый проект Creat React App

Code:
npx create-react-app bitcoin-dca
cd bitcoin-dca
npm start

Переходим на src/App.js и переписываем начальный код.

Code:
import React from "react";
import "./App.css";
function App() {
  return (
    

      

Bitcoin


    

  );
}
export default App;
src.app.js размещен на GitHub

Наконец, переходим на src/App.css и обновляем css-элементы, как показано ниже.

Code:
body {
  background-color: #232323;
  color: white;
}
.title {
  color: #f7931a;
  font-size: 40px;
}
.App {
  text-align: center;
}
src.app.css размещен на GitHub

.     .     .

История Цен из API CoinGecko

API CoinGecko бесплатно предлагает крипто-данные без ключа API. Конечная точка /coins/{id}/market_chart/range предоставляет историю рыночных данных о конкретной монеты в пределах указанного диапазона, это именно то, что нам нужно.

Параметр id относится к ID монеты, в данном случае это биткоин (id=bitcoin). Параметр vs_currency определяет, в какой валюте нам будет отправлена цена биткойна.

Параметры from и to задают период времени, за который будет получена цена, и должны быть представлены в виде временной метки UNIX.

Например, https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range?vs_currency=usd&from=1392577232&to=1422577232 получит цену биткоина в USD за каждый день между датами 16.02.2014 и 30.01.2015.

.     .     .

Получение данных

Во-первых, давайте зададим статичные значения startDate, endDate, freqInDays и amountToInvest в начале App.js. В идеале, мы создадим форму для ввода этих переменных пользователем, но пока определим их тут.

Затем создадим простую async-функцию, которая принимает startDate и endDate, запрашивает данные при помощи API CoinGecko и помещает их в состояние.

Чтобы хранить данные и различные состояния, нам нужно определить coinData, isLoading и error в компоненте состояния.

Code:
import React, { useEffect, useState } from "react";
import "./App.css";
const APIURL = "https://api.coingecko.com/api/v3/";
function App() {
  const startDate = "1/1/2016";
  const endDate = "1/1/2020";
  const freqInDays = 30;
  const amountToInvest = 200;
  const [coinData, setCoinData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(false);
  const getCoinData = async (startDate, endDate) => {
    setIsLoading(true);
    const url = ""; // TODO
    try {
      const coinResponse = await fetch(url);
      const data = await coinResponse.json();
      setCoinData(data);
      setError(false);
      setIsLoading(false);
    } catch (e) {
      setIsLoading(false);
      setError(e);
    }
  };
  return (
    

      

Bitcoin


    

  );
}
export default App;
app.js размещен на GitHub

Чтобы передавать параметры startDate и endDate в понятном для человека виде, мы будем использовать библиотеку Day.js, которая поможет нам конвертировать даты из вида, понятного человеку, во временные метки UNIX. Импортируем dayjs и применим его расширение advancedformat.

Code:
...
import dayjs from "dayjs";
import advancedFormat from "dayjs/plugin/advancedFormat";
dayjs.extend(advancedFormat);
...

Затем используем метод format, принадлежащий dayjs, чтобы конвертировать даты во временные метки UNIX внутри функции getCoinData.

Code:
...
const getCoinData = async (startDate, endDate) => {
  ...
  const startDateUnix = dayjs(startDate).format("X");
  const endDateUnix = dayjs(endDate).format("X");
  ...
}
...
getCoinData.js размещен на GitHub

Затем создаем URL  описанным выше способом, добываем данные, и обновляем состояние компонента с помощью setCoinData.

Code:
...
 const getCoinData = async (startDate, endDate) => {
    ...
    const startDateUnix = dayjs(startDate).format("X");
    const endDateUnix = dayjs(endDate).format("X");
    const range = `range?vs_currency=usd&from=${startDateUnix}&to=${endDateUnix}`;
    const url = `${APIURL}/coins/bitcoin/market_chart/${range}`;
    try {
      const coinResponse = await fetch(url);
      const data = await coinResponse.json();
      setCoinData(data);
      setError(false);
      setIsLoading(false);
    } catch (e) {
      setIsLoading(false);
      setError(e);
    }
 }
...
getCoinData.js размещен на GitHub

Теперь мы можем вызвать эту функцию в Хуке useEffect с датами, размещенными в верхней части компонента.

Code:
...
useEffect(() => {
  getCoinData(startDate, endDate);
}, []);
...

Существует четыре UI-состояния, которые нам нужно обработать: noData, loading, error и data. Добавим несколько условных операторов под Хуком useEffect, как это показано ниже.

Code:
...
let content =
No Data
;
if (coinData && coinData.prices && coinData.prices.length > 0)
  content =
Data
;
if (isLoading) content =
Loading
;
if (error) content =
{error}
;
return (
  

    

Bitcoin


    {content}
  

);
...
useEffect.js размещен на GitHub

Данные, получаемые из const data = await coinResponse.json(), будут представлять из себя массив из временных меток UNIX и цен в промежутке между двумя заданными нами датами.

Это именно то, что нам потребуется для расчёта итоговых значений и создания графика.

.     .     .

Расчет Итоговых Значений

Теперь наша цель - рассчитать, используя массив coinData.prices, следующие значения:

  • Итоговое Количество Монет в BTC - totalCoinAmount
  • Итоговая Стоимость в USD - endTotal
  • Итоговые Инвестиции в USD - totalInvested
  • Заработанные Деньги в USD - numberGained
  • Заработанные Деньги в Процентах - percentGained

По большей части логика тут такая же, как и в секции Построение Графика Dollar Cost Averaging.

numberGained - это разность итоговой стоимости в USD и totalInvested. percentGained - это процент, на который увеличилось totalInvested (конечное значение - endTotal). Создаем файл src/Totals, как показано ниже.

Code:
import React from "react";
export default function Totals({ priceArr, freqInDays, amountToInvest }) {
  const numOfDays = priceArr.length;
  let coinAmount = 0;
  for (let i = 0; i < numOfDays; i += freqInDays) {
    const coinValue = priceArr[i][1];
    coinAmount += amountToInvest / coinValue;
  }
  const totalCoinAmount = coinAmount;
  const totalInvested = amountToInvest * Math.floor(numOfDays / freqInDays);
  const endTotal = totalCoinAmount * priceArr[priceArr.length - 1][1];
  const numberGained = endTotal - totalInvested;
  const percentGained = ((endTotal - totalInvested) / totalInvested) * 100;
  return
Totals
;
}
src.totals.js размещен на GitHub

Для того, чтобы выводить на экран эти значения, создаем другой компонент src/Totaljs, применяя простые элементы дизайна.

Code:
import React from "react";
export default function Total({ title, value }) {
  return (
    

      

{title}:


      

{value}


    

  );
}
const styles = {
  row: {
    display: "flex",
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    maxWidth: 350,
    margin: "10px auto",
  },
  title: {
    fontWeight: 600,
    margin: 0,
  },
  value: {
    color: "#f7931a",
    fontSize: 24,
    margin: 0,
  },
};
src.totals.js размещен на GitHub

Если вы попробуете запустить вычисления выше, то обнаружите, что большинство значений содержат много знаков после запятой. Создаем вспомогательную функцию ./src/round.js, которая будет округлять числа, чтоб они выглядели лучше.

Code:
export default function round(num, digit) {
  return +(Math.round(num + "e+" + digit) + "e-" + digit);
}
Импортируем оба компонента round и Total в компонент Totals.

А затем создаем несколько компонентов Total, передавая описание в свойстве title и актуальное значение в свойстве value. Также мы можем применить к этим значениям функцию round.

Code:
// ./src/Totals.js
import Total from "./Total";
import round from "./round";
...
return (
    

      
      
              title={"Amount Invested (USD)"}
        value={`$${round(totalInvested, 2)}`}
      />
      
      
    

  );
...
src.totals.js размещен на GitHub

Наконец, импортируем Totals в App.js и заменяем состояние «data» на компонент Totals.

Code:
...
import Totals from "./Totals";
...
let content =
No Data
;
if (coinData && coinData.prices && coinData.prices.length > 0)
  content = (
            priceArr={coinData.prices}
        freqInDays={freqInDays}
        amountToInvest={amountToInvest}
      />
  );
if (isLoading) content =
Loading
;
if (error) content =
{error}
;
...
Totals.js размещен на GitHub


.     .     .

Создание Массива для Графика

Приведенный ниже код вам уже должен быть хорошо знаком из секции DCA, пожалуйста, ознакомьтесь с этой секцией, чтобы узнать, как этот код работает.

Один момент, что мы снова используем dayjs, чтобы хранить информацию в удобном для человеческого восприятия виде. Создаем новый файл ./src/Graph.js, как показано ниже:

Code:
import React from "react";
import dayjs from "dayjs";
export default function Graph({ priceArr, freqInDays, amountToInvest }) {
  const numOfDays = priceArr.length;
  let coinAmount = 0;
  let totalInvested = 0;
  let dataArr = [];
  for (let i = 0; i < numOfDays; i += freqInDays) {
    const coinPrice = priceArr[i][1];
    coinAmount += amountToInvest / coinPrice;
    totalInvested += amountToInvest;
    const total = coinAmount * coinPrice;
    const date = dayjs(priceArr[i][0]).format("MM/DD/YYYY");
    dataArr.push({
      TotalInvested: totalInvested,
      CoinAmount: coinAmount,
      CoinPrice: coinPrice,
      Total: total,
      date: date,
    });
  }
  return
Chart
;
}
const styles = {
  container: {
    maxWidth: 700,
    margin: "0 auto",
  },
};
src.graph.js размещен на GitHub

Это создаст массив объектов dataArr, который будет выглядеть таким образом:

Code:
[
  {TotalInvested: 200, CoinAmount: .46, CoinPrice: 460, Total: 200, date: '1/1/2016'},
  {TotalInvested: 400, CoinAmount: 1, CoinPrice: 380, Total: 200, date: '1/5/2016'},
  ...
]

.     .     .