가상투자 화면 목록 선택 동작
This commit is contained in:
@@ -3,7 +3,7 @@ import type { Timeframe } from '../types';
|
||||
/** 차트 Timeframe → 백엔드 candleType */
|
||||
export function timeframeToCandleType(tf: Timeframe): string {
|
||||
switch (tf) {
|
||||
case '1m': case '3m': case '5m': case '15m': case '30m': case '1h': case '4h':
|
||||
case '1m': case '3m': case '5m': case '10m': case '15m': case '30m': case '1h': case '4h':
|
||||
return tf;
|
||||
case '1D': return '1d';
|
||||
case '1W': case '1M': return '1d';
|
||||
|
||||
@@ -10,11 +10,11 @@ export const SYMBOLS = [
|
||||
];
|
||||
|
||||
const TF_SECONDS: Record<Timeframe, number> = {
|
||||
'1m':60,'3m':180,'5m':300,'15m':900,'30m':1800,'1h':3600,'4h':14400,'1D':86400,'1W':604800,'1M':2592000,
|
||||
'1m':60,'3m':180,'5m':300,'10m':600,'15m':900,'30m':1800,'1h':3600,'4h':14400,'1D':86400,'1W':604800,'1M':2592000,
|
||||
};
|
||||
|
||||
const TF_BARS: Record<Timeframe, number> = {
|
||||
'1m':300,'3m':300,'5m':300,'15m':300,'30m':300,'1h':500,'4h':500,'1D':500,'1W':300,'1M':120,
|
||||
'1m':300,'3m':300,'5m':300,'10m':300,'15m':300,'30m':300,'1h':500,'4h':500,'1D':500,'1W':300,'1M':120,
|
||||
};
|
||||
|
||||
const SEED_MAP: Record<string, number> = {
|
||||
|
||||
@@ -47,6 +47,7 @@ const CANDLE_KO: Record<string, string> = {
|
||||
'1m': '1분봉',
|
||||
'3m': '3분봉',
|
||||
'5m': '5분봉',
|
||||
'10m': '10분봉',
|
||||
'15m': '15분봉',
|
||||
'30m': '30분봉',
|
||||
'1h': '1시간봉',
|
||||
|
||||
@@ -4,7 +4,7 @@ export const START_NODE_ID = '__strategy_start__';
|
||||
export const DEFAULT_START_CANDLE = '1m';
|
||||
|
||||
export const STRATEGY_CANDLE_TYPES = [
|
||||
'1m', '3m', '5m', '15m', '30m', '1h', '4h', '1d',
|
||||
'1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1d',
|
||||
] as const;
|
||||
|
||||
export type StrategyCandleType = (typeof STRATEGY_CANDLE_TYPES)[number];
|
||||
|
||||
@@ -72,7 +72,7 @@ export function candleTypeToTimeframe(candleType: string): Timeframe {
|
||||
if (c === '1d') return '1D';
|
||||
if (c === '1h') return '1h';
|
||||
if (c === '4h') return '4h';
|
||||
if (['1m', '3m', '5m', '15m', '30m'].includes(c)) return c as Timeframe;
|
||||
if (['1m', '3m', '5m', '10m', '15m', '30m'].includes(c)) return c as Timeframe;
|
||||
return '1m';
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function resolveStrategyTimeframes(strategy: StrategyDto | undefined): Ti
|
||||
}
|
||||
if (set.size === 0) set.add('1m');
|
||||
return [...set].sort((a, b) => {
|
||||
const order: Timeframe[] = ['1m', '3m', '5m', '15m', '30m', '1h', '4h', '1D', '1W', '1M'];
|
||||
const order: Timeframe[] = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1D', '1W', '1M'];
|
||||
return order.indexOf(a) - order.indexOf(b);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ const CANDLE_TYPE_KO: Record<string, string> = {
|
||||
'1m': '1분봉',
|
||||
'3m': '3분봉',
|
||||
'5m': '5분봉',
|
||||
'10m': '10분봉',
|
||||
'15m': '15분봉',
|
||||
'30m': '30분봉',
|
||||
'1h': '1시간봉',
|
||||
|
||||
@@ -77,6 +77,7 @@ function candleEndpoint(
|
||||
case '1m': return `${base}/minutes/1?market=${market}&count=${count}${toParam}`;
|
||||
case '3m': return `${base}/minutes/3?market=${market}&count=${count}${toParam}`;
|
||||
case '5m': return `${base}/minutes/5?market=${market}&count=${count}${toParam}`;
|
||||
case '10m': return `${base}/minutes/10?market=${market}&count=${count}${toParam}`;
|
||||
case '15m': return `${base}/minutes/15?market=${market}&count=${count}${toParam}`;
|
||||
case '30m': return `${base}/minutes/30?market=${market}&count=${count}${toParam}`;
|
||||
case '1h': return `${base}/minutes/60?market=${market}&count=${count}${toParam}`;
|
||||
@@ -318,7 +319,7 @@ export class UpbitWebSocketClient {
|
||||
|
||||
// ─── 타임프레임 초 단위 ───────────────────────────────────────────────────
|
||||
export const TF_SECONDS: Record<Timeframe, number> = {
|
||||
'1m': 60, '3m': 180, '5m': 300, '15m': 900, '30m': 1800,
|
||||
'1m': 60, '3m': 180, '5m': 300, '10m': 600, '15m': 900, '30m': 1800,
|
||||
'1h': 3600, '4h': 14400, '1D': 86400, '1W': 604800, '1M': 2592000,
|
||||
};
|
||||
|
||||
|
||||
@@ -143,6 +143,29 @@ export function getTrafficLightState(matchRate: number, metrics: ConditionMetric
|
||||
return 'red';
|
||||
}
|
||||
|
||||
/** 매수·매도 조건 충족률 기준 타이밍 (TradeAlertModal: 매수=블루, 매도=레드) */
|
||||
export type VirtualTradeTiming = 'buy' | 'sell' | 'neutral';
|
||||
|
||||
function sideMatchRate(metrics: ConditionMetric[], side: 'buy' | 'sell'): number {
|
||||
const rows = metrics.filter(m => m.row.side === side && m.status !== 'unknown');
|
||||
if (rows.length === 0) return 0;
|
||||
const met = rows.filter(m => m.status === 'match').length;
|
||||
return Math.round((met / rows.length) * 100);
|
||||
}
|
||||
|
||||
export function resolveVirtualTradeTiming(metrics: ConditionMetric[]): VirtualTradeTiming {
|
||||
const buyRate = sideMatchRate(metrics, 'buy');
|
||||
const sellRate = sideMatchRate(metrics, 'sell');
|
||||
if (buyRate === 0 && sellRate === 0) return 'neutral';
|
||||
if (buyRate >= 100 && buyRate > sellRate) return 'buy';
|
||||
if (sellRate >= 100 && sellRate > buyRate) return 'sell';
|
||||
if (buyRate >= 100 && sellRate < 100) return 'buy';
|
||||
if (sellRate >= 100 && buyRate < 100) return 'sell';
|
||||
if (buyRate > sellRate && buyRate >= 50) return 'buy';
|
||||
if (sellRate > buyRate && sellRate >= 50) return 'sell';
|
||||
return 'neutral';
|
||||
}
|
||||
|
||||
export function formatUpdatedTime(ts: number | undefined): string {
|
||||
if (!ts) return '—';
|
||||
return new Date(ts).toLocaleTimeString('ko-KR', {
|
||||
|
||||
Reference in New Issue
Block a user