Initial commit: BTC/USDT Trading View with technical indicators
This commit is contained in:
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
build/
|
||||
55
Caddyfile.example
Normal file
55
Caddyfile.example
Normal file
@@ -0,0 +1,55 @@
|
||||
# Caddyfile 설정 예시 - Authentik Forward Auth 사용
|
||||
# 이 내용을 기존 Caddyfile에 추가하세요
|
||||
|
||||
trading.yourdomain.com {
|
||||
# Authentik Forward Auth
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
|
||||
# 인증 후 전달할 헤더들
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
||||
|
||||
# 신뢰할 수 있는 프록시 (Docker 네트워크)
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
|
||||
# 앱으로 프록시
|
||||
reverse_proxy bini-trading-view:80
|
||||
|
||||
# 로그 (선택사항)
|
||||
log {
|
||||
output file /var/log/caddy/trading.log
|
||||
}
|
||||
}
|
||||
|
||||
# ========================================
|
||||
# Authentik 설정 단계
|
||||
# ========================================
|
||||
#
|
||||
# 1. Authentik 관리자 페이지 접속
|
||||
# https://authentik.yourdomain.com/if/admin/
|
||||
#
|
||||
# 2. Provider 생성
|
||||
# - Applications > Providers > Create
|
||||
# - Type: Proxy Provider
|
||||
# - Name: trading-view-provider
|
||||
# - Authorization flow: default-provider-authorization-implicit-consent
|
||||
# - Forward auth (single application) 선택
|
||||
# - External host: https://trading.yourdomain.com
|
||||
#
|
||||
# 3. Application 생성
|
||||
# - Applications > Applications > Create
|
||||
# - Name: Trading View
|
||||
# - Slug: trading-view
|
||||
# - Provider: trading-view-provider (위에서 생성한 것)
|
||||
#
|
||||
# 4. Outpost 설정
|
||||
# - Applications > Outposts
|
||||
# - 기존 Outpost 편집 또는 새로 생성
|
||||
# - Applications에 "Trading View" 추가
|
||||
#
|
||||
# 5. Docker 네트워크 확인
|
||||
# - Caddy와 Authentik이 같은 네트워크에 있어야 함
|
||||
# - authentik-server는 Authentik 서버 컨테이너 이름
|
||||
#
|
||||
# ========================================
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy static files
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
COPY styles.css /usr/share/nginx/html/
|
||||
COPY app.js /usr/share/nginx/html/
|
||||
COPY indicators.js /usr/share/nginx/html/
|
||||
|
||||
# Copy nginx config
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
bini-trading-view:
|
||||
build: .
|
||||
container_name: bini-trading-view
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
networks:
|
||||
- caddy_network # Caddy와 같은 네트워크
|
||||
|
||||
networks:
|
||||
caddy_network:
|
||||
external: true # 기존 Caddy 네트워크에 연결 (네트워크 이름은 환경에 맞게 수정)
|
||||
153
index.html
Normal file
153
index.html
Normal file
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bitcoin Trading View</title>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.1.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<div class="title-section">
|
||||
<h1>BTC/USDT Real-time Trading View</h1>
|
||||
<select id="timeframe">
|
||||
<option value="1m">1분</option>
|
||||
<option value="5m">5분</option>
|
||||
<option value="15m" selected>15분</option>
|
||||
<option value="1h">1시간</option>
|
||||
<option value="4h">4시간</option>
|
||||
<option value="1d">1일</option>
|
||||
<option value="1w">1주</option>
|
||||
<option value="1M">1월</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="price-info">
|
||||
<span id="current-price">--</span>
|
||||
<span id="price-change">--</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="charts-container">
|
||||
<!-- Main Chart with Candlestick, MA, Bollinger Bands -->
|
||||
<div class="chart-wrapper main-chart">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">Price Chart</span>
|
||||
<div class="legend" id="main-legend">
|
||||
<label class="ma-toggle ma5"><input type="checkbox" id="toggle-ma5" checked><span>MA5</span></label>
|
||||
<label class="ma-toggle ma20"><input type="checkbox" id="toggle-ma20" checked><span>MA20</span></label>
|
||||
<label class="ma-toggle ma60"><input type="checkbox" id="toggle-ma60" checked><span>MA60</span></label>
|
||||
<label class="ma-toggle ma120"><input type="checkbox" id="toggle-ma120"><span>MA120</span></label>
|
||||
<label class="ma-toggle ma200"><input type="checkbox" id="toggle-ma200"><span>MA200</span></label>
|
||||
<label class="ma-toggle bb"><input type="checkbox" id="toggle-bb" checked><span>BB</span></label>
|
||||
<label class="ma-toggle gc-dc"><input type="checkbox" id="toggle-gcdc"><span>GC/DC</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-chart"></div>
|
||||
<div id="signals-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- MACD Chart -->
|
||||
<div class="chart-wrapper indicator-chart">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">MACD (12, 26, 9)</span>
|
||||
</div>
|
||||
<div id="macd-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- RSI Chart -->
|
||||
<div class="chart-wrapper indicator-chart">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">RSI (14)</span>
|
||||
</div>
|
||||
<div id="rsi-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Stochastic Chart -->
|
||||
<div class="chart-wrapper indicator-chart">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">Stochastic (14, 3, 3)</span>
|
||||
</div>
|
||||
<div id="stochastic-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis Panel -->
|
||||
<div class="analysis-panel">
|
||||
<div class="analysis-header">
|
||||
<h3>Candle Analysis</h3>
|
||||
<span id="analysis-time">--</span>
|
||||
</div>
|
||||
|
||||
<!-- Overall Signal -->
|
||||
<div class="overall-signal" id="overall-signal">
|
||||
<span class="signal-label">Overall</span>
|
||||
<span class="signal-value neutral">NEUTRAL</span>
|
||||
</div>
|
||||
|
||||
<!-- Korean Analysis -->
|
||||
<div class="korean-analysis">
|
||||
<div class="korean-analysis-header">
|
||||
<span>분석 요약</span>
|
||||
</div>
|
||||
<p id="korean-analysis-text">데이터 로딩 중...</p>
|
||||
</div>
|
||||
|
||||
<!-- Indicator Analysis -->
|
||||
<div class="indicator-analysis">
|
||||
<div class="analysis-item" id="analysis-ma">
|
||||
<span class="item-label">MA Trend</span>
|
||||
<span class="item-value neutral">--</span>
|
||||
<span class="item-detail">--</span>
|
||||
</div>
|
||||
<div class="analysis-item" id="analysis-bb">
|
||||
<span class="item-label">Bollinger</span>
|
||||
<span class="item-value neutral">--</span>
|
||||
<span class="item-detail">--</span>
|
||||
</div>
|
||||
<div class="analysis-item" id="analysis-macd">
|
||||
<span class="item-label">MACD</span>
|
||||
<span class="item-value neutral">--</span>
|
||||
<span class="item-detail">--</span>
|
||||
</div>
|
||||
<div class="analysis-item" id="analysis-rsi">
|
||||
<span class="item-label">RSI</span>
|
||||
<span class="item-value neutral">--</span>
|
||||
<span class="item-detail">--</span>
|
||||
</div>
|
||||
<div class="analysis-item" id="analysis-stoch">
|
||||
<span class="item-label">Stochastic</span>
|
||||
<span class="item-value neutral">--</span>
|
||||
<span class="item-detail">--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="analysis-summary">
|
||||
<h4>Summary</h4>
|
||||
<p id="analysis-summary-text">Loading...</p>
|
||||
</div>
|
||||
|
||||
<!-- Score -->
|
||||
<div class="analysis-score">
|
||||
<div class="score-bar">
|
||||
<div class="score-fill" id="score-fill"></div>
|
||||
<div class="score-marker" id="score-marker"></div>
|
||||
</div>
|
||||
<div class="score-labels">
|
||||
<span>Strong Sell</span>
|
||||
<span>Neutral</span>
|
||||
<span>Strong Buy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="indicators.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
376
indicators.js
Normal file
376
indicators.js
Normal file
@@ -0,0 +1,376 @@
|
||||
// Technical Indicators Calculator
|
||||
|
||||
class TechnicalIndicators {
|
||||
|
||||
// Simple Moving Average (SMA)
|
||||
static SMA(data, period) {
|
||||
const result = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < period - 1) {
|
||||
result.push({ time: data[i].time, value: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
sum += data[i - j].close;
|
||||
}
|
||||
result.push({ time: data[i].time, value: sum / period });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exponential Moving Average (EMA)
|
||||
static EMA(data, period) {
|
||||
const result = [];
|
||||
const multiplier = 2 / (period + 1);
|
||||
|
||||
// First EMA uses SMA
|
||||
let sum = 0;
|
||||
for (let i = 0; i < period; i++) {
|
||||
sum += data[i].close;
|
||||
result.push({ time: data[i].time, value: null });
|
||||
}
|
||||
result[period - 1] = { time: data[period - 1].time, value: sum / period };
|
||||
|
||||
// Calculate EMA for remaining data
|
||||
for (let i = period; i < data.length; i++) {
|
||||
const ema = (data[i].close - result[i - 1].value) * multiplier + result[i - 1].value;
|
||||
result.push({ time: data[i].time, value: ema });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bollinger Bands
|
||||
static BollingerBands(data, period = 20, stdDev = 2) {
|
||||
const sma = this.SMA(data, period);
|
||||
const upper = [];
|
||||
const lower = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < period - 1) {
|
||||
upper.push({ time: data[i].time, value: null });
|
||||
lower.push({ time: data[i].time, value: null });
|
||||
} else {
|
||||
// Calculate standard deviation
|
||||
let sumSquares = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
sumSquares += Math.pow(data[i - j].close - sma[i].value, 2);
|
||||
}
|
||||
const std = Math.sqrt(sumSquares / period);
|
||||
|
||||
upper.push({ time: data[i].time, value: sma[i].value + stdDev * std });
|
||||
lower.push({ time: data[i].time, value: sma[i].value - stdDev * std });
|
||||
}
|
||||
}
|
||||
|
||||
return { middle: sma, upper, lower };
|
||||
}
|
||||
|
||||
// MACD (Moving Average Convergence Divergence)
|
||||
static MACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
|
||||
const emaFast = this.EMA(data, fastPeriod);
|
||||
const emaSlow = this.EMA(data, slowPeriod);
|
||||
|
||||
// MACD Line = Fast EMA - Slow EMA
|
||||
const macdLine = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (emaFast[i].value === null || emaSlow[i].value === null) {
|
||||
macdLine.push({ time: data[i].time, value: null, close: null });
|
||||
} else {
|
||||
const value = emaFast[i].value - emaSlow[i].value;
|
||||
macdLine.push({ time: data[i].time, value, close: value });
|
||||
}
|
||||
}
|
||||
|
||||
// Signal Line = EMA of MACD Line
|
||||
const validMacdData = macdLine.filter(d => d.value !== null);
|
||||
const signalEma = this.EMA(validMacdData, signalPeriod);
|
||||
|
||||
// Map signal back to full timeline
|
||||
const signalLine = [];
|
||||
let signalIndex = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (macdLine[i].value === null || signalIndex >= signalEma.length) {
|
||||
signalLine.push({ time: data[i].time, value: null });
|
||||
} else if (signalEma[signalIndex] && signalEma[signalIndex].time === data[i].time) {
|
||||
signalLine.push({ time: data[i].time, value: signalEma[signalIndex].value });
|
||||
signalIndex++;
|
||||
} else {
|
||||
signalLine.push({ time: data[i].time, value: null });
|
||||
}
|
||||
}
|
||||
|
||||
// Histogram = MACD Line - Signal Line
|
||||
const histogram = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (macdLine[i].value === null || signalLine[i].value === null) {
|
||||
histogram.push({ time: data[i].time, value: 0, color: '#363a45' });
|
||||
} else {
|
||||
const value = macdLine[i].value - signalLine[i].value;
|
||||
histogram.push({
|
||||
time: data[i].time,
|
||||
value,
|
||||
color: value >= 0 ? '#26a69a' : '#ef5350'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { macdLine, signalLine, histogram };
|
||||
}
|
||||
|
||||
// RSI (Relative Strength Index)
|
||||
static RSI(data, period = 14) {
|
||||
const result = [];
|
||||
const gains = [];
|
||||
const losses = [];
|
||||
|
||||
// Calculate price changes
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i === 0) {
|
||||
gains.push(0);
|
||||
losses.push(0);
|
||||
result.push({ time: data[i].time, value: null });
|
||||
continue;
|
||||
}
|
||||
|
||||
const change = data[i].close - data[i - 1].close;
|
||||
gains.push(change > 0 ? change : 0);
|
||||
losses.push(change < 0 ? Math.abs(change) : 0);
|
||||
|
||||
if (i < period) {
|
||||
result.push({ time: data[i].time, value: null });
|
||||
} else if (i === period) {
|
||||
// First RSI calculation uses simple average
|
||||
let avgGain = 0, avgLoss = 0;
|
||||
for (let j = 1; j <= period; j++) {
|
||||
avgGain += gains[j];
|
||||
avgLoss += losses[j];
|
||||
}
|
||||
avgGain /= period;
|
||||
avgLoss /= period;
|
||||
|
||||
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||
const rsi = 100 - (100 / (1 + rs));
|
||||
result.push({ time: data[i].time, value: rsi, avgGain, avgLoss });
|
||||
} else {
|
||||
// Subsequent RSI uses smoothed average
|
||||
const prevAvgGain = result[i - 1].avgGain;
|
||||
const prevAvgLoss = result[i - 1].avgLoss;
|
||||
const avgGain = (prevAvgGain * (period - 1) + gains[i]) / period;
|
||||
const avgLoss = (prevAvgLoss * (period - 1) + losses[i]) / period;
|
||||
|
||||
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||
const rsi = 100 - (100 / (1 + rs));
|
||||
result.push({ time: data[i].time, value: rsi, avgGain, avgLoss });
|
||||
}
|
||||
}
|
||||
|
||||
return result.map(d => ({ time: d.time, value: d.value }));
|
||||
}
|
||||
|
||||
// Stochastic Oscillator
|
||||
static Stochastic(data, kPeriod = 14, dPeriod = 3, smooth = 3) {
|
||||
const kValues = [];
|
||||
|
||||
// Calculate %K
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < kPeriod - 1) {
|
||||
kValues.push({ time: data[i].time, value: null, close: null });
|
||||
} else {
|
||||
let highest = -Infinity;
|
||||
let lowest = Infinity;
|
||||
|
||||
for (let j = 0; j < kPeriod; j++) {
|
||||
highest = Math.max(highest, data[i - j].high);
|
||||
lowest = Math.min(lowest, data[i - j].low);
|
||||
}
|
||||
|
||||
const k = highest === lowest ? 50 : ((data[i].close - lowest) / (highest - lowest)) * 100;
|
||||
kValues.push({ time: data[i].time, value: k, close: k });
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth %K (Fast %K -> Slow %K)
|
||||
const slowK = [];
|
||||
for (let i = 0; i < kValues.length; i++) {
|
||||
if (i < kPeriod - 1 + smooth - 1) {
|
||||
slowK.push({ time: kValues[i].time, value: null, close: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = 0; j < smooth; j++) {
|
||||
if (kValues[i - j].value !== null) {
|
||||
sum += kValues[i - j].value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
const value = count > 0 ? sum / count : null;
|
||||
slowK.push({ time: kValues[i].time, value, close: value });
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate %D (SMA of Slow %K)
|
||||
const dValues = [];
|
||||
for (let i = 0; i < slowK.length; i++) {
|
||||
if (i < kPeriod - 1 + smooth - 1 + dPeriod - 1) {
|
||||
dValues.push({ time: slowK[i].time, value: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = 0; j < dPeriod; j++) {
|
||||
if (slowK[i - j].value !== null) {
|
||||
sum += slowK[i - j].value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
dValues.push({ time: slowK[i].time, value: count > 0 ? sum / count : null });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
k: slowK.map(d => ({ time: d.time, value: d.value })),
|
||||
d: dValues
|
||||
};
|
||||
}
|
||||
|
||||
// Detect Golden Cross / Dead Cross
|
||||
static detectCrosses(shortMA, longMA) {
|
||||
const signals = [];
|
||||
|
||||
for (let i = 1; i < shortMA.length; i++) {
|
||||
if (shortMA[i].value === null || longMA[i].value === null ||
|
||||
shortMA[i - 1].value === null || longMA[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevShort = shortMA[i - 1].value;
|
||||
const prevLong = longMA[i - 1].value;
|
||||
const currShort = shortMA[i].value;
|
||||
const currLong = longMA[i].value;
|
||||
|
||||
// Golden Cross: Short MA crosses above Long MA
|
||||
if (prevShort <= prevLong && currShort > currLong) {
|
||||
signals.push({
|
||||
time: shortMA[i].time,
|
||||
type: 'golden',
|
||||
description: 'Golden Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// Dead Cross: Short MA crosses below Long MA
|
||||
if (prevShort >= prevLong && currShort < currLong) {
|
||||
signals.push({
|
||||
time: shortMA[i].time,
|
||||
type: 'dead',
|
||||
description: 'Dead Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect RSI signals
|
||||
static detectRSISignals(rsi, oversold = 30, overbought = 70) {
|
||||
const signals = [];
|
||||
|
||||
for (let i = 1; i < rsi.length; i++) {
|
||||
if (rsi[i].value === null || rsi[i - 1].value === null) continue;
|
||||
|
||||
// Oversold -> Recovery (Buy signal)
|
||||
if (rsi[i - 1].value < oversold && rsi[i].value >= oversold) {
|
||||
signals.push({
|
||||
time: rsi[i].time,
|
||||
type: 'buy',
|
||||
description: 'RSI Oversold Recovery'
|
||||
});
|
||||
}
|
||||
|
||||
// Overbought -> Decline (Sell signal)
|
||||
if (rsi[i - 1].value > overbought && rsi[i].value <= overbought) {
|
||||
signals.push({
|
||||
time: rsi[i].time,
|
||||
type: 'sell',
|
||||
description: 'RSI Overbought Decline'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect MACD signals
|
||||
static detectMACDSignals(macd) {
|
||||
const signals = [];
|
||||
const { macdLine, signalLine } = macd;
|
||||
|
||||
for (let i = 1; i < macdLine.length; i++) {
|
||||
if (macdLine[i].value === null || signalLine[i].value === null ||
|
||||
macdLine[i - 1].value === null || signalLine[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevMacd = macdLine[i - 1].value;
|
||||
const prevSignal = signalLine[i - 1].value;
|
||||
const currMacd = macdLine[i].value;
|
||||
const currSignal = signalLine[i].value;
|
||||
|
||||
// MACD crosses above Signal (Buy)
|
||||
if (prevMacd <= prevSignal && currMacd > currSignal) {
|
||||
signals.push({
|
||||
time: macdLine[i].time,
|
||||
type: 'buy',
|
||||
description: 'MACD Bullish Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// MACD crosses below Signal (Sell)
|
||||
if (prevMacd >= prevSignal && currMacd < currSignal) {
|
||||
signals.push({
|
||||
time: macdLine[i].time,
|
||||
type: 'sell',
|
||||
description: 'MACD Bearish Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect Stochastic signals
|
||||
static detectStochasticSignals(stochastic, oversold = 20, overbought = 80) {
|
||||
const signals = [];
|
||||
const { k, d } = stochastic;
|
||||
|
||||
for (let i = 1; i < k.length; i++) {
|
||||
if (k[i].value === null || d[i].value === null ||
|
||||
k[i - 1].value === null || d[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// %K crosses above %D in oversold zone (Buy)
|
||||
if (k[i - 1].value <= d[i - 1].value && k[i].value > d[i].value && k[i].value < oversold) {
|
||||
signals.push({
|
||||
time: k[i].time,
|
||||
type: 'buy',
|
||||
description: 'Stochastic Oversold Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// %K crosses below %D in overbought zone (Sell)
|
||||
if (k[i - 1].value >= d[i - 1].value && k[i].value < d[i].value && k[i].value > overbought) {
|
||||
signals.push({
|
||||
time: k[i].time,
|
||||
type: 'sell',
|
||||
description: 'Stochastic Overbought Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in app.js
|
||||
window.TechnicalIndicators = TechnicalIndicators;
|
||||
21
nginx.conf
Normal file
21
nginx.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
}
|
||||
643
package-lock.json
generated
Normal file
643
package-lock.json
generated
Normal file
@@ -0,0 +1,643 @@
|
||||
{
|
||||
"name": "bini-trading-view",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bini-trading-view",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-server": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
|
||||
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"corser": "^2.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"http-server": "bin/http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true,
|
||||
"license": "(WTFPL OR MIT)",
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
|
||||
"integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
"debug": "^4.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.12"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"qs": "^6.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
package.json
Normal file
15
package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "bini-trading-view",
|
||||
"version": "1.0.0",
|
||||
"description": "Bitcoin Real-time Trading View with Technical Indicators",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
"start": "npx http-server -p 3000 -o"
|
||||
},
|
||||
"keywords": ["bitcoin", "trading", "chart", "technical-analysis"],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
}
|
||||
523
styles.css
Normal file
523
styles.css
Normal file
@@ -0,0 +1,523 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background-color: #131722;
|
||||
color: #d1d4dc;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
background: #1e222d;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
color: #f0b90b;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 8px 15px;
|
||||
background: #2a2e39;
|
||||
border: 1px solid #363a45;
|
||||
border-radius: 5px;
|
||||
color: #d1d4dc;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select:hover {
|
||||
border-color: #f0b90b;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
#current-price {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #f0b90b;
|
||||
}
|
||||
|
||||
#price-change {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#price-change.up {
|
||||
color: #26a69a;
|
||||
}
|
||||
|
||||
#price-change.down {
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.charts-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Analysis Panel */
|
||||
.analysis-panel {
|
||||
width: 280px;
|
||||
background: #1e222d;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
height: fit-content;
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.analysis-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #363a45;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.analysis-header h3 {
|
||||
color: #f0b90b;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#analysis-time {
|
||||
font-size: 11px;
|
||||
color: #848e9c;
|
||||
}
|
||||
|
||||
.overall-signal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: #2a2e39;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.overall-signal .signal-label {
|
||||
font-size: 12px;
|
||||
color: #848e9c;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.overall-signal .signal-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.overall-signal .signal-value.buy {
|
||||
color: #26a69a;
|
||||
}
|
||||
|
||||
.overall-signal .signal-value.sell {
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.overall-signal .signal-value.neutral {
|
||||
color: #848e9c;
|
||||
}
|
||||
|
||||
/* Korean Analysis */
|
||||
.korean-analysis {
|
||||
background: #2a2e39;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border-left: 3px solid #f0b90b;
|
||||
}
|
||||
|
||||
.korean-analysis-header {
|
||||
font-size: 12px;
|
||||
color: #f0b90b;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.korean-analysis p {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #d1d4dc;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.indicator-analysis {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.analysis-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
background: #2a2e39;
|
||||
border-radius: 5px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.analysis-item .item-label {
|
||||
font-size: 12px;
|
||||
color: #848e9c;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.analysis-item .item-value {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.analysis-item .item-value.buy {
|
||||
background: rgba(38, 166, 154, 0.2);
|
||||
color: #26a69a;
|
||||
}
|
||||
|
||||
.analysis-item .item-value.sell {
|
||||
background: rgba(239, 83, 80, 0.2);
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.analysis-item .item-value.neutral {
|
||||
background: rgba(132, 142, 156, 0.2);
|
||||
color: #848e9c;
|
||||
}
|
||||
|
||||
.analysis-item .item-detail {
|
||||
font-size: 11px;
|
||||
color: #6c7284;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.analysis-summary {
|
||||
padding: 10px;
|
||||
background: #2a2e39;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.analysis-summary h4 {
|
||||
font-size: 12px;
|
||||
color: #848e9c;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.analysis-summary p {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #d1d4dc;
|
||||
}
|
||||
|
||||
.analysis-score {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.score-bar {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
background: linear-gradient(to right, #ef5350 0%, #ef5350 33%, #848e9c 33%, #848e9c 66%, #26a69a 66%, #26a69a 100%);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.score-fill {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.score-marker {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transition: left 0.3s ease;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.score-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
color: #6c7284;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
background: #1e222d;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 15px;
|
||||
background: #2a2e39;
|
||||
border-bottom: 1px solid #363a45;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-weight: 600;
|
||||
color: #d1d4dc;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ma-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ma-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.ma-toggle input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ma-toggle span {
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ma-toggle input:not(:checked) + span {
|
||||
opacity: 0.4;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.legend .ma5 span { color: #FFEB3B; }
|
||||
.legend .ma20 span { color: #1565C0; }
|
||||
.legend .ma60 span { color: #00E676; }
|
||||
.legend .ma120 span { color: #FF5252; }
|
||||
.legend .ma200 span { color: #E040FB; }
|
||||
.legend .bb span { color: #FF9800; }
|
||||
.legend .gc-dc span { color: #26a69a; }
|
||||
|
||||
.main-chart {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#main-chart {
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
#macd-chart,
|
||||
#rsi-chart,
|
||||
#stochastic-chart {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.indicator-chart {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#signals-container {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.signal-marker {
|
||||
position: absolute;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.signal-marker.golden {
|
||||
background: rgba(38, 166, 154, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signal-marker.dead {
|
||||
background: rgba(239, 83, 80, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signal-panel {
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
background: #1e222d;
|
||||
border-radius: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.signal-panel h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #f0b90b;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#signal-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.signal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: #2a2e39;
|
||||
border-radius: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.signal-item .time {
|
||||
color: #848e9c;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.signal-item .type {
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-weight: 600;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.signal-item .type.buy {
|
||||
background: rgba(38, 166, 154, 0.2);
|
||||
color: #26a69a;
|
||||
}
|
||||
|
||||
.signal-item .type.sell {
|
||||
background: rgba(239, 83, 80, 0.2);
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.signal-item .description {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e222d;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #363a45;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #4a4e59;
|
||||
}
|
||||
|
||||
/* Loading indicator */
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #848e9c;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: '';
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 3px solid #363a45;
|
||||
border-top-color: #f0b90b;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#main-chart {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
#macd-chart,
|
||||
#rsi-chart,
|
||||
#stochastic-chart {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user