diff --git a/README.md b/README.md index 2081322..0a8daa2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# UpAndDown-Client -About [오르락내리락] 과거의 차트로 공부하고! 나만의 종목으로 예측까지 해보는 주식 차트 학습 서비스 +# Candly-Client +About [Candly] 과거의 차트로 공부하고! 나만의 종목으로 예측까지 해보는 주식 차트 학습 서비스 diff --git a/src/components/Navbar/navbar.client.tsx b/src/components/Navbar/navbar.client.tsx index 8893c6e..41283a7 100644 --- a/src/components/Navbar/navbar.client.tsx +++ b/src/components/Navbar/navbar.client.tsx @@ -10,7 +10,7 @@ import Image from "next/image"; const menuItems = [ { label: "홈", href: "/" }, { label: "연습문제", href: "/practice" }, - { label: "실전투자", href: "/investment", dynamic: true }, + { label: "실전예측", href: "/investment", dynamic: true }, { label: "랭킹", href: "/ranking" }, { label: "마이페이지", href: "/mypage" }, ]; @@ -52,7 +52,7 @@ export default function Navbar() { router.push("/investment"); } } catch (err) { - console.error("실전투자 이동 중 오류:", err); + console.error("실전예측 이동 중 오류:", err); router.push("/investment"); } }; diff --git a/src/components/blocks/Investment/InvestmentStock/InvestmentStock.client.tsx b/src/components/blocks/Investment/InvestmentStock/InvestmentStock.client.tsx index 8b480bf..21cc373 100644 --- a/src/components/blocks/Investment/InvestmentStock/InvestmentStock.client.tsx +++ b/src/components/blocks/Investment/InvestmentStock/InvestmentStock.client.tsx @@ -393,7 +393,7 @@ export default function InvestmentStockClient() { )} setShowIndicators((prev) => !prev)} > {showIndicators ? "– 보조지표 접기" : "+ 보조지표 설정"} diff --git a/src/components/blocks/MainHome/MainHome.client.tsx b/src/components/blocks/MainHome/MainHome.client.tsx index 52ad417..1aa13c6 100644 --- a/src/components/blocks/MainHome/MainHome.client.tsx +++ b/src/components/blocks/MainHome/MainHome.client.tsx @@ -36,7 +36,7 @@ export default function MainHomeClient() { router.push("/investment"); } } catch (err) { - console.error("실전투자 이동 중 오류:", err); + console.error("실전예측 이동 중 오류:", err); router.push("/investment"); } }; diff --git a/src/components/blocks/MyPageInvestment/MyPageInvestment.client.tsx b/src/components/blocks/MyPageInvestment/MyPageInvestment.client.tsx index 3a59787..74b6caa 100644 --- a/src/components/blocks/MyPageInvestment/MyPageInvestment.client.tsx +++ b/src/components/blocks/MyPageInvestment/MyPageInvestment.client.tsx @@ -101,8 +101,20 @@ export default function MyPageInvestmentClient() { }); const validScores = stockResult.stocks - .map((item: any) => item.cumulative_score) - .filter((score: any) => typeof score === "number"); + .map((item: any) => { + const predictCount = + investResult.find( + (s: any) => + s.stock_code === item.stock_code?._id || + s.stock_id === item._id + )?.scores?.length ?? 0; + + return predictCount > 0 && typeof item.cumulative_score === "number" + ? item.cumulative_score + : null; + }) + .filter((score: any) => score !== null); + const avg = validScores.length > 0 ? Math.round( @@ -124,7 +136,7 @@ export default function MyPageInvestmentClient() { return (
-

실전투자 히스토리

+

실전예측 히스토리

diff --git a/src/components/blocks/MyPagePractice/MyPagePractice.client.tsx b/src/components/blocks/MyPagePractice/MyPagePractice.client.tsx index 1b49a68..e91d084 100644 --- a/src/components/blocks/MyPagePractice/MyPagePractice.client.tsx +++ b/src/components/blocks/MyPagePractice/MyPagePractice.client.tsx @@ -136,7 +136,7 @@ export default function MyPagePracticeClient() {
diff --git a/src/components/blocks/PracticePage/PracticePage.client.tsx b/src/components/blocks/PracticePage/PracticePage.client.tsx index 9fe798e..0790165 100644 --- a/src/components/blocks/PracticePage/PracticePage.client.tsx +++ b/src/components/blocks/PracticePage/PracticePage.client.tsx @@ -317,7 +317,7 @@ export default function PracticeClient() { )} setShowIndicators((prev) => !prev)} > {showIndicators ? "– 보조지표 접기" : "+ 보조지표 설정"} @@ -426,6 +426,7 @@ export default function PracticeClient() { onChange={(e) => setInput(e.target.value)} placeholder="답변을 입력하세요" maxLength={300} + disabled={isAnswered} className="w-full h-32 p-4 rounded border border-gray-600 bg-transparent resize-none focus:outline-none" />
@@ -435,7 +436,7 @@ export default function PracticeClient() { diff --git a/src/components/charts/InvestCandleChart.tsx b/src/components/charts/InvestCandleChart.tsx index 4304324..ae49855 100644 --- a/src/components/charts/InvestCandleChart.tsx +++ b/src/components/charts/InvestCandleChart.tsx @@ -385,14 +385,32 @@ export default function InvestCandleChart({ visibleCandles > 1 ? chartWidth / (visibleCandles - 1) : chartWidth; const candleWidth = Math.min(40, candleSpacing * 0.7, 24); + // function getLinePoints( + // arr: (number | null)[], + // candleSpacing: number, + // getY: (v: number) => number + // ) { + // return arr + // .map((val, i) => + // typeof val === "number" && !isNaN(val) + // ? `${i * candleSpacing},${getY(val)}` + // : null + // ) + // .filter(Boolean) + // .join(" "); + // } function getLinePoints( arr: (number | null)[], candleSpacing: number, - getY: (v: number) => number + getY: (v: number) => number, + slicedData: Candle[] ) { return arr .map((val, i) => - typeof val === "number" && !isNaN(val) + typeof val === "number" && + !isNaN(val) && + slicedData[i] && + !isDotOnlyCandle(slicedData[i]) ? `${i * candleSpacing},${getY(val)}` : null ) @@ -400,24 +418,72 @@ export default function InvestCandleChart({ .join(" "); } - const ma5Points = getLinePoints(ma5_visible, candleSpacing, getY); - const ma20Points = getLinePoints(ma20_visible, candleSpacing, getY); - const ma60Points = getLinePoints(ma60_visible, candleSpacing, getY); - const ma120Points = getLinePoints(ma120_visible, candleSpacing, getY); + // 캔들이 정상적인 날인지 확인하는 유틸 함수 + function isDotOnlyCandle(candle: Candle) { + return ( + candle.open === candle.close && + candle.high === candle.close && + candle.low === candle.close && + candle.volume === 0 + ); + } + + // const ma5Points = getLinePoints(ma5_visible, candleSpacing, getY); + // const ma20Points = getLinePoints(ma20_visible, candleSpacing, getY); + // const ma60Points = getLinePoints(ma60_visible, candleSpacing, getY); + // const ma120Points = getLinePoints(ma120_visible, candleSpacing, getY); + // const bb_upper_points = getLinePoints( + // bb_visible.map((b) => b?.upper), + // candleSpacing, + // getY + // ); + // const bb_middle_points = getLinePoints( + // bb_visible.map((b) => b?.middle), + // candleSpacing, + // getY + // ); + // const bb_lower_points = getLinePoints( + // bb_visible.map((b) => b?.lower), + // candleSpacing, + // getY + // ); + const ma5Points = getLinePoints(ma5_visible, candleSpacing, getY, slicedData); + const ma20Points = getLinePoints( + ma20_visible, + candleSpacing, + getY, + slicedData + ); + const ma60Points = getLinePoints( + ma60_visible, + candleSpacing, + getY, + slicedData + ); + const ma120Points = getLinePoints( + ma120_visible, + candleSpacing, + getY, + slicedData + ); + const bb_upper_points = getLinePoints( bb_visible.map((b) => b?.upper), candleSpacing, - getY + getY, + slicedData ); const bb_middle_points = getLinePoints( bb_visible.map((b) => b?.middle), candleSpacing, - getY + getY, + slicedData ); const bb_lower_points = getLinePoints( bb_visible.map((b) => b?.lower), candleSpacing, - getY + getY, + slicedData ); const chartRef = useRef(null); @@ -481,7 +547,13 @@ export default function InvestCandleChart({ const lowerPoints: string[] = []; bb_visible.forEach((bb, i) => { - if (bb?.upper && bb?.lower) { + const candle = slicedData[i]; + if ( + bb?.upper && + bb?.lower && + candle && // candle 존재 확인 + !isDotOnlyCandle(candle) // dot 전용 데이터 제외 + ) { const x = i * candleSpacing; upperPoints.push(`${x},${getY(bb.upper)}`); lowerPoints.push(`${x},${getY(bb.lower)}`); @@ -490,12 +562,11 @@ export default function InvestCandleChart({ if (upperPoints.length === 0) return ""; - // 상단선을 그리고, 하단선을 역순으로 연결해서 닫힌 영역 만들기 const pathData = [ - `M ${upperPoints[0]}`, // 시작점으로 이동 - `L ${upperPoints.slice(1).join(" L ")}`, // 상단선 그리기 - `L ${lowerPoints.slice().reverse().join(" L ")}`, // 하단선을 역순으로 그리기 - "Z", // path 닫기 + `M ${upperPoints[0]}`, + `L ${upperPoints.slice(1).join(" L ")}`, + `L ${lowerPoints.slice().reverse().join(" L ")}`, + "Z", ].join(" "); return pathData; @@ -504,7 +575,7 @@ export default function InvestCandleChart({ // --- 렌더 --- return (
- typeof val === "number" && isFinite(val) + typeof val === "number" && + isFinite(val) && + !isDotOnlyCandle(slicedData[i]) ? `${i * candleSpacing},${(1 - val / 100) * RSI_HEIGHT}` : null ) .filter((v): v is string => v !== null) - .join(" ")} opacity={0.96} /> @@ -995,13 +1067,15 @@ export default function InvestCandleChart({
{/* 일반 candle 값 or dot 전용 candle 값 구분 */} {(() => { + // dotData도 있는 날짜면 const dot = dotData?.find((d) => dayjs(d.date).isSame(tooltip.data!.date, "day") ); + // "오늘" 날짜인지 확인 const isToday = dayjs(tooltip.data!.date).isSame(dayjs(), "day"); - // 실시간 시세 + 예측값만 있는 경우 (오늘) - if (isToday && todayPrice && dot?.close) { + // 오늘이고 todayPrice가 있을 때 (실시간) + if (isToday && todayPrice && dot && dot.close) { return ( <>
@@ -1011,7 +1085,8 @@ export default function InvestCandleChart({ : {todayPrice.toLocaleString()}
- + + 예측값 : {dot.close.toLocaleString()} @@ -1020,55 +1095,47 @@ export default function InvestCandleChart({ ); } - // 예측값만 있는 경우 - if ( - dot?.close && - (!tooltip.data?.open || tooltip.data.volume === 0) - ) { + // dotData(예측값)가 있는 경우 + if (dot) { return (
- + 예측값 - + {" "} : {dot.close.toLocaleString()}
); } - - // 일반 캔들값 (or 예측값도 있는 경우 같이 표시) - const rows = []; - - rows.push( -
시: {tooltip.data.open.toLocaleString()}
, -
고: {tooltip.data.high.toLocaleString()}
, -
저: {tooltip.data.low.toLocaleString()}
, -
종: {tooltip.data.close.toLocaleString()}
, -
- 거래량: {tooltip.data.volume.toLocaleString()} -
, -
RSI: {rsi}
- ); - - if (dot?.close) { - rows.push( -
- - 예측값 - - : {dot.close.toLocaleString()} -
, -
- 오차: {(dot.close - tooltip.data.close).toFixed(2)} ( - {( - ((dot.close - tooltip.data.close) / tooltip.data.close) * - 100 - ).toFixed(2)} - %) + // 일반 candle 값 표기 + return ( + <> +
시: {tooltip.data.open.toLocaleString()}
+
고: {tooltip.data.high.toLocaleString()}
+
저: {tooltip.data.low.toLocaleString()}
+
종: {tooltip.data.close.toLocaleString()}
+
거래량: {tooltip.data.volume.toLocaleString()}
+
+ {/* RSI:{" "} + {typeof rsi_visible[tooltip.idx] === "number" + ? rsi_visible[tooltip.idx].toFixed(2) + : "-"} */} + RSI: {rsi}
- ); - } - - return rows; + {/* dot값이 겹치는 경우 오차 등도 표시 */} + {dot && (dot as any).close !== undefined && ( +
+ 오차: {((dot as any).close - tooltip.data.close).toFixed(2)}{" "} + ( + {( + (((dot as any).close - tooltip.data.close) / + tooltip.data.close) * + 100 + ).toFixed(2)} + %) +
+ )} + + ); })()} {/* 뉴스 영역 그대로 */} {tooltipNews.length > 0 && (