Skip to content

Instantly share code, notes, and snippets.

@gigamesh
Created March 25, 2021 05:42
Show Gist options
  • Save gigamesh/2fc2fedca04bed483da89e18efe37183 to your computer and use it in GitHub Desktop.
Save gigamesh/2fc2fedca04bed483da89e18efe37183 to your computer and use it in GitHub Desktop.
import React from 'react';
import moment from 'moment';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
Brush,
ResponsiveContainer,
} from 'recharts';
import {
Box,
FormLabel,
Text,
SimpleGrid,
useMediaQuery,
} from '@chakra-ui/react';
import Button from './Button';
import { database as db } from '../initFirebase';
import { formatNumber } from '../helpers';
const Charts = React.memo(function Chart() {
const [isSmallScreen] = useMediaQuery('(max-width: 600px)');
const [chartData, setChartData] = React.useState(null);
const [brushBounds, setBrushBounds] = React.useState({
start: 0,
end: 1000,
});
const prevTimeoutId = React.useRef(null);
const allChartData = React.useRef(null);
const calculateDelta = rawChartData => {
// console.log(new Date(rawChartData[0].time * 1000));
const ethStartPrice = rawChartData[0].ethPrice;
const btcStartPrice = rawChartData[0].btcPrice;
const processedData = [];
let funds = {
USD: 1,
BTC: 0,
ETH: 0,
};
for (const index in rawChartData) {
const candle = rawChartData[index];
// start by buying holding at index === 0
if (+index === 0) {
if (candle.holding === 'BTC-USD') {
funds.BTC = funds.USD / candle.btcPrice;
} else {
funds.ETH = funds.USD / candle.ethPrice;
}
funds.USD = 0;
}
// If new holding, buy/sell depending on new holding
if (index > 0 && rawChartData[index - 1].holding !== candle.holding) {
if (rawChartData[index - 1].holding === 'BTC-USD') {
funds.USD = funds.BTC * candle.btcPrice;
funds.ETH = funds.USD / candle.ethPrice;
funds.BTC = 0;
} else {
funds.USD = funds.ETH * candle.ethPrice;
funds.BTC = funds.USD / candle.btcPrice;
funds.ETH = 0;
}
funds.USD = 0;
}
const btcDelta = candle.btcPrice / btcStartPrice - 1;
const ethDelta = candle.ethPrice / ethStartPrice - 1;
const finFrenDelta =
candle.holding === 'BTC-USD'
? funds.BTC * candle.btcPrice - 1
: funds.ETH * candle.ethPrice - 1;
processedData.push({
time: candle.time,
btcDelta,
ethDelta,
finFrenDelta,
});
}
return processedData;
};
React.useEffect(() => {
(async () => {
const rawChartData = Object.values(
(await db.ref('chart-data').once('value')).val()
);
setBrushBounds({ start: 0, end: rawChartData.length - 1 });
const processedData = calculateDelta(rawChartData);
allChartData.current = rawChartData;
setChartData(processedData);
})();
}, []);
const handleReset = () => {
const processedData = calculateDelta(allChartData.current);
setBrushBounds({ start: 0, end: allChartData.current.length - 1 });
setChartData(processedData);
};
const handleBrushMoved = ({ startIndex, endIndex }) => {
setBrushBounds({ start: startIndex, end: endIndex });
if (prevTimeoutId.current) {
clearTimeout(prevTimeoutId.current);
}
const id = setTimeout(() => {
const processedData = calculateDelta(
allChartData.current.filter((c, i) => i >= startIndex && i < endIndex)
);
const startFiller = {
time: processedData[0].time,
btcDelta: 0,
ethDelta: 0,
finFrenDelta: 0,
};
const endFiller = { ...startFiller };
endFiller.time = processedData[processedData.length - 1].time;
const startPad = Array(startIndex + 1).fill(startFiller);
const endPad = Array(allChartData.current.length - endIndex - 1).fill(
endFiller
);
const finalizedData = [...startPad, ...processedData, ...endPad];
setChartData(finalizedData);
}, 400);
prevTimeoutId.current = id;
};
const CustomTooltip = ({ payload }) => {
// console.log(payload);
if (payload.length) {
const tooltipData = payload.map(item => ({
key:
item.dataKey === 'ethDelta'
? 'ETH Hodl'
: item.dataKey === 'btcDelta'
? 'BTC Hodl'
: 'FinFren',
value: item.value,
color: item.color,
}));
return (
<Box
borderRadius="5px"
bg="background"
border="1px solid rgba(79, 209, 197, 0.5)"
boxShadow="0 0 3px rgb(77, 209, 196, 0.3), 0 0 6px rgb(77, 209, 196, 0.2), 0 0 12px rgb(77, 209, 196, 0.1)"
py={4}
pl={4}
pr={2}
>
<div>
<Text mt={0} textAlign="center" color="white" fontSize="14px">
{moment(payload[0].payload.time * 1000).format('ll')}
</Text>
<SimpleGrid columns={2} spacingX={4}>
{tooltipData.reverse().map(item => (
<React.Fragment key={item.key}>
<Text
color={item.color}
fontWeight="bold"
my={0}
ml={4}
fontSize="14px"
textAlign="right"
>
{item.key}:{' '}
</Text>
<Text key={item.key} my={0} fontSize="14px">
{formatNumber((item.value * 100).toFixed(2))}%
</Text>
</React.Fragment>
))}
</SimpleGrid>
</div>
</Box>
);
} else return null;
};
return (
chartData && (
<Box
pos="relative"
pb={10}
overflow="auto"
d={['block', null, null, 'flex']}
flexDir="column"
justifyContent="center"
alignItems="center"
>
<FormLabel pl={10} fontSize="sm" fontWeight="300" w="100%">
ROI
</FormLabel>
<Box w="100%" height="500px">
<ResponsiveContainer>
<LineChart
width={920}
height={400}
data={chartData}
margin={{
top: 5,
left: isSmallScreen ? 0 : 30,
right: isSmallScreen ? 20 : 40,
}}
>
<CartesianGrid strokeDasharray="3 3" style={{ opacity: 0.2 }} />
<XAxis
height={35}
type="number"
tickCount={isSmallScreen ? 8 : 12}
interval={1}
minTickGap={0}
tick={{
fontSize: '12px',
fill: '#eee',
}}
tickMargin={10}
dataKey="time"
tickFormatter={time =>
moment(time * 1000).format(
isSmallScreen ? 'D/M/YY' : 'MMM D, YYYY'
)
}
domain={['dataMin', 'dataMax']}
/>
<Brush
dataKey="time"
data={allChartData?.current || []}
height={20}
stroke="#4FD1C5"
fill="transparent"
travellerWidth={10}
tickFormatter={() => ''}
onChange={handleBrushMoved}
startIndex={brushBounds.start}
endIndex={brushBounds.end}
/>
<YAxis
tickCount={8}
style={{ fontSize: '14px', fill: '#eee' }}
tickFormatter={num =>
num >= 0
? isSmallScreen
? formatNumber(Math.round(num)) + 'x'
: formatNumber(Math.round(num * 100)) + '%'
: ''
}
/>
<Tooltip content={<CustomTooltip />} />
<Legend
wrapperStyle={{ paddingTop: '16px', fontSize: '22px' }}
formatter={value => {
switch (value) {
case 'ethDelta': {
return 'ETH';
}
case 'btcDelta': {
return 'BTC';
}
case 'finFrenDelta': {
return 'FinFren';
}
}
return value;
}}
/>
<Line
type="linear"
dataKey="btcDelta"
stroke="#8884d8"
dot={false}
animationDuration={300}
/>
<Line
type="linear"
dataKey="ethDelta"
dot={false}
stroke="#c98282"
animationDuration={300}
/>
<Line
type="monotone"
dataKey="finFrenDelta"
dot={false}
stroke="#4FD1C5"
animationDuration={300}
/>
</LineChart>
</ResponsiveContainer>
</Box>
<Button
pos="absolute"
right={'20px'}
bottom={isSmallScreen ? '30px' : '85px'}
fontSize="14px"
height="30px"
px="10px"
onClick={handleReset}
>
Reset
</Button>
<Text fontSize="xs" textAlign="center" opacity={0.6}>
*The currently-running strategy started with ETH on June 10, 2016
</Text>
</Box>
)
);
});
export default Charts;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment