updated
This commit is contained in:
parent
bbf5931c52
commit
7bca898286
5
quote_plotter_client/public/img/green.svg
Normal file
5
quote_plotter_client/public/img/green.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||||
|
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(254.59340659340654 254.59340659340654) rotate(180) scale(2.81 2.81)">
|
||||||
|
<path d="M 46.969 89.104 c -1.041 1.194 -2.897 1.194 -3.937 0 L 13.299 54.989 c -0.932 -1.072 -0.171 -2.743 1.25 -2.743 h 14.249 V 1.91 c 0 -1.055 0.855 -1.91 1.91 -1.91 h 28.584 c 1.055 0 1.91 0.855 1.91 1.91 v 50.336 h 14.249 c 1.421 0 2.182 1.671 1.25 2.743 L 46.969 89.104 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(101,214,41); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 974 B |
5
quote_plotter_client/public/img/red.svg
Normal file
5
quote_plotter_client/public/img/red.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||||
|
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
|
||||||
|
<path d="M 46.969 89.104 c -1.041 1.194 -2.897 1.194 -3.937 0 L 13.299 54.989 c -0.932 -1.072 -0.171 -2.743 1.25 -2.743 h 14.249 V 1.91 c 0 -1.055 0.855 -1.91 1.91 -1.91 h 28.584 c 1.055 0 1.91 0.855 1.91 1.91 v 50.336 h 14.249 c 1.421 0 2.182 1.671 1.25 2.743 L 46.969 89.104 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(214,41,41); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 961 B |
@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import { type RawBrokerSymbol, type QueryOptions, type QuoteData, type QuoteRecord } from "./dataTypes";
|
import { type RawBrokerSymbol, type QueryOptions, type QuoteData } from "./dataTypes";
|
||||||
|
|
||||||
|
|
||||||
export const useBrokerSymbols = () => {
|
export const useBrokerSymbols = () => {
|
||||||
return useQuery<RawBrokerSymbol, Error>({
|
return useQuery<RawBrokerSymbol, Error>({
|
||||||
queryKey: ['brokers'],
|
queryKey: ['brokers'],
|
||||||
queryFn: async(): Promise<RawBrokerSymbol> => {
|
queryFn: async(): Promise<RawBrokerSymbol> => {
|
||||||
const response = await fetch('http://localhost:8000/api/quotes/brokers&symbols', {
|
const response = await fetch('http://localhost:8000/api/brokers&symbols', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -17,7 +17,6 @@ export const useBrokerSymbols = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
// console.error('broker and symbol data fetch error:', errorData);
|
// console.error('broker and symbol data fetch error:', errorData);
|
||||||
throw new Error('Unable to fetch broker and symbols');
|
throw new Error('Unable to fetch broker and symbols');
|
||||||
}
|
}
|
||||||
@ -38,7 +37,7 @@ function quoteDataLink(params: QueryOptions): string {
|
|||||||
symbol_b: params.symbolB,
|
symbol_b: params.symbolB,
|
||||||
},
|
},
|
||||||
{ skipEmptyString: true, skipNull: true });
|
{ skipEmptyString: true, skipNull: true });
|
||||||
return `http://localhost:8000/api/quotes/api/data?${query}`;
|
return `http://localhost:8000/api/data?${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useQuoteData = (params: QueryOptions) => {
|
export const useQuoteData = (params: QueryOptions) => {
|
||||||
@ -54,54 +53,11 @@ export const useQuoteData = (params: QueryOptions) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
// console.error('Quote data fetch error:', errorData);
|
// console.error('Quote data fetch error:', errorData);
|
||||||
throw new Error('Unable to fetch quote data');
|
throw new Error('Unable to fetch quote data');
|
||||||
}
|
}
|
||||||
const quoteData = await response.json();
|
const quoteData = await response.json();
|
||||||
// console.log('Fetched quote data:', quoteData);
|
return quoteData as QuoteData;
|
||||||
|
|
||||||
const records = quoteData.data || [];
|
|
||||||
// console.log('Records:', records);
|
|
||||||
const recordsA = records.filter(
|
|
||||||
(record: QuoteRecord) => record.broker === params.brokerA && record.symbol
|
|
||||||
=== params.symbolA
|
|
||||||
);
|
|
||||||
const recordsB = records.filter(
|
|
||||||
(record: QuoteRecord) => record.broker === params.brokerB && record.symbol
|
|
||||||
=== params.symbolB
|
|
||||||
);
|
|
||||||
// console.log('recordsA:', recordsA, 'recordsB:', recordsB);
|
|
||||||
if (!recordsA || !recordsB) {
|
|
||||||
throw new Error('No matching records found for the selected brokers and symbols');
|
|
||||||
}
|
|
||||||
const timestamps = [...new Set([...recordsA, ...recordsB].map(record => record.timestamp))
|
|
||||||
].sort();
|
|
||||||
const quoteDataResult: QuoteData = {
|
|
||||||
brokerA: params.brokerA,
|
|
||||||
brokerB: params.brokerB,
|
|
||||||
symbolA: params.symbolA,
|
|
||||||
symbolB: params.symbolB,
|
|
||||||
records: timestamps.map(timestamp => {
|
|
||||||
const recordA = recordsA.find((record: QuoteRecord) =>
|
|
||||||
record.timestamp === timestamp) || {};
|
|
||||||
const recordB = recordsB.find((record: QuoteRecord) =>
|
|
||||||
record.timestamp === timestamp) || {};
|
|
||||||
return {
|
|
||||||
askA: recordA.ask_price ?? null,
|
|
||||||
bidA: recordA.bid_price ?? null,
|
|
||||||
askB: recordB.ask_price ?? null,
|
|
||||||
bidB: recordB.bid_price ?? null,
|
|
||||||
spreadA: recordA.spread ?? null,
|
|
||||||
spreadB: recordB.spread ?? null,
|
|
||||||
midlineA: recordA.midline ?? null,
|
|
||||||
midlineB: recordB.midline ?? null,
|
|
||||||
timestamp,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
// console.log('Processed quote data:', quoteDataResult);
|
|
||||||
return quoteDataResult;
|
|
||||||
},
|
},
|
||||||
enabled: !!params.brokerA && !!params.symbolA && !!params.brokerB && !!params.symbolB,
|
enabled: !!params.brokerA && !!params.symbolA && !!params.brokerB && !!params.symbolB,
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export interface QueryOptions {
|
|||||||
export interface QuoteRecord {
|
export interface QuoteRecord {
|
||||||
bid_price: number;
|
bid_price: number;
|
||||||
ask_price: number;
|
ask_price: number;
|
||||||
|
direction: string;
|
||||||
spread: number;
|
spread: number;
|
||||||
midline: number;
|
midline: number;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@ -21,22 +22,29 @@ export interface QuoteRecord {
|
|||||||
symbol: string;
|
symbol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QuoteRecordData {
|
||||||
|
askA: number | null;
|
||||||
|
askB: number | null;
|
||||||
|
bidA: number | null;
|
||||||
|
bidB: number | null;
|
||||||
|
directionA?: string;
|
||||||
|
directionB?: string;
|
||||||
|
spreadA: number | null;
|
||||||
|
spreadB: number | null;
|
||||||
|
midlineA: number | null;
|
||||||
|
midlineB: number | null;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface QuoteData {
|
export interface QuoteData {
|
||||||
brokerA: string;
|
brokerA: string;
|
||||||
symbolA: string;
|
symbolA: string;
|
||||||
brokerB: string;
|
brokerB: string;
|
||||||
symbolB: string;
|
symbolB: string;
|
||||||
records: {
|
records: QuoteRecordData[];
|
||||||
askA: number | null;
|
lastDataPoints?: Record<string, { timestamp: string | null; value: number | null }>;
|
||||||
askB: number | null;
|
markableRecords?: QuoteRecordData[];
|
||||||
bidA: number | null;
|
|
||||||
bidB: number | null;
|
|
||||||
spreadA: number | null;
|
|
||||||
spreadB: number | null;
|
|
||||||
midlineA: number | null;
|
|
||||||
midlineB: number | null;
|
|
||||||
timestamp: string;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryContextType {
|
export interface QueryContextType {
|
||||||
|
|||||||
@ -1,75 +1,360 @@
|
|||||||
import React, {useRef, useState} from "react";
|
import React, {useRef, useState, useEffect} from "react";
|
||||||
import { useQuoteData } from "../QueryOptions/apiQueryOptions";
|
import { useQuoteData } from "../QueryOptions/apiQueryOptions";
|
||||||
import { useQueryContext } from "../context/querycontext";
|
import { useQueryContext } from "../context/querycontext";
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import { type QuoteData } from "../QueryOptions/dataTypes";
|
||||||
|
|
||||||
|
|
||||||
const ChartComponents: React.FC = () => {
|
const ChartComponents: React.FC = () => {
|
||||||
const chartRef = useRef<ReactECharts>(null);
|
const chartRef = useRef<ReactECharts>(null);
|
||||||
const { queryOptions, isLoadingBrokers, isErrorBrokers } =
|
const { queryOptions, isLoadingBrokers, isErrorBrokers } =
|
||||||
useQueryContext();
|
useQueryContext();
|
||||||
|
const {data: quoteData, isError: isErrorQuotes } =
|
||||||
|
useQuoteData(queryOptions) as { data: QuoteData | undefined; isLoading: boolean; isError: boolean };
|
||||||
|
|
||||||
|
|
||||||
const { data: quoteData, isLoading: isLoadingQuotes, isError: isErrorQuotes } =
|
const [showMidline, setShowMidline] = useState(false);
|
||||||
useQuoteData(queryOptions);
|
|
||||||
|
|
||||||
const [showMidline, setShowMidline] = useState(false);
|
|
||||||
|
|
||||||
|
|
||||||
// console.log('quote data before render:', quoteData,
|
const [latestValues, setLatestValues] = useState({
|
||||||
// 'isLoadingQuotes:', isLoadingQuotes,
|
lastTimestampA: null as number | null,
|
||||||
// 'quotesError:', isErrorQuotes);
|
lastTimestampB: null as number | null,
|
||||||
|
lastAskA: null as number | null,
|
||||||
|
lastBidA: null as number | null,
|
||||||
|
lastAskB: null as number | null,
|
||||||
|
lastBidB: null as number | null,
|
||||||
|
lastMidlineA: null as number | null,
|
||||||
|
lastMidlineB: null as number | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (quoteData?.records) {
|
||||||
|
const newLatestValues = {
|
||||||
|
askA: null as number | null,
|
||||||
|
bidA: null as number | null,
|
||||||
|
midlineA: null as number | null,
|
||||||
|
spreadA: null as number | null,
|
||||||
|
timestampA: null as string | null,
|
||||||
|
askB: null as number | null,
|
||||||
|
bidB: null as number | null,
|
||||||
|
midlineB: null as number | null,
|
||||||
|
spreadB: null as number | null,
|
||||||
|
timestampB: null as string | null,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const record of quoteData.records) {
|
||||||
|
if (newLatestValues.askA === null && record.askA !== null) {
|
||||||
|
newLatestValues.askA = record.askA;
|
||||||
|
newLatestValues.bidA = record.bidA;
|
||||||
|
newLatestValues.midlineA = record.midlineA;
|
||||||
|
newLatestValues.spreadA = record.spreadA;
|
||||||
|
newLatestValues.timestampA = record.timestamp;
|
||||||
|
// console.log('Updated A in filteredData at:', record.timestamp, 'with askA:', record.askA);
|
||||||
|
}
|
||||||
|
if (newLatestValues.askB === null && record.askB !== null) {
|
||||||
|
newLatestValues.askB = record.askB;
|
||||||
|
newLatestValues.bidB = record.bidB;
|
||||||
|
newLatestValues.midlineB = record.midlineB;
|
||||||
|
newLatestValues.spreadB = record.spreadB;
|
||||||
|
newLatestValues.timestampB = record.timestamp;
|
||||||
|
// console.log('Updated B in filteredData at:', record.timestamp, 'with askB:', record.askB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastTimestampA = newLatestValues.timestampA ? new Date(newLatestValues.timestampA).getTime() : (quoteData.records[0]?.timestamp ? new Date(quoteData.records[0].timestamp).getTime() : null);
|
||||||
|
const lastTimestampB = newLatestValues.timestampB ? new Date(newLatestValues.timestampB).getTime() : (quoteData.records[0]?.timestamp ? new Date(quoteData.records[0].timestamp).getTime() : null);
|
||||||
|
setLatestValues({
|
||||||
|
lastTimestampA,
|
||||||
|
lastTimestampB,
|
||||||
|
lastAskA: newLatestValues.askA,
|
||||||
|
lastBidA: newLatestValues.bidA,
|
||||||
|
lastAskB: newLatestValues.askB,
|
||||||
|
lastBidB: newLatestValues.bidB,
|
||||||
|
lastMidlineA: newLatestValues.midlineA,
|
||||||
|
lastMidlineB: newLatestValues.midlineB,
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartInstance = chartRef.current?.getEchartsInstance();
|
||||||
|
if (chartInstance) {
|
||||||
|
const timestamps = quoteData.records.map(record => new Date(record.timestamp).getTime());
|
||||||
|
if (timestamps.length > 0) {
|
||||||
|
const maxTimestamp = Math.max(...timestamps);
|
||||||
|
const minTimestamp = Math.min(...timestamps);
|
||||||
|
const range = maxTimestamp - minTimestamp;
|
||||||
|
const last2Percent = minTimestamp + range * 0.98;
|
||||||
|
|
||||||
|
chartInstance.setOption({
|
||||||
|
dataZoom: [
|
||||||
|
{ startValue: last2Percent, endValue: maxTimestamp },
|
||||||
|
{ type: 'inside', startValue: last2Percent, endValue: maxTimestamp },
|
||||||
|
],
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
const updateMarkLine = () => {
|
||||||
|
if (newLatestValues.askA) {
|
||||||
|
const option = chartInstance.getOption();
|
||||||
|
option.series = (option.series as any[]).map((series: any) => {
|
||||||
|
if (series.name === `Ask A (${brokerA}/${symbolA})`) {
|
||||||
|
return {
|
||||||
|
...series,
|
||||||
|
markLine: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
yAxis: newLatestValues.askA,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#007bff',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: () => newLatestValues.askA!.toFixed(5),
|
||||||
|
},
|
||||||
|
silent: true,
|
||||||
|
animation: false,
|
||||||
|
clip: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return series;
|
||||||
|
});
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMarkLine();
|
||||||
|
|
||||||
|
chartInstance.on('dataZoom', updateMarkLine);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
chartInstance.off('dataZoom', updateMarkLine);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [quoteData]);
|
||||||
|
|
||||||
|
|
||||||
if (isLoadingBrokers) return <div>Loading brokers...</div>;
|
if (isLoadingBrokers) return <div>Loading brokers...</div>;
|
||||||
if (isErrorBrokers) return <div>Error loading brokers</div>;
|
if (isErrorBrokers) return <div>Error loading brokers</div>;
|
||||||
if (isErrorQuotes) return <div>Error loading quote data</div>;
|
if (isErrorQuotes) return <div>Error loading quote data</div>;
|
||||||
if (!quoteData?.records?.length) return <div>No quote data available</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
|
const { records: filteredData, brokerA, symbolA, brokerB, symbolB } = quoteData;
|
||||||
.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));
|
const valuesA = filteredData
|
||||||
// console.log('askB values:', datasetSource.map(r => r.askB).filter(v => v !== null));
|
.map(records => [records.askA, records.bidA, records.midlineA])
|
||||||
// console.log('bidA values:', datasetSource.map(r => r.bidA).filter(v => v !== null));
|
.flat()
|
||||||
// console.log('bidB values:', datasetSource.map(r => r.bidB).filter(v => v !== null));
|
.filter((v): v is number => v !== null);
|
||||||
|
const valuesB = filteredData
|
||||||
const minValueA = valuesA.length ? Math.min(...valuesA) - buffer : 0;
|
.map(records => [records.askB, records.bidB, records.midlineB])
|
||||||
const maxValueA = valuesA.length ? Math.max(...valuesA) + buffer : 100;
|
.flat()
|
||||||
const minValueB = valuesB.length ? Math.min(...valuesB) - buffer : 0;
|
.filter((v): v is number => v !== null);
|
||||||
const maxValueB = valuesB.length ? Math.max(...valuesB) + buffer : 100;
|
const buffer = 0.00001;
|
||||||
// console.log('minValueA:', minValueA);
|
|
||||||
// console.log('maxValueA:', maxValueA);
|
|
||||||
// console.log('minValueB:', minValueB);
|
|
||||||
// console.log('maxValueB:', maxValueB);
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const option = {
|
const minValueA = valuesA.length ? Math.min(...valuesA) - buffer : 0;
|
||||||
|
const maxValueA = valuesA.length ? Math.max(...valuesA) + buffer : 100;
|
||||||
|
const adjustedMinValueA = Math.min(minValueA, latestValues.lastAskA || minValueA) - buffer;
|
||||||
|
const adjustedMaxValueA = Math.max(maxValueA, latestValues.lastAskA || maxValueA) + buffer;
|
||||||
|
const minValueB = valuesB.length ? Math.min(...valuesB) - buffer : 0;
|
||||||
|
const maxValueB = valuesB.length ? Math.max(...valuesB) + buffer : 100;
|
||||||
|
const adjustedMinValueB = Math.min(minValueB, latestValues.lastAskB || minValueB) - buffer;
|
||||||
|
const adjustedMaxValueB = Math.max(maxValueB, latestValues.lastAskB || maxValueB) + buffer;
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
// lastTimestampA,
|
||||||
|
// lastTimestampB,
|
||||||
|
lastAskA,
|
||||||
|
lastBidA,
|
||||||
|
lastAskB,
|
||||||
|
lastBidB,
|
||||||
|
// lastMidlineA,
|
||||||
|
// lastMidlineB,
|
||||||
|
} = latestValues;
|
||||||
|
|
||||||
|
// const firstTimestamp = filteredData.at(0)?.timestamp;
|
||||||
|
|
||||||
|
// console.log(`Last Timestamp A: ${lastTimestampA} last Ask A: ${lastAskA} to First Timestamp ${firstTimestamp}`);
|
||||||
|
// console.log(`Last Ask A: ${lastAskA}`);
|
||||||
|
// console.log('Last Bid A:', lastBidA);
|
||||||
|
// console.log('Last Timestamp B:', lastTimestampB);
|
||||||
|
// console.log('Last Ask B:', lastAskB);
|
||||||
|
// console.log('Last Bid B:', lastBidB);
|
||||||
|
// console.log('Last Midline A:', lastMidlineA);
|
||||||
|
// console.log('Last Midline B:', lastMidlineB);
|
||||||
|
|
||||||
|
// console.log('MarkLine Data for Ask A:', lastAskA ? [{ yAxis: lastAskA }] : 'Invalid (null)');
|
||||||
|
|
||||||
|
const typeDataA = filteredData.filter(record => record.directionA === 'buy' || record.directionA === 'sell');
|
||||||
|
const typeDataB = filteredData.filter(record => record.directionB === 'buy' || record.directionB === 'sell');
|
||||||
|
|
||||||
|
|
||||||
|
const typeDataPointBidA = typeDataA.map(record => ({
|
||||||
|
coord: [record.timestamp, record.directionA === 'buy' ? record.askA : record.bidA],
|
||||||
|
name: record.directionA,
|
||||||
|
itemStyle: { color: record.directionA === 'buy' ? '#00ff00' : '#ff0000' },
|
||||||
|
symbolRotate: record.directionA === 'buy' ? 0 : 180,
|
||||||
|
symbolOffset: record.directionA === 'buy' ? [0, 10] : [0, -10],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const typeDataPointBidB = typeDataB.map(record => ({
|
||||||
|
coord: [record.timestamp, record.directionB === 'buy' ? record.askB : record.bidB],
|
||||||
|
name: record.directionB,
|
||||||
|
itemStyle: { color: record.directionB === 'buy' ? '#00ff00' : '#ff0000' },
|
||||||
|
symbolRotate: record.directionB === 'buy' ? 0 : 180,
|
||||||
|
symbolOffset: record.directionB === 'buy' ? [0, 10] : [0, -10],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const markPointDataA = {
|
||||||
|
symbol: 'arrow',
|
||||||
|
symbolSize: 15,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: (param: any) => param.data.name === 'buy' ? 'Buy' : 'Sell',
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'white'
|
||||||
|
},
|
||||||
|
data: typeDataPointBidA
|
||||||
|
};
|
||||||
|
|
||||||
|
const markPointDataB = {
|
||||||
|
symbol: 'arrow',
|
||||||
|
symbolSize: 15,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: (param: any) => param.data.name === 'buy' ? 'Buy' : 'Sell',
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'white'
|
||||||
|
},
|
||||||
|
data: typeDataPointBidB
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataZoom = [
|
||||||
|
{ type: 'slider', xAxisIndex: 0, start: 0, end: 100, labelFormatter: (value: number) => new Date(value).toLocaleTimeString() },
|
||||||
|
{ type: 'inside', xAxisIndex: 0, start: 0, 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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// console.log(echarts.version)
|
||||||
|
|
||||||
|
const marklineAskA = {
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#007bff',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: () => lastAskA!.toFixed(5)
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{ coord: ['min', lastAskA] },
|
||||||
|
{ coord: ['max', lastAskA] }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
silent: true,
|
||||||
|
animation: false,
|
||||||
|
clip: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const marklineAskB = {
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#ff4500',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: () => lastAskB!.toFixed(5)
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{ coord: ['min', lastAskB] },
|
||||||
|
{ coord: ['max', lastAskB] }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
silent: true,
|
||||||
|
animation: false,
|
||||||
|
clip: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const marklineBidA = {
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#00b7eb',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: () => lastBidA!.toFixed(5)
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{ coord: ['min', lastBidA] },
|
||||||
|
{ coord: ['max', lastBidA] }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
silent: true,
|
||||||
|
animation: false,
|
||||||
|
clip: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const marklineBidB = {
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#ff8c00',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: () => lastBidB!.toFixed(5)
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{ coord: ['min', lastBidB] },
|
||||||
|
{ coord: ['max', lastBidB] }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
silent: true,
|
||||||
|
animation: false,
|
||||||
|
clip: false
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const option = {
|
||||||
dataset: {
|
dataset: {
|
||||||
source: datasetSource,
|
source: filteredData,
|
||||||
dimensions: ['timestamp', 'askA', 'askB', 'bidA', 'bidB', 'midlineA', 'midlineB'],
|
dimensions: ['timestamp', 'askA', 'askB', 'bidA', 'bidB', 'midlineA', 'midlineB'],
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@ -86,205 +371,150 @@ const ChartComponents: React.FC = () => {
|
|||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: [
|
data: [
|
||||||
`Ask A (${quoteData.brokerA}/${quoteData.symbolA})`,
|
`Ask A (${brokerA}/${symbolA})`,
|
||||||
`Bid A (${quoteData.brokerA}/${quoteData.symbolA})`,
|
`Bid A (${brokerA}/${symbolA})`,
|
||||||
`Midline A (${quoteData.brokerA}/${quoteData.symbolA})`,
|
`Midline A (${brokerA}/${symbolA})`,
|
||||||
`Ask B (${quoteData.brokerB}/${quoteData.symbolB})`,
|
// `Spread A (${brokerA}/${symbolA})`,
|
||||||
`Bid B (${quoteData.brokerB}/${quoteData.symbolB})`,
|
`Ask B (${brokerB}/${symbolB})`,
|
||||||
`Midline B (${quoteData.brokerB}/${quoteData.symbolB})`,
|
`Bid B (${brokerB}/${symbolB})`,
|
||||||
|
`Midline B (${brokerB}/${symbolB})`,
|
||||||
|
// `Spread B (${brokerB}/${symbolB})`,
|
||||||
],
|
],
|
||||||
selected: {
|
selected: {
|
||||||
[`Ask A (${quoteData.brokerA}/${quoteData.symbolA})`]: !showMidline,
|
[`Ask A (${brokerA}/${symbolA})`]: !showMidline,
|
||||||
[`Ask B (${quoteData.brokerB}/${quoteData.symbolB})`]: !showMidline,
|
[`Bid A (${brokerA}/${symbolA})`]: !showMidline,
|
||||||
[`Bid A (${quoteData.brokerA}/${quoteData.symbolA})`]: !showMidline,
|
[`Ask B (${brokerB}/${symbolB})`]: !showMidline,
|
||||||
[`Bid B (${quoteData.brokerB}/${quoteData.symbolB})`]: !showMidline,
|
[`Bid B (${brokerB}/${symbolB})`]: !showMidline,
|
||||||
[`Midline A (${quoteData.brokerA}/${quoteData.symbolA})`]: showMidline,
|
[`Midline A (${brokerA}/${symbolA})`]: showMidline,
|
||||||
[`Midline B (${quoteData.brokerB}/${quoteData.symbolB})`]: showMidline,
|
[`Midline B (${brokerB}/${symbolB})`]: showMidline,
|
||||||
|
// [`Spread A (${brokerA}/${symbolA})`]: showMidline,
|
||||||
|
// [`Spread B (${brokerB}/${symbolB})`]: showMidline,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: { left: '5%', right: '5%', bottom: '15%', containLabel: true },
|
||||||
left: '5%',
|
toolbox: { feature: { dataZoom: {}, saveAsImage: {}, brush: { type: ['rect', 'polygon', 'clear'] } } },
|
||||||
right: '5%',
|
dataZoom,
|
||||||
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: {
|
xAxis: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
name: 'Timestamp',
|
name: 'Timestamp',
|
||||||
axisLabel: {
|
axisLabel: { formatter: (value: number) => new Date(value).toLocaleString() },
|
||||||
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: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: `${quoteData.brokerA}/${quoteData.symbolA}`,
|
name: `${brokerA}/${symbolA}`,
|
||||||
min: minValueA,
|
min: adjustedMinValueA,
|
||||||
max: maxValueA,
|
max: adjustedMaxValueA,
|
||||||
axisLabel: {
|
position: 'left',
|
||||||
formatter: (value: number) => value.toFixed(5),
|
axisLabel: { formatter: (value: number) => value.toFixed(5) },
|
||||||
},
|
|
||||||
// alignTicks:true,
|
|
||||||
scale: true,
|
scale: true,
|
||||||
splitNumber: 10,
|
splitNumber: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: `${quoteData.brokerB}/${quoteData.symbolB}`,
|
name: `${brokerB}/${symbolB}`,
|
||||||
min: minValueB,
|
min: adjustedMinValueB,
|
||||||
max: maxValueB,
|
max: adjustedMaxValueB,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
axisLabel: {
|
axisLabel: { formatter: (value: number) => value.toFixed(5) },
|
||||||
formatter: (value: number) => value.toFixed(5),
|
|
||||||
},
|
|
||||||
// alignTicks:true,
|
|
||||||
scale: true,
|
scale: true,
|
||||||
splitNumber: 10,
|
splitNumber: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: `Ask A (${quoteData.brokerA}/${quoteData.symbolA})`,
|
name: `Ask A (${brokerA}/${symbolA})`,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: datasetSource
|
data: filteredData.filter(record => record.askA !== null).map(record => [record.timestamp, record.askA]),
|
||||||
.filter(record => record.askA !== null)
|
|
||||||
.map(record => [record.timestamp, record.askA]),
|
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
lineStyle: { color: '#007bff', width: 2 },
|
lineStyle: { color: '#007bff', width: 2 },
|
||||||
itemStyle: { color: '#007bff' },
|
itemStyle: { color: '#007bff' },
|
||||||
showSymbol: true,
|
showSymbol: false,
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 4,
|
|
||||||
connectNulls: false,
|
connectNulls: false,
|
||||||
|
markLine: marklineAskA,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Bid A (${quoteData.brokerA}/${quoteData.symbolA})`,
|
name: `Bid A (${brokerA}/${symbolA})`,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: datasetSource
|
data: filteredData.filter(record => record.bidA !== null).map(record => [record.timestamp, record.bidA]),
|
||||||
.filter(record => record.bidA !== null)
|
|
||||||
.map(record => [record.timestamp, record.bidA]),
|
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
lineStyle: { color: '#00b7eb', type: 'dashed', width: 2 },
|
lineStyle: { color: '#00b7eb', type: 'dashed', width: 2 },
|
||||||
itemStyle: { color: '#00b7eb' },
|
itemStyle: { color: '#00b7eb' },
|
||||||
showSymbol: true,
|
showSymbol: false,
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 4,
|
|
||||||
connectNulls: false,
|
connectNulls: false,
|
||||||
|
markPoint: markPointDataA,
|
||||||
|
markLine: marklineBidA,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Ask B (${quoteData.brokerB}/${quoteData.symbolB})`,
|
name: `Midline A (${brokerA}/${symbolA})`,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: datasetSource
|
data: filteredData.filter(record => record.midlineA !== null).map(record => [record.timestamp, record.midlineA]),
|
||||||
.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,
|
yAxisIndex: 0,
|
||||||
lineStyle: { color: '#28a745', width: 2 },
|
lineStyle: { color: '#28a745', width: 2 },
|
||||||
itemStyle: { color: '#28a745' },
|
itemStyle: { color: '#28a745' },
|
||||||
showSymbol: true,
|
showSymbol: false,
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 4,
|
|
||||||
connectNulls: false,
|
connectNulls: false,
|
||||||
|
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: `Spread A (${brokerA}/${symbolA})`,
|
||||||
|
// type: 'line',
|
||||||
|
// data: filteredData.filter(record => record.spreadA !== null).map(record => [record.timestamp, record.spreadA]),
|
||||||
|
// yAxisIndex: 0,
|
||||||
|
// lineStyle: { color: '#6f42c1', width: 2 },
|
||||||
|
// itemStyle: { color: '#6f42c1' },
|
||||||
|
// showSymbol: false,
|
||||||
|
// connectNulls: false,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: `Ask B (${brokerB}/${symbolB})`,
|
||||||
|
type: 'line',
|
||||||
|
data: filteredData.filter(record => record.askB !== null).map(record => [record.timestamp, record.askB]),
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: '#ff4500', width: 2 },
|
||||||
|
itemStyle: { color: '#ff4500' },
|
||||||
|
showSymbol: false,
|
||||||
|
connectNulls: false,
|
||||||
|
markLine: marklineAskB,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Midline B (${quoteData.brokerB}/${quoteData.symbolB})`,
|
name: `Bid B (${brokerB}/${symbolB})`,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: datasetSource
|
data: filteredData.filter(record => record.bidB !== null).map(record => [record.timestamp, record.bidB]),
|
||||||
.filter(record => record.midlineB !== null)
|
yAxisIndex: 1,
|
||||||
.map(record => [record.timestamp, record.midlineB]),
|
lineStyle: { color: '#ff8c00', type: 'dashed', width: 2 },
|
||||||
|
itemStyle: { color: '#ff8c00' },
|
||||||
|
showSymbol: false,
|
||||||
|
connectNulls: false,
|
||||||
|
markPoint: markPointDataB,
|
||||||
|
maekLine: marklineBidB,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Midline B (${brokerB}/${symbolB})`,
|
||||||
|
type: 'line',
|
||||||
|
data: filteredData.filter(record => record.midlineB !== null).map(record => [record.timestamp, record.midlineB]),
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
lineStyle: { color: '#9932cc', width: 2 },
|
lineStyle: { color: '#9932cc', width: 2 },
|
||||||
itemStyle: { color: '#9932cc' },
|
itemStyle: { color: '#9932cc' },
|
||||||
showSymbol: true,
|
showSymbol: false,
|
||||||
symbol: 'square',
|
|
||||||
symbolSize: 4,
|
|
||||||
connectNulls: false,
|
connectNulls: false,
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: `Spread B (${brokerB}/${symbolB})`,
|
||||||
|
// type: 'line',
|
||||||
|
// data: filteredData.filter(record => record.spreadB !== null).map(record => [record.timestamp, record.spreadB]),
|
||||||
|
// yAxisIndex: 1,
|
||||||
|
// lineStyle: { color: '#dc3545', width: 2 },
|
||||||
|
// itemStyle: { color: '#dc3545' },
|
||||||
|
// showSymbol: false,
|
||||||
|
// connectNulls: false,
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className= "p-4">
|
<div className="p-4">
|
||||||
<h2 className="text-2xl font-bold text-gray-800 dark:text-gray-500 mb-4">Quote Chart</h2>
|
<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">
|
<div className="flex space-x-4 mb-4">
|
||||||
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
|
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
|
||||||
@ -295,7 +525,7 @@ const ChartComponents: React.FC = () => {
|
|||||||
onChange={() => setShowMidline(false)}
|
onChange={() => setShowMidline(false)}
|
||||||
className="form-radio text-blue-500 focus:ring-blue-500"
|
className="form-radio text-blue-500 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span>Show Ask/Bid</span>
|
<span>Show Ask/Bid/Spread</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
|
<label className="flex items-center space-x-2 text-gray-700 dark:text-gray-500">
|
||||||
<input
|
<input
|
||||||
@ -308,18 +538,16 @@ const ChartComponents: React.FC = () => {
|
|||||||
<span>Show Midline</span>
|
<span>Show Midline</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ReactECharts
|
<ReactECharts
|
||||||
echarts={echarts}
|
echarts={echarts}
|
||||||
ref = {chartRef}
|
ref={chartRef}
|
||||||
option={option}
|
option={option}
|
||||||
style={{ height: 600, width: '100%' }}
|
style={{ height: 600, width: '100%' }}
|
||||||
opts={{ renderer: 'canvas' }}
|
opts={{ renderer: 'canvas' }}
|
||||||
theme="dark"
|
theme="dark"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user