Skip to content

Instantly share code, notes, and snippets.

@yuryprokashev
Created June 14, 2024 13:35
Show Gist options
  • Save yuryprokashev/a5f1e7c385bda342541d8c2a3245e5f8 to your computer and use it in GitHub Desktop.
Save yuryprokashev/a5f1e7c385bda342541d8c2a3245e5f8 to your computer and use it in GitHub Desktop.
import React from 'react';
import {
DomainTuple,
VictoryAxis,
VictoryCandlestick,
VictoryChart,
VictoryPrimitiveShapeProps,
VictoryZoomContainer
} from 'victory';
import { PageProps } from './types';
import { VictoryClosedOrder } from "../component/VictoryClosedOrder";
import dayjs from "dayjs";
import { useLocation } from "react-router-dom";
import { useAppSelector } from "../redux/hooks";
import { selectSymbolParameters } from "../redux/slice/symbolParameterSlice";
import { useTheme } from "@mui/material";
type VictoryCandle = {
x: Date,
open: number,
close: number,
high: number,
low: number
}
type ChartState = {
zoomDomain: {
x: DomainTuple,
y: DomainTuple
}
}
const defaultChart = {
msPerRangePoint: 16539428,
candleWidth: 4
}
const OrderPage: React.FC<PageProps> = ({ thunks }) => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const symbolKey = queryParams.get('symbol_key');
const symbolParameterState = useAppSelector(selectSymbolParameters);
const currentSymbolParameterState = symbolParameterState?.map?.[symbolKey ?? ''] ?? null;
const victoryCandles = currentSymbolParameterState ? currentSymbolParameterState?.data?.['timestamp'].map((timestamp: string | number, index: number) => {
return {
x: dayjs(timestamp).toDate(),
open: currentSymbolParameterState?.data?.['open'][index] as number,
close: currentSymbolParameterState?.data?.['close'][index] as number,
high: currentSymbolParameterState?.data?.['high'][index] as number,
low: currentSymbolParameterState?.data?.['low'][index] as number,
} as VictoryCandle;
}) : [];
const entryPrice = parseFloat(queryParams.get('entry_price') ?? '0');
const takeProfitPrice = parseFloat(queryParams.get('tp_price') ?? '0');
const stopLossPrice = parseFloat(queryParams.get('sl_price') ?? '0');
const orderEntry = dayjs(queryParams.get('entry_time')).toDate();
const orderClose = dayjs(queryParams.get('close_time')).toDate();
const orderDuration = dayjs(orderClose).diff(dayjs(orderEntry), 'ms');
const getEntireDomain = (props: VictoryPrimitiveShapeProps) => {
const { data } = props;
if (!data) return { x: [0, 0] as DomainTuple, y: [0, 0] as DomainTuple };
return {
y: [Math.min(...data.map((d: { y: number }) => d.y)), Math.max(...data.map((d: { y: number }) => d.y))] as DomainTuple,
x: [Math.min(...data.map((d: { x: Date }) => d.x)), Math.max(...data.map((d: { x: Date }) => d.x))] as DomainTuple
};
};
const getZoomedCandles = (props: VictoryPrimitiveShapeProps): VictoryCandle[] => {
const { data } = props;
const { x: xDomain } = chartState.zoomDomain;
const [minX, maxX] = xDomain;
return data?.filter((d: VictoryCandle) => {
return d.x >= minX && d.x <= maxX;
});
}
const orderFitYDomain = [
Math.min(entryPrice, takeProfitPrice, stopLossPrice) * 0.95,
Math.max(entryPrice, takeProfitPrice, stopLossPrice) * 1.05
] as DomainTuple;
const orderFitXDomain = [
dayjs(orderEntry).subtract(1, 'month').toDate(),
dayjs(orderClose).add(1, 'month').toDate()
] as DomainTuple;
const [chartState, setChartState] = React.useState<ChartState>({
zoomDomain: {
x: orderFitXDomain,
y: orderFitYDomain
}
});
const handleZoomDomainChange = (domain: { x: DomainTuple }, props: any) => {
console.log('props', props)
setChartState({
zoomDomain: {
x: [
dayjs(domain.x[0]).toDate(),
dayjs(domain.x[1]).toDate()
],
y: orderFitYDomain
}
});
};
const theme = useTheme();
const candleColors = {
positive: theme.palette.success.main,
negative: theme.palette.error.main
};
const candleWidth = (props: any) => {
const { scale } = props;
const { x } = scale;
const range = x.range();
const domain = x.domain();
const rangeSize = range[1] - range[0];
const domainSize = domain[1] - domain[0];
const currentMsPerRangePoint = domainSize / rangeSize;
const candleWidth = defaultChart.candleWidth * defaultChart.msPerRangePoint / currentMsPerRangePoint;
return Math.max(
Math.floor(candleWidth),
1
);
};
const entireDomain = getEntireDomain({ data: victoryCandles });
// console.log('chartState.zoomDomain', chartState.zoomDomain)
// @ts-ignore
console.log('chartState.zoomDomain.width', chartState.zoomDomain.x[1] - chartState.zoomDomain.x[0])
const zoomedCandles = getZoomedCandles({ data: victoryCandles });
// console.log('zoomedCandles', zoomedCandles)
return (
<div>
<h1>Order Page</h1>
<VictoryChart
scale={{ x: "time" }}
domain={entireDomain}
containerComponent={
// @ts-ignore
<VictoryZoomContainer
onZoomDomainChange={handleZoomDomainChange}
zoomDimension="x"
zoomDomain={chartState.zoomDomain}
minimumZoom={{x: orderDuration}}
/>
}
>
<VictoryAxis />
<VictoryAxis dependentAxis />
<VictoryCandlestick
data={zoomedCandles}
candleColors={candleColors}
candleWidth={candleWidth}
wickStrokeWidth={1}
style={{
data: {
strokeWidth: 0
}
}}
/>
<VictoryClosedOrder
orderEntry={orderEntry}
orderClose={orderClose}
entryPrice={entryPrice}
takeProfitPrice={takeProfitPrice}
stopLossPrice={stopLossPrice}
/>
</VictoryChart>
</div>
);
};
export default OrderPage;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment