145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
/**
|
|
* 위젯 대시보드 — 종목·매매 상태 공유
|
|
*/
|
|
import React, {
|
|
createContext, useCallback, useContext, useEffect, useMemo, useState,
|
|
} from 'react';
|
|
import type { PaperSummaryDto, PaperTradeDto, StrategyDto } from '../utils/backendApi';
|
|
import type { TickerData, MarketInfo } from '../hooks/useMarketTicker';
|
|
import type { TradeOrderFillRequest } from '../types';
|
|
import type { Theme } from '../types';
|
|
import type { ChartRealtimeSource } from '../hooks/useChartRealtimeData';
|
|
|
|
export interface WidgetDashboardContextValue {
|
|
theme: Theme;
|
|
selectedMarket: string;
|
|
setSelectedMarket: (m: string) => void;
|
|
tickers: Map<string, TickerData>;
|
|
marketInfos: MarketInfo[];
|
|
marketLoading: boolean;
|
|
usdRate: number;
|
|
strategies: StrategyDto[];
|
|
selectedStrategyId: number | null;
|
|
setSelectedStrategyId: (id: number | null) => void;
|
|
summary: PaperSummaryDto | null;
|
|
trades: PaperTradeDto[];
|
|
refreshPaperData: () => void;
|
|
tradePrice: number | null;
|
|
fillBuy: TradeOrderFillRequest | null;
|
|
fillSell: TradeOrderFillRequest | null;
|
|
setFillBuy: (r: TradeOrderFillRequest | null) => void;
|
|
setFillSell: (r: TradeOrderFillRequest | null) => void;
|
|
paperTradingEnabled: boolean;
|
|
paperAutoTradeEnabled: boolean;
|
|
onPaperOrderFilled?: () => void;
|
|
chartRealtimeSource: ChartRealtimeSource;
|
|
}
|
|
|
|
const WidgetDashboardContext = createContext<WidgetDashboardContextValue | null>(null);
|
|
|
|
export function useWidgetDashboard(): WidgetDashboardContextValue {
|
|
const ctx = useContext(WidgetDashboardContext);
|
|
if (!ctx) throw new Error('useWidgetDashboard must be used within WidgetDashboardProvider');
|
|
return ctx;
|
|
}
|
|
|
|
export function useWidgetDashboardOptional(): WidgetDashboardContextValue | null {
|
|
return useContext(WidgetDashboardContext);
|
|
}
|
|
|
|
interface ProviderProps {
|
|
theme: Theme;
|
|
tickers: Map<string, TickerData>;
|
|
marketInfos: MarketInfo[];
|
|
marketLoading: boolean;
|
|
usdRate: number;
|
|
defaultMarket: string;
|
|
strategies: StrategyDto[];
|
|
summary: PaperSummaryDto | null;
|
|
trades: PaperTradeDto[];
|
|
refreshPaperData: () => void;
|
|
paperTradingEnabled: boolean;
|
|
paperAutoTradeEnabled: boolean;
|
|
onPaperOrderFilled?: () => void;
|
|
chartRealtimeSource: ChartRealtimeSource;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export const WidgetDashboardProvider: React.FC<ProviderProps> = ({
|
|
theme,
|
|
tickers,
|
|
marketInfos,
|
|
marketLoading,
|
|
usdRate,
|
|
defaultMarket,
|
|
strategies,
|
|
summary,
|
|
trades,
|
|
refreshPaperData,
|
|
paperTradingEnabled,
|
|
paperAutoTradeEnabled,
|
|
onPaperOrderFilled,
|
|
chartRealtimeSource,
|
|
children,
|
|
}) => {
|
|
const [selectedMarket, setSelectedMarket] = useState(defaultMarket);
|
|
const [selectedStrategyId, setSelectedStrategyId] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (defaultMarket) setSelectedMarket(defaultMarket);
|
|
}, [defaultMarket]);
|
|
const [fillBuy, setFillBuy] = useState<TradeOrderFillRequest | null>(null);
|
|
const [fillSell, setFillSell] = useState<TradeOrderFillRequest | null>(null);
|
|
|
|
const tradePrice = useMemo(() => {
|
|
const t = tickers.get(selectedMarket);
|
|
return t?.tradePrice ?? null;
|
|
}, [tickers, selectedMarket]);
|
|
|
|
const value = useMemo<WidgetDashboardContextValue>(() => ({
|
|
theme,
|
|
selectedMarket,
|
|
setSelectedMarket,
|
|
tickers,
|
|
marketInfos,
|
|
marketLoading,
|
|
usdRate,
|
|
strategies,
|
|
selectedStrategyId,
|
|
setSelectedStrategyId,
|
|
summary,
|
|
trades,
|
|
refreshPaperData,
|
|
tradePrice,
|
|
fillBuy,
|
|
fillSell,
|
|
setFillBuy,
|
|
setFillSell,
|
|
paperTradingEnabled,
|
|
paperAutoTradeEnabled,
|
|
onPaperOrderFilled,
|
|
chartRealtimeSource,
|
|
}), [
|
|
theme, selectedMarket, tickers, marketInfos, marketLoading, usdRate,
|
|
strategies, selectedStrategyId, summary, trades, refreshPaperData,
|
|
tradePrice, fillBuy, fillSell, paperTradingEnabled, paperAutoTradeEnabled,
|
|
onPaperOrderFilled, chartRealtimeSource,
|
|
]);
|
|
|
|
return (
|
|
<WidgetDashboardContext.Provider value={value}>
|
|
{children}
|
|
</WidgetDashboardContext.Provider>
|
|
);
|
|
};
|
|
|
|
export function useWidgetOrderbookFillHandler(): (price: number, rowType: 'ask' | 'bid') => void {
|
|
const ctx = useWidgetDashboard();
|
|
return useCallback((price: number, rowType: 'ask' | 'bid') => {
|
|
const side = rowType === 'bid' ? 'buy' : 'sell';
|
|
const req: TradeOrderFillRequest = { market: ctx.selectedMarket, price, side, seq: Date.now() };
|
|
if (side === 'buy') ctx.setFillBuy(req);
|
|
else ctx.setFillSell(req);
|
|
}, [ctx]);
|
|
}
|