Перевод статьи:
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 AveragingDollar Cost Averaging (DCA) - это инвестиционная стратегия, для которой характерно пополнение портфеля одним и тем же активом на одну и ту же сумму в долларах через регулярные временные интервалы с целью снижения краткосрочной волатильности.
Например, инвестиция в размере $ 200 в конкретную ценную бумагу или криптовалюту каждый месяц означает, что вы будете покупать больше единиц актива, когда его цена низкая, и меньше, когда цена выше. Читайте
статью Investopedia о DCA, чтобы узнать больше.
Построение графика стоимости DCA биткоина конкретного счета с течением времени требует, чтобы мы рассчитали общую стоимость счета на каждом интервале в течение определенного периода времени.
Например, если взять за интервал месяц, а за период времени - два года, то нам нужно вычислить общую стоимость счета 24 раза. Чтобы вычислить общую стоимость на определенном интервале, нам нужно умножить общее количество накопленных монет за период на цену монеты в момент покупки.
Количество накопленных за период монет может быть рассчитана путем деления суммы инвестирования на цену монеты в момент совершения покупки для каждого интервала времени.
Давайте проиллюстрируем это примером, скажем, мы планируем покупать биткоин на сумму $ 200 каждый месяц с января 2016 по май 2016.
Количество монет за первый месяц легко рассчитать, просто берем Сумму для Инвестирования ($ 200) и делим на Цену Монеты ($ 434.33) на 1 января 2016 года.
Посчитать общую стоимость тоже легко, просто возьмите Количество Монет, умноженное на текущую Цену Монеты, за первый месяц она должна равняться сумме вложенных средств ($ 200).
// amountToInvest / coinPrice
200 / 434.33 ~= .46 // Amount of Coin for the first month (Количество Монет за первый месяц)
// amountOfCoin * coinPrice
.46 * 434.33 ~= 200 // Total Value (Общая Стоимость)
Расчет Количества Монет за второй месяц немного отличается.
Во-первых, как в прошлом месяце, делим сумму инвестиций на цену монеты текущего месяца ($ 371,04). Затем добавляем это значение к сумме монет предыдущего месяца (.46).
// 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 (Общее Накопленное Количество Монет)
Чтобы рассчитать Общую Стоимость на второй месяц, мы берем Общее Накопленное Количество Монет и умножаем на текущую Цену Монеты.
// 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
Код для вычисления этих значений может выглядеть примерно так.
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 размещен на
GitHubnumOfDays - общее количество дней за период времени. В нашем случае получается 121 день между январем и маем 2016.
freqInDays - это временной интервал, через который осуществляется покупка, в данном случае он составляет 30 дней.
priceArr - это массив объектов с ценами биткойна и датами, соответствующими этой цене.
amountToInvest - это сумма в долларах, которая будет инвестирована за временной интервал, в данном случае это $ 200.
coinAmount - это общее количество монет, накопленных к текущему моменту.
totalInvested - это общая сумма инвестиций к текущему моменту.
total - общая стоимость портфеля в долларах США.
Для этих четырех значений:
TotalInvested,
CoinAmount,
CoinPrice и
Total, - мы хотим построить график изменения во времени.
freqInDays,
amountToInvest и
numOfDays будут задаваться пользователем, а цены биткоина,
priceArr, будут взяты из API CoinGecko.
. . .
Начало работыСоздаем новый проект
Creat React Appnpx create-react-app bitcoin-dca
cd bitcoin-dca
npm start
Переходим на
src/App.js и переписываем начальный код.
import React from "react";
import "./App.css";
function App() {
return (
<div className="App">
<h1 className="title">Bitcoin</h1>
</div>
);
}
export default App;
src.app.js размещен на
GitHubНаконец, переходим на
src/App.css и обновляем css-элементы, как показано ниже.
body {
background-color: #232323;
color: white;
}
.title {
color: #f7931a;
font-size: 40px;
}
.App {
text-align: center;
}
src.app.css размещен на
GitHub. . .
История Цен из API CoinGeckoAPI 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 в компоненте состояния.
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 (
<div className="App">
<h1>Bitcoin</h1>
</div>
);
}
export default App;
app.js размещен на
GitHubЧтобы передавать параметры
startDate и
endDate в понятном для человека виде, мы будем использовать библиотеку
Day.js, которая поможет нам конвертировать даты из вида, понятного человеку, во временные метки UNIX. Импортируем
dayjs и применим его расширение
advancedformat.
...
import dayjs from "dayjs";
import advancedFormat from "dayjs/plugin/advancedFormat";
dayjs.extend(advancedFormat);
...
Затем используем метод
format, принадлежащий
dayjs, чтобы конвертировать даты во временные метки UNIX внутри функции
getCoinData.
...
const getCoinData = async (startDate, endDate) => {
...
const startDateUnix = dayjs(startDate).format("X");
const endDateUnix = dayjs(endDate).format("X");
...
}
...
getCoinData.js размещен на
GitHubЗатем создаем URL описанным выше способом, добываем данные, и обновляем состояние компонента с помощью
setCoinData.
...
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 с датами, размещенными в верхней части компонента.
...
useEffect(() => {
getCoinData(startDate, endDate);
}, []);
...
Существует четыре UI-состояния, которые нам нужно обработать:
noData,
loading,
error и
data. Добавим несколько условных операторов под Хуком
useEffect, как это показано ниже.
...
let content = <div>No Data</div>;
if (coinData && coinData.prices && coinData.prices.length > 0)
content = <div>Data</div>;
if (isLoading) content = <div>Loading</div>;
if (error) content = <div>{error}</div>;
return (
<div className="App">
<h1 className="title">Bitcoin</h1>
{content}
</div>
);
...
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, как показано ниже.
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 <div>Totals</div>;
}
src.totals.js размещен на
GitHubДля того, чтобы выводить на экран эти значения, создаем другой компонент
src/Totaljs, применяя простые элементы дизайна.
import React from "react";
export default function Total({ title, value }) {
return (
<div style={styles.row}>
<h4 style={styles.title}>{title}:</h4>
<h4 style={styles.value}>{value}</h4>
</div>
);
}
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, которая будет округлять числа, чтоб они выглядели лучше.
export default function round(num, digit) {
return +(Math.round(num + "e+" + digit) + "e-" + digit);
}
Импортируем оба компонента
round и
Total в компонент
Totals.
А затем создаем несколько компонентов
Total, передавая описание в свойстве
title и актуальное значение в свойстве
value. Также мы можем применить к этим значениям функцию round.
// ./src/Totals.js
import Total from "./Total";
import round from "./round";
...
return (
<div>
<Total title={"Ending Value (USD)"} value={`$${round(endTotal, 2)}`} />
<Total title={"Amount of Coin (BTC)"} value={round(totalCoinAmount, 5)} />
<Total
title={"Amount Invested (USD)"}
value={`$${round(totalInvested, 2)}`}
/>
<Total title={"Gained (USD)"} value={`$${round(numberGained, 2)}`} />
<Total title={"Gained (%)"} value={`${round(percentGained, 2)}%`} />
</div>
);
...
src.totals.js размещен на
GitHubНаконец, импортируем
Totals в
App.js и заменяем состояние «data» на компонент
Totals.
...
import Totals from "./Totals";
...
let content = <div>No Data</div>;
if (coinData && coinData.prices && coinData.prices.length > 0)
content = (
<Totals
priceArr={coinData.prices}
freqInDays={freqInDays}
amountToInvest={amountToInvest}
/>
);
if (isLoading) content = <div>Loading</div>;
if (error) content = <div>{error}</div>;
...
Totals.js размещен на
GitHub. . .
Создание Массива для ГрафикаПриведенный ниже код вам уже должен быть хорошо знаком из секции
DCA, пожалуйста, ознакомьтесь с этой секцией, чтобы узнать, как этот код работает.
Один момент, что мы снова используем
dayjs, чтобы хранить информацию в удобном для человеческого восприятия виде. Создаем новый файл
./src/Graph.js, как показано ниже:
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 <div style={styles.container}>Chart</div>;
}
const styles = {
container: {
maxWidth: 700,
margin: "0 auto",
},
};
src.graph.js размещен на
GitHubЭто создаст массив объектов
dataArr, который будет выглядеть таким образом:
[
{TotalInvested: 200, CoinAmount: .46, CoinPrice: 460, Total: 200, date: '1/1/2016'},
{TotalInvested: 400, CoinAmount: 1, CoinPrice: 380, Total: 200, date: '1/5/2016'},
...
]
. . .