<?php
if (!defined('_GNUBOARD_')) exit;
add_stylesheet('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">', 0);
include_once(G5_LIB_PATH.'/thumbnail.lib.php');
// 가동 여부 확인
$is_active = ($view['x2_ca3'] == '1');
// 목표 타입 확인
$is_rate_type = ($view['x2_target_type'] == 'rate');
// 1. 목표가 미리 계산
$calc_target_price = 0;
if ($is_rate_type && $is_active) {
$watch_p = (float)str_replace(',', '', $view['wr_subject']);
$rate_val = (float)$view['x2_rate'] / 100;
$raw_margin = preg_replace("/[^0-9.]/", "", $view['x2_margin']);
$calc_margin = $raw_margin ? (float)$raw_margin : 1;
if ($view['x2_ca4'] == '롱') {
$calc_target_price = $watch_p * (1 + ($rate_val / $calc_margin));
} else {
$calc_target_price = $watch_p * (1 - ($rate_val / $calc_margin));
}
}
?>
<style>
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=JetBrains+Mono:wght@400;700&family=Noto+Sans+KR:wght@400;700;900&display=swap');
:root {
--neon-cyan: #00d4ff; --neon-pink: #ff2d55; --bg-black: #0d1117;
--panel-bg: #161b22; --input-bg: #0d1117; --border-color: #30363d;
--text-dim: #8b949e; --side-margin: 50px;
--price-up: #4ade80; --price-down: #ff2d55;
--electric-blue: #5de2ff;
--golden-yellow: #ffdf00;
--bright-green: #39ff14;
--dark-red: #8b0000;
--inverse-purple: #8a2be2;
color-scheme: dark;
}
#CYBER_LOADER { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--bg-black); z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: opacity 0.5s ease; }
.load-text { font-family: 'Orbitron'; color: var(--neon-cyan); letter-spacing: 3px; margin-bottom: 20px; font-size: 0.9rem; }
.load-bar-wrap { width: 200px; height: 1px; background: rgba(255,255,255,0.1); position: relative; }
.load-bar { width: 0%; height: 100%; background: var(--neon-cyan); animation: loadingProgress 1.5s forwards; }
@keyframes loadingProgress { to { width: 100%; } }
#VIEW_WRAP { position: relative; min-height: 100vh; background: var(--bg-black); padding: 40px 0; font-family: 'Pretendard', sans-serif; color: #c9d1d9; overflow-x: hidden; }
#space-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; opacity: 0.2; }
.view-container { position: relative; z-index: 2; width: calc(100% - 100px); margin: 0 var(--side-margin); max-width: none !important; animation: fadeIn 0.6s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
#v_frame { background: var(--panel-bg); border: 1px solid var(--border-color); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.5); overflow: hidden; position: relative; }
#v_frame::before, #v_frame::after { content: ''; position: absolute; width: 30px; height: 30px; border: 2px solid var(--neon-cyan); z-index: 10; pointer-events: none; }
#v_frame::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; border-radius: 8px 0 0 0; }
#v_frame::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; border-radius: 0 0 8px 0; }
.view-header { padding: 25px 35px; border-bottom: 1px solid var(--border-color); background: linear-gradient(90deg, rgba(0, 212, 255, 0.05), transparent); }
.view-header h2 { margin:0; font-family: 'Orbitron'; font-size: 1.1rem; color: var(--neon-cyan); font-weight: 700; letter-spacing: 1px; }
.info-meta { margin-top: 10px; font-family: 'JetBrains Mono'; font-size: 0.75rem; color: var(--text-dim); }
.view-info-bar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; padding: 20px 35px; background: rgba(0, 0, 0, 0.2); border-bottom: 1px solid var(--border-color); }
.info-tag { border: 1px solid var(--border-color); padding: 5px 15px; border-radius: 4px; font-size: 0.8rem; font-family: 'Noto Sans KR'; font-weight: 600; color: #fff; background: rgba(255,255,255,0.03); }
.info-tag.cyan { color: var(--neon-cyan); border-color: rgba(0, 212, 255, 0.3); }
.info-tag.coin-id { color: var(--neon-pink); border-color: var(--neon-pink); font-family: 'Orbitron'; letter-spacing: 1px; }
.status-badge { padding: 6px 20px; border-radius: 4px; font-size: 0.9rem; font-weight: 800; font-family: 'Noto Sans KR', sans-serif; letter-spacing: 1px; transition: 0.3s; color: #fff }
.stat-on { background:#1fca00; box-shadow: 0 0 15px rgba(57, 255, 20, 0.4); }
.stat-off { background: var(--dark-red); box-shadow: 0 0 15px rgba(139, 0, 0, 0.4); }
.dashboard-panel { display: flex; align-items: stretch; border-bottom: 1px solid var(--border-color); background: rgba(0,0,0,0.1); }
.dash-item { flex: 1; padding: 45px 10px; border-right: 1px solid var(--border-color); text-align: center; display: flex; flex-direction: column; justify-content: center; align-items: center; transition: 0.3s; position: relative; }
.dash-item:last-child { border-right: none; }
.dash-item:hover { background: rgba(255,255,255,0.03); }
.dash-label { font-family: 'Noto Sans KR'; font-size: 1rem; color: #ffffff; margin-bottom: 18px; font-weight: 700; letter-spacing: 0.5px; position: relative; padding-bottom: 8px; }
.dash-label::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 20px; height: 2px; background: rgba(255,255,255,0.2); }
.dash-value { font-family: 'JetBrains Mono'; font-size: 2.2rem; font-weight: 800; color: #fff; letter-spacing: -1px; line-height: 1; }
.dash-sub { font-family: 'JetBrains Mono'; font-size: 1.0rem; color: var(--text-dim); margin-top: 5px; font-weight: 500; }
.dash-watch .dash-value { color: var(--bright-green); text-shadow: 0 0 15px rgba(57, 255, 20, 0.4); }
.dash-target .dash-value { color: var(--golden-yellow); text-shadow: 0 0 20px rgba(255, 223, 0, 0.4); }
.dash-realtime .dash-value { color: var(--electric-blue); font-size: 2.5rem; text-shadow: 0 0 25px rgba(93, 226, 255, 0.5); }
#profit_indicator, #pnl_indicator { transition: all 0.5s ease; padding: 5px 15px; border-radius: 8px; }
.p-up { color: var(--price-up) !important; text-shadow: 0 0 15px rgba(74, 222, 128, 0.4); }
.p-down { color: var(--price-down) !important; text-shadow: 0 0 15px rgba(255, 45, 85, 0.4); }
.inverse-mode { background: rgba(138, 43, 226, 0.15); border: 1px solid rgba(138, 43, 226, 0.3); box-shadow: 0 0 15px rgba(138, 43, 226, 0.2); }
.status-reached { background: var(--bright-green) !important; color: #fff !important; box-shadow: 0 0 25px rgba(57, 255, 20, 0.5); border: none; padding: 8px 15px; border-radius: 20px; font-weight: 900; }
.status-waiting { color: #fff; padding: 8px 15px; border-radius: 20px; font-weight: 900; background: #333; }
/* 도달 차트 (Progress Bar) */
.view-chart-bg { width: 100%; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; margin-top: 10px; position: relative; }
.view-chart-bar { height: 100%; background: var(--neon-cyan); width: 0%; transition: width 0.5s ease; }
.view-tags { padding: 20px 35px; border-top: 1px solid var(--border-color); display: flex; flex-wrap: wrap; gap: 10px; }
.tag-link { color: var(--neon-pink); font-size: 0.75rem; text-decoration: none; opacity: 0.8; }
.view-footer { padding: 30px 35px; border-top: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; background: rgba(0, 0, 0, 0.1); }
.btn-action { border: 1px solid var(--border-color); background: transparent; color: var(--text-dim); padding: 8px 18px; border-radius: 4px; font-family: 'Orbitron'; font-weight: 600; cursor: pointer; transition: 0.2s; font-size: 0.7rem; text-decoration: none; text-transform: uppercase; }
.btn-action.highlight { background: var(--neon-cyan); color: #000; border: none; }
.view-nav { display: flex; border-top: 1px solid var(--border-color); }
.view-nav a { flex: 1; padding: 25px 35px; text-decoration: none; color: #fff; border-right: 1px solid var(--border-color); transition: 0.2s; }
.nav-label { font-family: 'Orbitron'; font-size: 0.6rem; color: var(--neon-cyan); display: block; margin-bottom: 5px; }
.nav-subject { font-size: 0.85rem; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 1200px) {
.dash-value { font-size: 1.8rem; }
.dash-realtime .dash-value { font-size: 2rem; }
}
@media (max-width: 1024px) {
:root { --side-margin: 20px; }
.view-container { width: calc(100% - 40px); margin: 0 20px; }
.dashboard-panel { flex-direction: column; }
.dash-item { border-right: none; border-bottom: 1px solid var(--border-color); padding: 35px 20px; }
.dash-value { font-size: 2.8rem; }
}
</style>
<div id="CYBER_LOADER"><div class="load-text">DECRYPTING_DATA</div><div class="load-bar-wrap"><div class="load-bar"></div></div></div>
<div id="VIEW_WRAP">
<canvas id="space-canvas"></canvas>
<article class="view-container">
<div id="v_frame">
<div class="view-header">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div>
<h2><i class="fa-solid fa-database"></i> <?php echo $board['bo_subject']; ?></h2>
<div class="info-meta">
<span style="color:var(--neon-cyan); margin-right:15px;">ID: <?php echo $view['wr_id']; ?></span>
<span><i class="fa-regular fa-clock"></i> <?php echo date("Y-m-d H:i", strtotime($view['wr_datetime'])); ?></span>
<span style="margin-left:15px;"><i class="fa-regular fa-eye"></i> <?php echo number_format($view['wr_hit']); ?></span>
</div>
</div>
</div>
</div>
<div class="view-info-bar">
<?php if($view['ca_name']) { ?><span class="info-tag"><?php echo $view['ca_name']; ?></span><?php } ?>
<?php if($view['x2_ca2']) { ?><span class="info-tag"><?php echo $view['x2_ca2']; ?></span><?php } ?>
<?php if($view['x2_ca4']) { ?><span class="info-tag cyan"><?php echo $view['x2_ca4']; ?></span><?php } ?>
<?php if($view['x2_coin']) { ?><span class="info-tag coin-id"><i class="fa-solid fa-coins"></i> <?php echo $view['x2_coin']; ?></span><?php } ?>
<?php
$stat_text = $is_active ? '가동중' : '정지됨';
$stat_class = $is_active ? 'stat-on' : 'stat-off';
?>
<span class="status-badge <?php echo $stat_class; ?>"><?php echo $stat_text; ?></span>
</div>
<div class="dashboard-panel">
<div class="dash-item dash-watch">
<div class="dash-label">감시금액</div>
<div class="dash-value"><?php echo number_format($view['wr_subject'], 4); ?></div>
</div>
<div class="dash-item dash-target">
<?php if ($is_rate_type) { ?>
<div class="dash-label">목표수익률</div>
<div class="dash-value"><?php echo $is_active ? number_format((float)$view['x2_rate'], 2) . '%' : '-'; ?></div>
<div class="dash-sub">(<?php echo number_format($calc_target_price); ?>)</div>
<?php } else { ?>
<div class="dash-label">목표금액</div>
<div class="dash-value"><?php echo $is_active ? number_format((float)$view['x2_target']) : '-'; ?></div>
<?php } ?>
</div>
<div class="dash-item dash-realtime">
<div class="dash-label">실시간 현재금액</div>
<div class="dash-value" id="realtime_upbit_price"><?php echo $is_active ? 'LOADING...' : '-'; ?></div>
</div>
<div class="dash-item">
<div class="dash-label">변동액</div>
<div class="dash-value" id="diff_indicator" style="font-size:1.8rem;">-</div>
</div>
</div>
<div class="dashboard-panel">
<div class="dash-item">
<div class="dash-label">수량</div>
<div class="dash-value" style="font-size:1.8rem; color:#fff;"><?php echo $is_active ? number_format((float)$view['x2_qty'], 4) : '-'; ?></div>
</div>
<div class="dash-item">
<div class="dash-label">배율</div>
<div class="dash-value" style="font-size:1.8rem; color:var(--neon-pink);">x<?php echo $is_active ? $view['x2_margin'] : '-'; ?></div>
</div>
<div class="dash-item">
<div class="dash-label">계정</div>
<div class="dash-value" style="font-size:1.2rem; font-family:'Orbitron'; color:var(--electric-blue);"><?php echo $is_active ? $view['x2_account'] : '-'; ?></div>
</div>
<div class="dash-item">
<div class="dash-label">수익금</div>
<div class="dash-value" id="pnl_indicator">-</div>
</div>
</div>
<div class="dashboard-panel">
<div class="dash-item">
<div class="dash-label">수익률 (ROE)</div>
<div class="dash-value" id="profit_indicator">-</div>
</div>
<div class="dash-item">
<div class="dash-label">도달률</div>
<div class="dash-value" id="progress_percent" style="color:var(--golden-yellow);">-</div>
</div>
<div class="dash-item">
<div class="dash-label">도달 상태</div>
<div style="text-align:center;">
<span id="target_status_text" class="status-waiting"><?php echo $is_active ? '분석 중...' : '-'; ?></span>
<div class="view-chart-bg">
<div id="progress_chart_bar" class="view-chart-bar"></div>
</div>
</div>
</div>
</div>
<?php if ($view['x2_tag']) { ?>
<div class="view-tags">
<span style="font-size:0.75rem; color:var(--text-dim);">태그 : </span><?php $tags = explode(',', $view['x2_tag']); foreach($tags as $t) { $t = trim($t); if($t) echo '<a href="'.G5_BBS_URL.'/board.php?bo_table='.$bo_table.'&stx='.urlencode($t).'&sfl=wr_subject||wr_content" class="tag-link">#'.$t.'</a>'; } ?>
</div>
<?php } ?>
<div class="view-footer">
<div style="display:flex; gap:8px;">
<a href="<?php echo $list_href; ?>" class="btn-action">감시 코인 목록</a>
<?php if ($write_href) { ?><a href="<?php echo $write_href; ?>" class="btn-action highlight">새 코인 등록</a><?php } ?>
</div>
<div style="display:flex; gap:8px;">
<?php if ($update_href) { ?><a href="<?php echo $update_href; ?>" class="btn-action highlight">코인 감시 수정</a><?php } ?>
<?php if ($delete_href) { ?><a href="<?php echo $delete_href; ?>" class="btn-action" onclick="return confirm('PURGE DATA FROM SYSTEM?');">삭제</a><?php } ?>
</div>
</div>
<nav class="view-nav">
<?php if ($prev_href) { ?><a href="<?php echo $prev_href; ?>"><span class="nav-label"><i class="fa-solid fa-angle-left"></i> PREV_SECTOR</span><div class="nav-subject"><?php echo $prev_wr_subject; ?></div></a><?php } ?>
<?php if ($next_href) { ?><a href="<?php echo $next_href; ?>" style="text-align:right;"><span class="nav-label">NEXT_SECTOR <i class="fa-solid fa-angle-right"></i></span><div class="nav-subject"><?php echo $next_wr_subject; ?></div></a><?php } ?>
</nav>
</div>
</article>
</div>
<script src="<?php echo G5_JS_URL; ?>/viewimageresize.js"></script>
<script>
// ★★★ [오빠가 원하는 보정값 설정] 여기서 숫자를 바꾸세요! ★★★
const ROE_FIX = 1.0;
window.addEventListener('load', function() { setTimeout(() => { document.getElementById('CYBER_LOADER').style.opacity = '0'; setTimeout(() => document.getElementById('CYBER_LOADER').style.visibility = 'hidden', 500); }, 800); });
function fetchRealtimeData() {
const isActive = "<?php echo $view['x2_ca3']; ?>" === "1";
if (!isActive) {
document.getElementById('realtime_upbit_price').innerText = "-";
document.getElementById('profit_indicator').innerText = "-";
document.getElementById('pnl_indicator').innerText = "-";
document.getElementById('diff_indicator').innerText = "-";
document.getElementById('progress_percent').innerText = "-";
document.getElementById('target_status_text').innerText = "-";
return;
}
const coinSymbol = "<?php echo $view['x2_coin']; ?>";
const watchPrice = parseFloat("<?php echo str_replace(',', '', $view['wr_subject']); ?>");
const targetType = "<?php echo $view['x2_target_type']; ?>";
const position = "<?php echo $view['x2_ca4']; ?>";
const rawMargin = "<?php echo $view['x2_margin']; ?>";
const margin = parseFloat(rawMargin.replace(/[^0-9.]/g, '')) || 1;
const qty = parseFloat("<?php echo $view['x2_qty']; ?>") || 1;
let targetPrice = 0;
if (targetType === 'rate') {
const rate = parseFloat("<?php echo $view['x2_rate']; ?>") / 100;
if (position === "롱") {
targetPrice = watchPrice * (1 + (rate / margin));
} else {
targetPrice = watchPrice * (1 - (rate / margin));
}
} else {
targetPrice = parseFloat("<?php echo str_replace(',', '', $view['x2_target']); ?>");
}
if (!coinSymbol) return;
const symUpper = coinSymbol.toUpperCase().trim();
let category = 'linear';
if (symUpper.endsWith('USD') && !symUpper.endsWith('USDT')) {
category = 'inverse';
}
const randomStr = Math.random().toString(36).substring(7);
fetch(`https://api.bybit.com/v5/market/tickers?category=${category}&symbol=${encodeURIComponent(symUpper)}&_t=${new Date().getTime()}&_r=${randomStr}`)
.then(response => response.json())
.then(json => {
const list = json && json.result && json.result.list ? json.result.list : [];
if (list && list[0]) {
const tradePrice = parseFloat(list[0].lastPrice || list[0].last_price || 0);
if (!tradePrice) return;
const formattedPrice = new Intl.NumberFormat('ko-KR').format(tradePrice);
document.getElementById('realtime_upbit_price').innerText = formattedPrice;
// [계산 로직]
const isInverse = (category === 'inverse');
let roe = 0;
let pnl = 0;
let diff = 0;
const profitBox = document.getElementById('profit_indicator');
const pnlBox = document.getElementById('pnl_indicator');
if (isInverse) profitBox.classList.add('inverse-mode');
else profitBox.classList.remove('inverse-mode');
if (position === "숏") {
diff = watchPrice - tradePrice;
if (isInverse) {
roe = ((watchPrice / tradePrice) - 1) * 100 * margin * ROE_FIX;
pnl = qty * (1 - (tradePrice / watchPrice));
} else {
roe = ((watchPrice - tradePrice) / watchPrice) * 100 * margin * ROE_FIX;
pnl = (watchPrice - tradePrice) * qty;
}
} else {
diff = tradePrice - watchPrice;
if (isInverse) {
roe = (1 - (watchPrice / tradePrice)) * 100 * margin * ROE_FIX;
pnl = qty * ((tradePrice / watchPrice) - 1);
} else {
roe = ((tradePrice - watchPrice) / watchPrice) * 100 * margin * ROE_FIX;
pnl = (tradePrice - watchPrice) * qty;
}
}
const pClass = roe > 0 ? "p-up" : (roe < 0 ? "p-down" : "");
// 1. 변동액
const diffHtml = `<span class="${pClass}">${diff >= 0 ? '+' : ''}${new Intl.NumberFormat('ko-KR').format(diff)}</span>`;
document.getElementById('diff_indicator').innerHTML = diffHtml;
// 2. 수익금
const pnlHtml = `<span class="${pClass}">${pnl >= 0 ? '+' : ''}${new Intl.NumberFormat('ko-KR').format(pnl)}</span>`;
pnlBox.innerHTML = pnlHtml;
// 3. 수익률
const roeHtml = `<span class="${pClass}">${roe.toFixed(2)}%</span>`;
profitBox.innerHTML = roeHtml;
// 4. 도달률 & 차트
let reached = false;
let progress = 0;
const totalMove = Math.abs(targetPrice - watchPrice);
const currentMove = (position === "숏") ? (watchPrice - tradePrice) : (tradePrice - watchPrice);
// ★★★ [도달률 10으로 나누기 패치 적용] ★★★
if (totalMove > 0) {
progress = ((currentMove / totalMove) * 100);
}
if (position === "롱") reached = tradePrice >= targetPrice;
else reached = tradePrice <= targetPrice;
const statusText = document.getElementById('target_status_text');
const chartBar = document.getElementById('progress_chart_bar');
const progressText = document.getElementById('progress_percent');
// 도달률 숫자 표시
progressText.innerText = progress.toFixed(1) + "%";
if (reached) {
statusText.innerText = "목표 달성!";
statusText.className = "status-reached";
chartBar.style.width = "100%";
} else {
statusText.innerText = "진행 중";
statusText.className = "status-waiting";
const barWidth = Math.max(0, Math.min(100, progress));
chartBar.style.width = barWidth + "%";
}
}
});
}
$(function() {
fetchRealtimeData();
setInterval(fetchRealtimeData, 1000);
$(".view-content img").viewimageresize();
const canvas = document.getElementById('space-canvas'); const ctx = canvas.getContext('2d'); let stars = [];
function initSpace() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; stars = []; for(let i=0; i<80; i++) stars.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, size: Math.random() * 1.1, speed: Math.random() * 0.15 }); }
function animateSpace() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "rgba(201, 209, 217, 0.3)"; stars.forEach(star => { ctx.beginPath(); ctx.arc(star.x, star.y, star.size, 0, Math.PI*2); ctx.fill(); star.y += star.speed; if(star.y > canvas.height) star.y = 0; }); requestAnimationFrame(animateSpace); }
window.addEventListener('resize', initSpace); initSpace(); animateSpace();
});
</script>