aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2025-04-27 21:03:25 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2025-04-27 21:03:25 -0600
commit623a03cc02b2e87a187aca29db5f3c787713fc74 (patch)
tree8fa5a48eb998822ff631739d6c0bb86796d46297
parentcc68bc880407d55837e02052c7ab098079641843 (diff)
add market option
-rw-r--r--client/src/BidsPage.jsx5
-rw-r--r--client/src/MarketDataPage.jsx98
-rw-r--r--client/src/SubmitBidPage.jsx36
3 files changed, 85 insertions, 54 deletions
diff --git a/client/src/BidsPage.jsx b/client/src/BidsPage.jsx
index c6392b5..3a40f41 100644
--- a/client/src/BidsPage.jsx
+++ b/client/src/BidsPage.jsx
@@ -38,6 +38,11 @@ const columns = [
const color = val >= 0 ? 'green' : 'red';
return <Typography.Text style={{ color }}>{val.toFixed(2)}</Typography.Text>;
}
+ },
+ {
+ title: 'Market',
+ dataIndex: 'market',
+ render: (val) => val,
}
];
diff --git a/client/src/MarketDataPage.jsx b/client/src/MarketDataPage.jsx
index 76d8284..52ca47c 100644
--- a/client/src/MarketDataPage.jsx
+++ b/client/src/MarketDataPage.jsx
@@ -6,6 +6,10 @@ import { VChart } from '@visactor/react-vchart';
import API_BASE_URL from './config';
import { MarketContext, MARKET_FULL_NAMES } from './App';
+// TODO:
+//
+// - [ ] Cancel Promise if market changes
+// - [ ] Fix Data Zoom being reset
const { Row, Col } = Grid;
@@ -37,7 +41,9 @@ function TimeAgo({ timestamp, prefix }) {
function MarketDataPage() {
const { selectedMarket } = useContext(MarketContext);
- const [fiveMinData, setFiveMinData] = useState([]);
+ const [fiveMinZoom, setFiveMinZoom] = useState({ start: 0.9, end: 1 });
+ const [dayAheadZoom, setDayAheadZoom] = useState({ start: 0.05, end: 1 });
+ const [fiveMinData, setFiveMinData] = useState([]);
const [dayAheadData, setDayAheadData] = useState([]);
const [loading, setLoading] = useState(true);
const [fiveMinLastUpdated, setFiveMinLastUpdated] = useState(null);
@@ -95,49 +101,57 @@ function MarketDataPage() {
};
}, [selectedMarket]);
- const getChartSpec = (data, title) => ({
- type: 'line',
- data: {
- values: data.map(d => ({
- timestamp: new Date(d.timestamp).toLocaleString(),
- LMP: d.lmp,
- Energy: d.energy,
- Congestion: d.congestion,
- Loss: d.loss
- })),
- transforms: [
+ const getChartSpec = (data, title, zoom) => ({
+ type: 'line',
+ data: {
+ values: data.map(d => ({
+ timestamp: new Date(d.timestamp).toLocaleString(),
+ LMP: d.lmp,
+ Energy: d.energy,
+ Congestion: d.congestion,
+ Loss: d.loss
+ })),
+ transforms: [
+ {
+ type: 'fold',
+ options: {
+ key: 'name',
+ value: 'value',
+ fields: ['LMP', 'Energy', 'Congestion', 'Loss']
+ }
+ }
+ ]
+ },
+ xField: 'timestamp',
+ yField: 'value',
+ seriesField: 'name',
+ smooth: true,
+ legend: {
+ position: 'top'
+ },
+ tooltip: {
+ formatter: (datum) => ({
+ name: datum.name,
+ value: datum.value.toFixed(2)
+ })
+ },
+ dataZoom: [
{
- type: 'fold',
- options: {
- key: 'name',
- value: 'value',
- fields: ['LMP', 'Energy', 'Congestion', 'Loss']
+ orient: 'bottom',
+ height: 20,
+ start: zoom?.start ?? (title === 'fiveMin' ? 0.9 : 0.05),
+ end: zoom?.end ?? 1,
+ onChange: (e) => {
+ if (title === 'fiveMin') {
+ setFiveMinZoom({ start: e.start, end: e.end });
+ } else {
+ setDayAheadZoom({ start: e.start, end: e.end });
+ }
}
}
]
- },
- xField: 'timestamp',
- yField: 'value',
- seriesField: 'name',
- smooth: true,
- legend: {
- position: 'top'
- },
- tooltip: {
- formatter: (datum) => ({
- name: datum.name,
- value: datum.value.toFixed(2)
- })
- },
- dataZoom: [
- {
- orient: 'bottom',
- height: 20,
- start: title === 'fiveMin' ? 0.9 : 0.05,
- end: 1
- }
- ]
- });
+ });
+
const computeKPIs = (data) => {
if (!data.length) return {
@@ -243,7 +257,7 @@ function MarketDataPage() {
title='Real-Time Market Data'
>
<div style={{ padding: 20 }}>
- <VChart spec={getChartSpec(fiveMinData, 'fiveMin')} />
+ <VChart spec={getChartSpec(fiveMinData, 'fiveMin', fiveMinZoom)} />
</div>
</Card>
</Col>
@@ -259,7 +273,7 @@ function MarketDataPage() {
title='Day-Ahead Market Data'
>
<div style={{ padding: 20 }}>
- <VChart spec={getChartSpec(dayAheadData, 'dayAhead')} />
+ <VChart spec={getChartSpec(dayAheadData, 'dayAhead', dayAheadZoom)} />
</div>
</Card>
</Col>
diff --git a/client/src/SubmitBidPage.jsx b/client/src/SubmitBidPage.jsx
index 65a9eb8..94a0c32 100644
--- a/client/src/SubmitBidPage.jsx
+++ b/client/src/SubmitBidPage.jsx
@@ -1,37 +1,46 @@
-import React, { useState, useEffect } from 'react';
-import { Form, InputNumber, DatePicker, Button, Message, Typography, Card } from '@arco-design/web-react';
+import React, { useState, useEffect, useContext } from 'react';
+import { Form, InputNumber, DatePicker, Button, Message, Typography, Card, Select } from '@arco-design/web-react';
import '@arco-design/web-react/dist/css/arco.css';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import API_BASE_URL from './config';
+import { MarketContext, MARKET_FULL_NAMES } from './App';
dayjs.extend(utc);
dayjs.extend(timezone);
+const MARKET_TIMEZONES = {
+ ISONE: 'America/New_York',
+ NYISO: 'America/New_York',
+ MISO: 'America/Chicago'
+};
+
function SubmitBidPage() {
+ const { selectedMarket } = useContext(MarketContext);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [localNow, setLocalNow] = useState('');
- const [newEnglandNow, setNewEnglandNow] = useState('');
+ const [marketNow, setMarketNow] = useState('');
useEffect(() => {
const updateTime = () => {
const now = dayjs();
const localFormatted = now.format('dddd, MMMM D, h:mm A');
- const newEngland = now.tz('America/New_York');
- const newEnglandFormatted = newEngland.format('dddd, MMMM D, h:mm A');
+ const marketTz = MARKET_TIMEZONES[selectedMarket] || 'America/New_York';
+ const marketTime = now.tz(marketTz);
+ const marketFormatted = marketTime.format('dddd, MMMM D, h:mm A');
setLocalNow(localFormatted);
- setNewEnglandNow(newEnglandFormatted);
+ setMarketNow(marketFormatted);
};
updateTime();
- const interval = setInterval(updateTime, 1000); // update every second
+ const interval = setInterval(updateTime, 1000);
return () => clearInterval(interval);
- }, []);
+ }, [selectedMarket]);
const handleSubmit = async (values) => {
setLoading(true);
@@ -39,10 +48,14 @@ function SubmitBidPage() {
const picked = dayjs(values.timestamp);
const adjusted = picked.minute(0).second(0).millisecond(0);
+ const marketTz = MARKET_TIMEZONES[selectedMarket] || 'America/New_York';
+ const timestampInMarketTz = adjusted.tz(marketTz).format();
+
const payload = {
- timestamp: adjusted.tz('America/New_York').format(),
+ timestamp: timestampInMarketTz,
quantity: values.quantity,
price: values.price,
+ market: selectedMarket,
user_id: 1 // Hardcoded user id for now
};
@@ -73,12 +86,11 @@ function SubmitBidPage() {
<div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)' }}>
<Typography.Title heading={4}>Submit New Bid</Typography.Title>
<Card style={{ marginTop: 16 }}>
- {/* 🕰️ Show Current Time Info */}
<Typography.Paragraph>
<strong>Current Local Time:</strong> {localNow}
</Typography.Paragraph>
<Typography.Paragraph>
- <strong>Current New England Time:</strong> {newEnglandNow}
+ <strong>Current {MARKET_FULL_NAMES[selectedMarket]} Time:</strong> {marketNow}
</Typography.Paragraph>
<Form
@@ -105,8 +117,8 @@ function SubmitBidPage() {
style={{ width: '100%' }}
format="YYYY-MM-DD HH:00"
placeholder="Select date and hour"
- timezone='America/New_York'
disabledMinutes={() => Array.from({ length: 60 }, (_, i) => i !== 0)}
+ timezone={MARKET_TIMEZONES[selectedMarket]}
/>
</Form.Item>