diff --git a/src/pages/visualizer/Chart.tsx b/src/pages/visualizer/Chart.tsx index 8652a6a..3d8b1df 100644 --- a/src/pages/visualizer/Chart.tsx +++ b/src/pages/visualizer/Chart.tsx @@ -41,15 +41,16 @@ function getThemeOptions(theme: (highcharts: typeof Highcharts) => void): Highch interface ChartProps { title: string; + options?: Highcharts.Options; series: Highcharts.SeriesOptionsType[]; min?: number; max?: number; } -export function Chart({ title, series, min, max }: ChartProps): ReactNode { +export function Chart({ title, options, series, min, max }: ChartProps): ReactNode { const colorScheme = useActualColorScheme(); - const options = useMemo((): Highcharts.Options => { + const fullOptions = useMemo((): Highcharts.Options => { const themeOptions = colorScheme === 'light' ? {} : getThemeOptions(HighchartsHighContrastDarkTheme); const chartOptions: Highcharts.Options = { @@ -148,14 +149,15 @@ export function Chart({ title, series, min, max }: ChartProps): ReactNode { enabled: false, }, series, + ...options, }; return merge(themeOptions, chartOptions); - }, [colorScheme, title, series, min, max]); + }, [colorScheme, title, options, series, min, max]); return ( - + ); } diff --git a/src/pages/visualizer/ConversionObservationsTable.tsx b/src/pages/visualizer/ConversionObservationsTable.tsx index 03b92f9..bf7d843 100644 --- a/src/pages/visualizer/ConversionObservationsTable.tsx +++ b/src/pages/visualizer/ConversionObservationsTable.tsx @@ -14,13 +14,13 @@ export function ConversionObservationsTable({ conversionObservations }: Conversi rows.push( {product} - {formatNumber(observation.bidPrice)} - {formatNumber(observation.askPrice)} - {formatNumber(observation.transportFees)} - {formatNumber(observation.exportTariff)} - {formatNumber(observation.importTariff)} - {formatNumber(observation.sunlight)} - {formatNumber(observation.humidity)} + {formatNumber(observation.bidPrice, 2)} + {formatNumber(observation.askPrice, 2)} + {formatNumber(observation.transportFees, 2)} + {formatNumber(observation.exportTariff, 2)} + {formatNumber(observation.importTariff, 2)} + {formatNumber(observation.sunlight, 2)} + {formatNumber(observation.humidity, 2)} , ); } diff --git a/src/pages/visualizer/ConversionPriceChart.tsx b/src/pages/visualizer/ConversionPriceChart.tsx new file mode 100644 index 0000000..f7ac6ef --- /dev/null +++ b/src/pages/visualizer/ConversionPriceChart.tsx @@ -0,0 +1,41 @@ +import Highcharts from 'highcharts'; +import { ReactNode } from 'react'; +import { ProsperitySymbol } from '../../models.ts'; +import { useStore } from '../../store.ts'; +import { getAskColor, getBidColor } from '../../utils/colors.ts'; +import { Chart } from './Chart.tsx'; + +export interface ConversionPriceChartProps { + symbol: ProsperitySymbol; +} + +export function ConversionPriceChart({ symbol }: ConversionPriceChartProps): ReactNode { + const algorithm = useStore(state => state.algorithm)!; + + const bidPriceData = []; + const askPriceData = []; + + for (const row of algorithm.data) { + const observation = row.state.observations.conversionObservations[symbol]; + if (observation === undefined) { + continue; + } + + bidPriceData.push([row.state.timestamp, observation.bidPrice]); + askPriceData.push([row.state.timestamp, observation.askPrice]); + } + + const options: Highcharts.Options = { + yAxis: { + opposite: true, + allowDecimals: true, + }, + }; + + const series: Highcharts.SeriesOptionsType[] = [ + { type: 'line', name: 'Bid', color: getBidColor(1.0), marker: { symbol: 'triangle' }, data: bidPriceData }, + { type: 'line', name: 'Ask', color: getAskColor(1.0), marker: { symbol: 'triangle-down' }, data: askPriceData }, + ]; + + return ; +} diff --git a/src/pages/visualizer/EnvironmentChart.tsx b/src/pages/visualizer/EnvironmentChart.tsx new file mode 100644 index 0000000..41b34a2 --- /dev/null +++ b/src/pages/visualizer/EnvironmentChart.tsx @@ -0,0 +1,37 @@ +import Highcharts from 'highcharts'; +import { ReactNode } from 'react'; +import { ProsperitySymbol } from '../../models.ts'; +import { useStore } from '../../store.ts'; +import { Chart } from './Chart.tsx'; + +export interface EnvironmentChartProps { + symbol: ProsperitySymbol; +} + +export function EnvironmentChart({ symbol }: EnvironmentChartProps): ReactNode { + const algorithm = useStore(state => state.algorithm)!; + + const sunlightData = []; + const humidityData = []; + + for (const row of algorithm.data) { + const observation = row.state.observations.conversionObservations[symbol]; + if (observation === undefined) { + continue; + } + + sunlightData.push([row.state.timestamp, observation.sunlight]); + humidityData.push([row.state.timestamp, observation.humidity]); + } + + const series: Highcharts.SeriesOptionsType[] = [ + { type: 'line', name: 'Sunlight', marker: { symbol: 'square' }, yAxis: 0, data: sunlightData }, + { type: 'line', name: 'Humidity', marker: { symbol: 'circle' }, yAxis: 1, data: humidityData }, + ]; + + const options: Highcharts.Options = { + yAxis: [{}, { opposite: true }], + }; + + return ; +} diff --git a/src/pages/visualizer/PositionChart.tsx b/src/pages/visualizer/PositionChart.tsx index a67703f..e613ce0 100644 --- a/src/pages/visualizer/PositionChart.tsx +++ b/src/pages/visualizer/PositionChart.tsx @@ -8,6 +8,7 @@ function getLimit(algorithm: Algorithm, symbol: ProsperitySymbol): number { const knownLimits: Record = { AMETHYSTS: 20, STARFRUIT: 20, + ORCHIDS: 100, }; if (knownLimits[symbol] !== undefined) { diff --git a/src/pages/visualizer/PriceChart.tsx b/src/pages/visualizer/ProductPriceChart.tsx similarity index 93% rename from src/pages/visualizer/PriceChart.tsx rename to src/pages/visualizer/ProductPriceChart.tsx index 4914226..6cc6340 100644 --- a/src/pages/visualizer/PriceChart.tsx +++ b/src/pages/visualizer/ProductPriceChart.tsx @@ -5,11 +5,11 @@ import { useStore } from '../../store.ts'; import { getAskColor, getBidColor } from '../../utils/colors.ts'; import { Chart } from './Chart.tsx'; -export interface PriceChartProps { +export interface ProductPriceChartProps { symbol: ProsperitySymbol; } -export function PriceChart({ symbol }: PriceChartProps): ReactNode { +export function ProductPriceChart({ symbol }: ProductPriceChartProps): ReactNode { const algorithm = useStore(state => state.algorithm)!; const series: Highcharts.SeriesOptionsType[] = [ diff --git a/src/pages/visualizer/TransportChart.tsx b/src/pages/visualizer/TransportChart.tsx new file mode 100644 index 0000000..c42635c --- /dev/null +++ b/src/pages/visualizer/TransportChart.tsx @@ -0,0 +1,36 @@ +import Highcharts from 'highcharts'; +import { ReactNode } from 'react'; +import { ProsperitySymbol } from '../../models.ts'; +import { useStore } from '../../store.ts'; +import { Chart } from './Chart.tsx'; + +export interface TransportChartProps { + symbol: ProsperitySymbol; +} + +export function TransportChart({ symbol }: TransportChartProps): ReactNode { + const algorithm = useStore(state => state.algorithm)!; + + const transportFeesData = []; + const importTariffData = []; + const exportTariffData = []; + + for (const row of algorithm.data) { + const observation = row.state.observations.conversionObservations[symbol]; + if (observation === undefined) { + continue; + } + + transportFeesData.push([row.state.timestamp, observation.transportFees]); + importTariffData.push([row.state.timestamp, observation.importTariff]); + exportTariffData.push([row.state.timestamp, observation.exportTariff]); + } + + const series: Highcharts.SeriesOptionsType[] = [ + { type: 'line', name: 'Transport fees', data: transportFeesData }, + { type: 'line', name: 'Import tariff', marker: { symbol: 'triangle' }, data: importTariffData }, + { type: 'line', name: 'Export tariff', marker: { symbol: 'triangle-down' }, data: exportTariffData }, + ]; + + return ; +} diff --git a/src/pages/visualizer/VisualizerPage.tsx b/src/pages/visualizer/VisualizerPage.tsx index 2c3f5c5..681af7b 100644 --- a/src/pages/visualizer/VisualizerPage.tsx +++ b/src/pages/visualizer/VisualizerPage.tsx @@ -4,12 +4,15 @@ import { Navigate, useLocation } from 'react-router-dom'; import { useStore } from '../../store.ts'; import { formatNumber } from '../../utils/format.ts'; import { AlgorithmSummaryCard } from './AlgorithmSummaryCard.tsx'; +import { EnvironmentChart } from './EnvironmentChart.tsx'; import { PositionChart } from './PositionChart.tsx'; -import { PriceChart } from './PriceChart.tsx'; +import { ProductPriceChart } from './ProductPriceChart.tsx'; import { ProfitLossChart } from './ProfitLossChart.tsx'; import { TimestampsCard } from './TimestampsCard.tsx'; +import { TransportChart } from './TransportChart.tsx'; import { VisualizerCard } from './VisualizerCard.tsx'; import { VolumeChart } from './VolumeChart.tsx'; +import { ConversionPriceChart } from './ConversionPriceChart.tsx'; export function VisualizerPage(): ReactNode { const algorithm = useStore(state => state.algorithm); @@ -20,6 +23,13 @@ export function VisualizerPage(): ReactNode { return ; } + const conversionProducts = new Set(); + for (const row of algorithm.data) { + for (const product of Object.keys(row.state.observations.conversionObservations)) { + conversionProducts.add(product); + } + } + let profitLoss = 0; const lastTimestamp = algorithm.activityLogs[algorithm.activityLogs.length - 1].timestamp; for (let i = algorithm.activityLogs.length - 1; i >= 0 && algorithm.activityLogs[i].timestamp == lastTimestamp; i--) { @@ -29,18 +39,42 @@ export function VisualizerPage(): ReactNode { const symbolColumns: ReactNode[] = []; Object.keys(algorithm.data[0].state.listings) .sort((a, b) => a.localeCompare(b)) - .forEach((symbol, i) => { + .forEach(symbol => { symbolColumns.push( - - + + , ); symbolColumns.push( - + , ); + + if (!conversionProducts.has(symbol)) { + return; + } + + symbolColumns.push( + + + , + ); + + symbolColumns.push( + + + , + ); + + symbolColumns.push( + + + , + ); + + symbolColumns.push(); }); return (