알림목록 종목별 정렬시 시간별로 정렬되도록 수정
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { PaperTradeDto } from '../../lib/shared';
|
import type { PaperTradeDto } from '../../lib/shared';
|
||||||
|
import { compareTimeAsc } from '@frontend/utils/tradeListSort';
|
||||||
import MobileStackHeader from '../../components/MobileStackHeader';
|
import MobileStackHeader from '../../components/MobileStackHeader';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -9,7 +10,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function VirtualHistoryScreen({ market, trades, onBack }: Props) {
|
export default function VirtualHistoryScreen({ market, trades, onBack }: Props) {
|
||||||
const filtered = trades.filter(t => t.symbol === market).slice(0, 50);
|
const filtered = useMemo(
|
||||||
|
() => trades
|
||||||
|
.filter(t => t.symbol === market)
|
||||||
|
.sort((a, b) => compareTimeAsc(a.createdAt, b.createdAt))
|
||||||
|
.slice(0, 50),
|
||||||
|
[trades, market],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="screen stack-screen">
|
<div className="screen stack-screen">
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
getTradeNotificationGridPreset,
|
getTradeNotificationGridPreset,
|
||||||
type TradeNotificationGridPresetId,
|
type TradeNotificationGridPresetId,
|
||||||
} from '../utils/tradeNotificationGridPresets';
|
} from '../utils/tradeNotificationGridPresets';
|
||||||
|
import { sortTradeNotifications } from '../utils/tradeListSort';
|
||||||
|
|
||||||
type RightTab = 'trade' | 'orderbook';
|
type RightTab = 'trade' | 'orderbook';
|
||||||
|
|
||||||
@@ -88,24 +89,6 @@ function fillBothSides(
|
|||||||
setFillSell({ market, price, side: 'sell', seq: seq + 1 });
|
setFillSell({ market, price, side: 'sell', seq: seq + 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortNotifications(
|
|
||||||
items: TradeNotificationItem[],
|
|
||||||
sort: TradeNotificationListSort,
|
|
||||||
): TradeNotificationItem[] {
|
|
||||||
const arr = [...items];
|
|
||||||
if (sort === 'market') {
|
|
||||||
return arr.sort((a, b) => {
|
|
||||||
const byMarket = a.market.localeCompare(b.market, 'ko');
|
|
||||||
if (byMarket !== 0) return byMarket;
|
|
||||||
return b.receivedAt - a.receivedAt;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (sort === 'oldest') {
|
|
||||||
return arr.sort((a, b) => a.receivedAt - b.receivedAt);
|
|
||||||
}
|
|
||||||
return arr.sort((a, b) => b.receivedAt - a.receivedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TradeNotificationListPage: React.FC<Props> = ({
|
export const TradeNotificationListPage: React.FC<Props> = ({
|
||||||
theme,
|
theme,
|
||||||
onGoToChart,
|
onGoToChart,
|
||||||
@@ -273,7 +256,7 @@ export const TradeNotificationListPage: React.FC<Props> = ({
|
|||||||
}, [allNotifications, filter, candleFilter]);
|
}, [allNotifications, filter, candleFilter]);
|
||||||
|
|
||||||
const sorted = useMemo(
|
const sorted = useMemo(
|
||||||
() => sortNotifications(filtered, listSort),
|
() => sortTradeNotifications(filtered, listSort),
|
||||||
[filtered, listSort],
|
[filtered, listSort],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
toUpbitMarket,
|
toUpbitMarket,
|
||||||
} from '../../utils/backtestUiUtils';
|
} from '../../utils/backtestUiUtils';
|
||||||
import { getKoreanName } from '../../utils/marketNameCache';
|
import { getKoreanName } from '../../utils/marketNameCache';
|
||||||
|
import { compareMarketSymbol, compareTimeAsc } from '../../utils/tradeListSort';
|
||||||
|
|
||||||
export type ExecutionListTab = 'backtest' | 'live';
|
export type ExecutionListTab = 'backtest' | 'live';
|
||||||
export type BacktestListSort = 'strategy' | 'symbol' | 'return';
|
export type BacktestListSort = 'strategy' | 'symbol' | 'return';
|
||||||
@@ -58,7 +59,13 @@ export default function BacktestExecutionList({
|
|||||||
const sortedBacktests = useMemo(() => {
|
const sortedBacktests = useMemo(() => {
|
||||||
const list = [...backtestRecords];
|
const list = [...backtestRecords];
|
||||||
if (sort === 'strategy') list.sort((a, b) => (a.strategyName ?? '').localeCompare(b.strategyName ?? ''));
|
if (sort === 'strategy') list.sort((a, b) => (a.strategyName ?? '').localeCompare(b.strategyName ?? ''));
|
||||||
else if (sort === 'symbol') list.sort((a, b) => a.symbol.localeCompare(b.symbol));
|
else if (sort === 'symbol') {
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const bySymbol = compareMarketSymbol(a.symbol, b.symbol);
|
||||||
|
if (bySymbol !== 0) return bySymbol;
|
||||||
|
return compareTimeAsc(a.createdAt, b.createdAt);
|
||||||
|
});
|
||||||
|
}
|
||||||
else list.sort((a, b) => (b.totalReturn ?? 0) - (a.totalReturn ?? 0));
|
else list.sort((a, b) => (b.totalReturn ?? 0) - (a.totalReturn ?? 0));
|
||||||
return list;
|
return list;
|
||||||
}, [backtestRecords, sort]);
|
}, [backtestRecords, sort]);
|
||||||
@@ -117,7 +124,13 @@ export default function BacktestExecutionList({
|
|||||||
<p className="btd-sidebar-hint">가상투자 화면에서 자동·수동<br />매매를 실행하세요.</p>
|
<p className="btd-sidebar-hint">가상투자 화면에서 자동·수동<br />매매를 실행하세요.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
liveItems.map(item => {
|
[...liveItems]
|
||||||
|
.sort((a, b) => {
|
||||||
|
const bySymbol = compareMarketSymbol(a.symbol, b.symbol);
|
||||||
|
if (bySymbol !== 0) return bySymbol;
|
||||||
|
return compareTimeAsc(a.createdAt, b.createdAt);
|
||||||
|
})
|
||||||
|
.map(item => {
|
||||||
const positive = item.totalReturnPct >= 0;
|
const positive = item.totalReturnPct >= 0;
|
||||||
const active = selectedLiveId === item.id;
|
const active = selectedLiveId === item.id;
|
||||||
const market = toUpbitMarket(item.symbol);
|
const market = toUpbitMarket(item.symbol);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { PaperTradeDto } from '../../utils/backendApi';
|
import type { PaperTradeDto } from '../../utils/backendApi';
|
||||||
import type { TickerData } from '../../hooks/useMarketTicker';
|
import type { TickerData } from '../../hooks/useMarketTicker';
|
||||||
import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames';
|
import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames';
|
||||||
import { fmtKrw } from '../TradeOrderPanel';
|
import { fmtKrw } from '../TradeOrderPanel';
|
||||||
|
import { sortPaperTradesForDisplay } from '../../utils/tradeListSort';
|
||||||
|
|
||||||
function coinCode(symbol: string): string {
|
function coinCode(symbol: string): string {
|
||||||
return symbol.replace(/^KRW-/, '');
|
return symbol.replace(/^KRW-/, '');
|
||||||
@@ -43,7 +44,10 @@ const PaperTradeHistoryList: React.FC<Props> = ({
|
|||||||
selectedTradeId = null,
|
selectedTradeId = null,
|
||||||
onSelectTrade,
|
onSelectTrade,
|
||||||
onSelectMarket,
|
onSelectMarket,
|
||||||
}) => (
|
}) => {
|
||||||
|
const displayTrades = useMemo(() => sortPaperTradesForDisplay(trades), [trades]);
|
||||||
|
|
||||||
|
return (
|
||||||
<div className={`ptd-trade-history vtd-trade-history${className ? ` ${className}` : ''}`}>
|
<div className={`ptd-trade-history vtd-trade-history${className ? ` ${className}` : ''}`}>
|
||||||
<section className="vtd-target-section vtd-trade-section">
|
<section className="vtd-target-section vtd-trade-section">
|
||||||
<div className="vtd-target-list-head">
|
<div className="vtd-target-list-head">
|
||||||
@@ -56,7 +60,7 @@ const PaperTradeHistoryList: React.FC<Props> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="ptd-trade-history-scroll">
|
<div className="ptd-trade-history-scroll">
|
||||||
<div className="vtd-target-list vtd-trade-list">
|
<div className="vtd-target-list vtd-trade-list">
|
||||||
{trades.map(t => {
|
{displayTrades.map(t => {
|
||||||
const { koreanName: ko, englishName: en } = resolveVirtualTargetNames(
|
const { koreanName: ko, englishName: en } = resolveVirtualTargetNames(
|
||||||
t.symbol,
|
t.symbol,
|
||||||
tickers?.get(t.symbol)?.koreanName,
|
tickers?.get(t.symbol)?.koreanName,
|
||||||
@@ -165,5 +169,6 @@ const PaperTradeHistoryList: React.FC<Props> = ({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default PaperTradeHistoryList;
|
export default PaperTradeHistoryList;
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { PaperTradeDto } from './backendApi';
|
||||||
|
import type { TradeNotificationItem } from '../contexts/TradeNotificationContext';
|
||||||
|
import type { TradeNotificationListSort } from './tradeNotificationListLayout';
|
||||||
|
|
||||||
|
/** 종목(마켓) 코드 비교 — 한글 정렬 */
|
||||||
|
export function compareMarketSymbol(a: string, b: string): number {
|
||||||
|
return a.localeCompare(b, 'ko');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** epoch ms·ISO 문자열·초 단위 타임스탬프 → 오름차순(이전 → 최근) */
|
||||||
|
export function compareTimeAsc(
|
||||||
|
a: number | string | null | undefined,
|
||||||
|
b: number | string | null | undefined,
|
||||||
|
): number {
|
||||||
|
return toTimeMs(a) - toTimeMs(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTimeMs(t: number | string | null | undefined): number {
|
||||||
|
if (t == null) return 0;
|
||||||
|
if (typeof t === 'number') {
|
||||||
|
if (!Number.isFinite(t)) return 0;
|
||||||
|
return t < 1e12 ? t * 1000 : t;
|
||||||
|
}
|
||||||
|
const d = new Date(t);
|
||||||
|
return Number.isNaN(d.getTime()) ? 0 : d.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 종목순 → 동일 종목은 체결·수신 시각 오름차순 (매수·매도 흐름) */
|
||||||
|
export function sortByMarketThenTimeAsc<T>(
|
||||||
|
items: readonly T[],
|
||||||
|
getMarket: (item: T) => string,
|
||||||
|
getTime: (item: T) => number | string | null | undefined,
|
||||||
|
): T[] {
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
const byMarket = compareMarketSymbol(getMarket(a), getMarket(b));
|
||||||
|
if (byMarket !== 0) return byMarket;
|
||||||
|
return compareTimeAsc(getTime(a), getTime(b));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortPaperTradesForDisplay(trades: readonly PaperTradeDto[]): PaperTradeDto[] {
|
||||||
|
return sortByMarketThenTimeAsc(trades, t => t.symbol, t => t.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortTradeNotifications(
|
||||||
|
items: TradeNotificationItem[],
|
||||||
|
sort: TradeNotificationListSort,
|
||||||
|
): TradeNotificationItem[] {
|
||||||
|
const arr = [...items];
|
||||||
|
if (sort === 'market') {
|
||||||
|
return sortByMarketThenTimeAsc(arr, n => n.market, n => n.receivedAt);
|
||||||
|
}
|
||||||
|
if (sort === 'oldest') {
|
||||||
|
return arr.sort((a, b) => compareTimeAsc(a.receivedAt, b.receivedAt));
|
||||||
|
}
|
||||||
|
return arr.sort((a, b) => compareTimeAsc(b.receivedAt, a.receivedAt));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user