326 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-19 17:04:19 +01:00
import React, {useRef, useState} from "react";
import { useQuoteData } from "../QueryOptions/apiQueryOptions";
import { useQueryContext } from "../context/querycontext";
import * as echarts from 'echarts';
import ReactECharts from 'echarts-for-react';
const ChartComponents: React.FC = () => {
const chartRef = useRef<ReactECharts>(null);
const { queryOptions, isLoadingBrokers, isErrorBrokers } =
useQueryContext();
const { data: quoteData, isLoading: isLoadingQuotes, isError: isErrorQuotes } =
useQuoteData(queryOptions);
const [showMidline, setShowMidline] = useState(false);
// console.log('quote data before render:', quoteData,
// 'isLoadingQuotes:', isLoadingQuotes,
// 'quotesError:', isErrorQuotes);
if (isLoadingBrokers) return <div>Loading brokers...</div>;
if (isErrorBrokers) return <div>Error loading brokers</div>;
if (isErrorQuotes) return <div>Error loading quote data</div>;
if (!quoteData?.records?.length) return <div>No quote data available</div>;
const datasetSource = quoteData.records.filter(record => record.askA !== null || record.midlineA
|| record.bidA !== null || record.askB !== null || record.bidB !== null || record.midlineB)
.map(record => ({
timestamp: record.timestamp,
askA: record.askA,
askB: record.askB,
bidA: record.bidA,
bidB: record.bidB,
midlineA: record.midlineA,
midlineB: record.midlineB,
}));
// console.log('datasetSource:', datasetSource);
const valuesA = datasetSource
.flatMap(record => [record.askA, record.bidA, record.midlineA])
.filter((value): value is number => value !== null);
const valuesB = datasetSource
.flatMap(record => [record.askB, record.bidB, record.midlineB])
.filter((value): value is number => value !== null);
const buffer = 0.00001;
// console.log('ValueA:', valuesA);
// console.log('ValueB:', valuesB);
// console.log('askA values:', datasetSource.map(r => r.askA).filter(v => v !== null));
// console.log('askB values:', datasetSource.map(r => r.askB).filter(v => v !== null));
// console.log('bidA values:', datasetSource.map(r => r.bidA).filter(v => v !== null));
// console.log('bidB values:', datasetSource.map(r => r.bidB).filter(v => v !== null));
const minValueA = valuesA.length ? Math.min(...valuesA) - buffer : 0;
const maxValueA = valuesA.length ? Math.max(...valuesA) + buffer : 100;
const minValueB = valuesB.length ? Math.min(...valuesB) - buffer : 0;
const maxValueB = valuesB.length ? Math.max(...valuesB) + buffer : 100;
// console.log('minValueA:', minValueA);
// console.log('maxValueA:', maxValueA);
// console.log('minValueB:', minValueB);
// console.log('maxValueB:', maxValueB);
//
const option = {
dataset: {
source: datasetSource,
dimensions: ['timestamp', 'askA', 'askB', 'bidA', 'bidB', 'midlineA', 'midlineB'],
},
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
const date = new Date(params[0].value[0]);
return (
`Time: ${date.toLocaleTimeString()}<br>` +
params
.map((p: any) => `${p.seriesName}: ${p.value[1] != null ? p.value[1].toFixed(5) : 'N/A'}`)
.join('<br>')
);
},
},
legend: {
data: [
`Ask A (${quoteData.brokerA}/${quoteData.symbolA})`,
`Bid A (${quoteData.brokerA}/${quoteData.symbolA})`,
`Midline A (${quoteData.brokerA}/${quoteData.symbolA})`,
`Ask B (${quoteData.brokerB}/${quoteData.symbolB})`,
`Bid B (${quoteData.brokerB}/${quoteData.symbolB})`,
`Midline B (${quoteData.brokerB}/${quoteData.symbolB})`,
],
selected: {
[`Ask A (${quoteData.brokerA}/${quoteData.symbolA})`]: !showMidline,
[`Ask B (${quoteData.brokerB}/${quoteData.symbolB})`]: !showMidline,
[`Bid A (${quoteData.brokerA}/${quoteData.symbolA})`]: !showMidline,
[`Bid B (${quoteData.brokerB}/${quoteData.symbolB})`]: !showMidline,
[`Midline A (${quoteData.brokerA}/${quoteData.symbolA})`]: showMidline,
[`Midline B (${quoteData.brokerB}/${quoteData.symbolB})`]: showMidline,
},
},
grid: {
left: '5%',
right: '5%',
bottom: '15%',
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
dataZoom: [
{
type: 'slider',
xAxisIndex: 0,
start: 80,
end: 100,
labelFormatter: (value: number) => {
const date = new Date(value);
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;
},
},
{
type: 'inside',
xAxisIndex: 0,
start: 80,
end: 100,
},
{
type: 'slider',
yAxisIndex: 0,
start: 0,
end: 100,
labelFormatter: (value: number) => value.toFixed(5),
},
{
type: 'inside',
yAxisIndex: 0,
start: 0,
end: 100,
},
{
type: 'slider',
yAxisIndex: 1,
start: 0,
end: 100,
labelFormatter: (value: number) => value.toFixed(5),
},
{
type: 'inside',
yAxisIndex: 1,
start: 0,
end: 100,
},
],
xAxis: {
type: 'time',
name: 'Timestamp',
axisLabel: {
formatter: (value: number) => {
const date = new Date(value);
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;
},
},
},
yAxis: [
{
type: 'value',
name: `${quoteData.brokerA}/${quoteData.symbolA}`,
min: minValueA,
max: maxValueA,
axisLabel: {
formatter: (value: number) => value.toFixed(5),
},
// alignTicks:true,
scale: true,
splitNumber: 10,
},
{
type: 'value',
name: `${quoteData.brokerB}/${quoteData.symbolB}`,
min: minValueB,
max: maxValueB,
position: 'right',
axisLabel: {
formatter: (value: number) => value.toFixed(5),
},
// alignTicks:true,
scale: true,
splitNumber: 10,
},
],
series: [
{
name: `Ask A (${quoteData.brokerA}/${quoteData.symbolA})`,
type: 'line',
data: datasetSource
.filter(record => record.askA !== null)
.map(record => [record.timestamp, record.askA]),
yAxisIndex: 0,
lineStyle: { color: '#007bff', width: 2 },
itemStyle: { color: '#007bff' },
showSymbol: true,
symbol: 'circle',
symbolSize: 4,
connectNulls: false,
},
{
name: `Bid A (${quoteData.brokerA}/${quoteData.symbolA})`,
type: 'line',
data: datasetSource
.filter(record => record.bidA !== null)
.map(record => [record.timestamp, record.bidA]),
yAxisIndex: 0,
lineStyle: { color: '#00b7eb', type: 'dashed', width: 2 },
itemStyle: { color: '#00b7eb' },
showSymbol: true,
symbol: 'circle',
symbolSize: 4,
connectNulls: false,
},
{
name: `Ask B (${quoteData.brokerB}/${quoteData.symbolB})`,
type: 'line',
data: datasetSource
.filter(record => record.askB !== null)
.map(record => [record.timestamp, record.askB]),
yAxisIndex: 1,
lineStyle: { color: '#ff4500', width: 2 },
itemStyle: { color: '#ff4500' },
showSymbol: true,
symbol: 'square',
symbolSize: 4,
connectNulls: false,
},
{
name: `Bid B (${quoteData.brokerB}/${quoteData.symbolB})`,
type: 'line',
data: datasetSource
.filter(record => record.bidB !== null)
.map(record => [record.timestamp, record.bidB]),
yAxisIndex: 1,
lineStyle: { color: '#ff8c00', type: 'dashed', width: 2 },
itemStyle: { color: '#ff8c00' },
showSymbol: true,
symbol: 'square',
symbolSize: 4,
connectNulls: false,
},
{
name: `Midline A (${quoteData.brokerA}/${quoteData.symbolA})`,
type: 'line',
data: datasetSource
.filter(record => record.midlineA !== null)
.map(record => [record.timestamp, record.midlineA]),
yAxisIndex: 0,
lineStyle: { color: '#28a745', width: 2 },
itemStyle: { color: '#28a745' },
showSymbol: true,
symbol: 'circle',
symbolSize: 4,
connectNulls: false,
},
{
name: `Midline B (${quoteData.brokerB}/${quoteData.symbolB})`,
type: 'line',
data: datasetSource
.filter(record => record.midlineB !== null)
.map(record => [record.timestamp, record.midlineB]),
yAxisIndex: 1,
lineStyle: { color: '#9932cc', width: 2 },
itemStyle: { color: '#9932cc' },
showSymbol: true,
symbol: 'square',
symbolSize: 4,
connectNulls: false,
},
],
};
return (
<div className= "p-4">
<h2 className="text-2xl font-bold text-gray-800 dark:text-gray-500 mb-4">Quote Chart</h2>
<div className="flex space-x-4 mb-4">
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
<input
type="radio"
name="chartMode"
checked={!showMidline}
onChange={() => setShowMidline(false)}
className="form-radio text-blue-500 focus:ring-blue-500"
/>
<span>Show Ask/Bid</span>
</label>
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
<input
type="radio"
name="chartMode"
checked={showMidline}
onChange={() => setShowMidline(true)}
className="form-radio text-blue-500 focus:ring-blue-500"
/>
<span>Show Midline</span>
</label>
</div>
<ReactECharts
echarts={echarts}
ref = {chartRef}
option={option}
style={{ height: 600, width: '100%' }}
opts={{ renderer: 'canvas' }}
theme="dark"
/>
</div>
)
};
export default ChartComponents;