diff --git a/quote_plotter_client/public/img/green.svg b/quote_plotter_client/public/img/green.svg new file mode 100644 index 0000000..42c762f --- /dev/null +++ b/quote_plotter_client/public/img/green.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/quote_plotter_client/public/img/red.svg b/quote_plotter_client/public/img/red.svg new file mode 100644 index 0000000..ed11224 --- /dev/null +++ b/quote_plotter_client/public/img/red.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/quote_plotter_client/src/QueryOptions/apiQueryOptions.tsx b/quote_plotter_client/src/QueryOptions/apiQueryOptions.tsx index 57f53e1..4e65452 100644 --- a/quote_plotter_client/src/QueryOptions/apiQueryOptions.tsx +++ b/quote_plotter_client/src/QueryOptions/apiQueryOptions.tsx @@ -1,13 +1,13 @@ import { useQuery } from "@tanstack/react-query"; 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 = () => { return useQuery({ queryKey: ['brokers'], queryFn: async(): Promise => { - const response = await fetch('http://localhost:8000/api/quotes/brokers&symbols', { + const response = await fetch('http://localhost:8000/api/brokers&symbols', { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -17,7 +17,6 @@ export const useBrokerSymbols = () => { }); if (!response.ok) { - const errorData = await response.json().catch(() => ({})); // console.error('broker and symbol data fetch error:', errorData); throw new Error('Unable to fetch broker and symbols'); } @@ -38,7 +37,7 @@ function quoteDataLink(params: QueryOptions): string { symbol_b: params.symbolB, }, { 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) => { @@ -54,54 +53,11 @@ export const useQuoteData = (params: QueryOptions) => { }, }); if (!response.ok) { - const errorData = await response.json().catch(() => ({})); // console.error('Quote data fetch error:', errorData); throw new Error('Unable to fetch quote data'); } const quoteData = await response.json(); - // console.log('Fetched quote data:', 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; + return quoteData as QuoteData; }, enabled: !!params.brokerA && !!params.symbolA && !!params.brokerB && !!params.symbolB, staleTime: 1000 * 60 * 5, diff --git a/quote_plotter_client/src/QueryOptions/dataTypes.tsx b/quote_plotter_client/src/QueryOptions/dataTypes.tsx index 274848a..8b3fc72 100644 --- a/quote_plotter_client/src/QueryOptions/dataTypes.tsx +++ b/quote_plotter_client/src/QueryOptions/dataTypes.tsx @@ -14,6 +14,7 @@ export interface QueryOptions { export interface QuoteRecord { bid_price: number; ask_price: number; + direction: string; spread: number; midline: number; timestamp: string; @@ -21,22 +22,29 @@ export interface QuoteRecord { 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 { brokerA: string; symbolA: string; brokerB: string; symbolB: string; - records: { - askA: number | null; - askB: number | null; - bidA: number | null; - bidB: number | null; - spreadA: number | null; - spreadB: number | null; - midlineA: number | null; - midlineB: number | null; - timestamp: string; - }[]; + records: QuoteRecordData[]; + lastDataPoints?: Record; + markableRecords?: QuoteRecordData[]; } export interface QueryContextType { diff --git a/quote_plotter_client/src/components/chartComponents.tsx b/quote_plotter_client/src/components/chartComponents.tsx index bc6e57f..dd6fe0b 100644 --- a/quote_plotter_client/src/components/chartComponents.tsx +++ b/quote_plotter_client/src/components/chartComponents.tsx @@ -1,75 +1,360 @@ -import React, {useRef, useState} from "react"; +import React, {useRef, useState, useEffect} from "react"; import { useQuoteData } from "../QueryOptions/apiQueryOptions"; import { useQueryContext } from "../context/querycontext"; import * as echarts from 'echarts'; import ReactECharts from 'echarts-for-react'; +import { type QuoteData } from "../QueryOptions/dataTypes"; const ChartComponents: React.FC = () => { - const chartRef = useRef(null); - const { queryOptions, isLoadingBrokers, isErrorBrokers } = - useQueryContext(); + const chartRef = useRef(null); + const { queryOptions, isLoadingBrokers, isErrorBrokers } = + useQueryContext(); + const {data: quoteData, isError: isErrorQuotes } = + useQuoteData(queryOptions) as { data: QuoteData | undefined; isLoading: boolean; isError: boolean }; - const { data: quoteData, isLoading: isLoadingQuotes, isError: isErrorQuotes } = - useQuoteData(queryOptions); + const [showMidline, setShowMidline] = useState(false); + + + const [latestValues, setLatestValues] = useState({ + lastTimestampA: null as number | null, + 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, + }); - const [showMidline, setShowMidline] = useState(false); + 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 Loading brokers...; + if (isErrorBrokers) return Error loading brokers; + if (isErrorQuotes) return Error loading quote data; + if (!quoteData?.records?.length) return No quote data available; + + + const { records: filteredData, brokerA, symbolA, brokerB, symbolB } = quoteData; + + const valuesA = filteredData + .map(records => [records.askA, records.bidA, records.midlineA]) + .flat() + .filter((v): v is number => v !== null); + const valuesB = filteredData + .map(records => [records.askB, records.bidB, records.midlineB]) + .flat() + .filter((v): v is number => v !== null); + const buffer = 0.00001; - // console.log('quote data before render:', quoteData, - // 'isLoadingQuotes:', isLoadingQuotes, - // 'quotesError:', isErrorQuotes); - - - if (isLoadingBrokers) return Loading brokers...; - if (isErrorBrokers) return Error loading brokers; - if (isErrorQuotes) return Error loading quote data; - if (!quoteData?.records?.length) return No quote data available; - 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 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 option = { + 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: { - source: datasetSource, + source: filteredData, dimensions: ['timestamp', 'askA', 'askB', 'bidA', 'bidB', 'midlineA', 'midlineB'], }, tooltip: { @@ -86,205 +371,150 @@ const ChartComponents: React.FC = () => { }, 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})`, + `Ask A (${brokerA}/${symbolA})`, + `Bid A (${brokerA}/${symbolA})`, + `Midline A (${brokerA}/${symbolA})`, + // `Spread A (${brokerA}/${symbolA})`, + `Ask B (${brokerB}/${symbolB})`, + `Bid B (${brokerB}/${symbolB})`, + `Midline B (${brokerB}/${symbolB})`, + // `Spread B (${brokerB}/${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, + [`Ask A (${brokerA}/${symbolA})`]: !showMidline, + [`Bid A (${brokerA}/${symbolA})`]: !showMidline, + [`Ask B (${brokerB}/${symbolB})`]: !showMidline, + [`Bid B (${brokerB}/${symbolB})`]: !showMidline, + [`Midline A (${brokerA}/${symbolA})`]: showMidline, + [`Midline B (${brokerB}/${symbolB})`]: showMidline, + // [`Spread A (${brokerA}/${symbolA})`]: showMidline, + // [`Spread B (${brokerB}/${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, - }, - ], + grid: { left: '5%', right: '5%', bottom: '15%', containLabel: true }, + toolbox: { feature: { dataZoom: {}, saveAsImage: {}, brush: { type: ['rect', 'polygon', 'clear'] } } }, + dataZoom, 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')}`; - }, - }, + axisLabel: { formatter: (value: number) => new Date(value).toLocaleString() }, }, yAxis: [ { type: 'value', - name: `${quoteData.brokerA}/${quoteData.symbolA}`, - min: minValueA, - max: maxValueA, - axisLabel: { - formatter: (value: number) => value.toFixed(5), - }, - // alignTicks:true, + name: `${brokerA}/${symbolA}`, + min: adjustedMinValueA, + max: adjustedMaxValueA, + position: 'left', + axisLabel: { formatter: (value: number) => value.toFixed(5) }, scale: true, splitNumber: 10, }, { type: 'value', - name: `${quoteData.brokerB}/${quoteData.symbolB}`, - min: minValueB, - max: maxValueB, + name: `${brokerB}/${symbolB}`, + min: adjustedMinValueB, + max: adjustedMaxValueB, position: 'right', - axisLabel: { - formatter: (value: number) => value.toFixed(5), - }, - // alignTicks:true, + axisLabel: { formatter: (value: number) => value.toFixed(5) }, scale: true, splitNumber: 10, }, ], series: [ { - name: `Ask A (${quoteData.brokerA}/${quoteData.symbolA})`, + name: `Ask A (${brokerA}/${symbolA})`, type: 'line', - data: datasetSource - .filter(record => record.askA !== null) - .map(record => [record.timestamp, record.askA]), + data: filteredData.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, + showSymbol: false, connectNulls: false, + markLine: marklineAskA, }, { - name: `Bid A (${quoteData.brokerA}/${quoteData.symbolA})`, + name: `Bid A (${brokerA}/${symbolA})`, type: 'line', - data: datasetSource - .filter(record => record.bidA !== null) - .map(record => [record.timestamp, record.bidA]), + data: filteredData.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, + showSymbol: false, connectNulls: false, + markPoint: markPointDataA, + markLine: marklineBidA, }, { - name: `Ask B (${quoteData.brokerB}/${quoteData.symbolB})`, + name: `Midline A (${brokerA}/${symbolA})`, 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]), + data: filteredData.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, + showSymbol: 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', - data: datasetSource - .filter(record => record.midlineB !== null) - .map(record => [record.timestamp, record.midlineB]), + data: filteredData.filter(record => record.bidB !== null).map(record => [record.timestamp, record.bidB]), + yAxisIndex: 1, + 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, lineStyle: { color: '#9932cc', width: 2 }, itemStyle: { color: '#9932cc' }, - showSymbol: true, - symbol: 'square', - symbolSize: 4, + showSymbol: 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 ( - + Quote Chart @@ -295,7 +525,7 @@ const ChartComponents: React.FC = () => { onChange={() => setShowMidline(false)} className="form-radio text-blue-500 focus:ring-blue-500" /> - Show Ask/Bid + Show Ask/Bid/Spread { Show Midline - - - ) + ); };