<!DOCTYPE html>
<html>
<head>
<title>Input Field Example</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-financial"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'JetBrains Mono', monospace, 'Trebuchet MS', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif;
background-color: #121212;
color: #e0e0e0;
margin: 0;
padding: 20px;
line-height: 1.5;
}
.container {
width: 350px;
margin: 40px auto;
text-align: left; /* Override the default center alignment */
}
/* Chart container styles */
.chart-container {
width: 100%;
height: 200px;
margin-bottom: 20px;
border: 1px solid #333;
border-radius: 4px;
overflow: hidden;
background-color: #1a1a1a;
}
.chart-controls {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
}
.input-container {
display: flex;
margin-bottom: 10px;
position: relative; /* For positioning suggestions */
}
#ticker {
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 4px 0 0 4px;
flex-grow: 1;
border-right: none;
background-color: #1a1a1a;
color: #e0e0e0;
}
#connectBtn {
padding: 8px 16px;
font-size: 16px;
border: 1px solid #444;
border-radius: 0 4px 4px 0;
background-color: orange;
color: black;
cursor: pointer;
white-space: nowrap;
}
#connectBtn:hover {
background-color: darkorange;
}
.price-pnl-container {
display: flex;
gap: 16px;
margin-bottom: 10px;
}
#currentPriceDisplay, #pnlEl {
padding: 8px 12px;
font-size: 16px;
background-color: #1a1a1a;
border: 1px solid #333;
border-radius: 4px;
text-align: left;
display: inline-block;
box-sizing: border-box;
}
#currentPriceDisplay {
min-width: 80px; /* Lebar minimum */
}
#pnlEl {
min-width: 120px;
}
/* Styles for crypto suggestions */
#suggestionsCtn {
position: absolute;
top: 100%;
left: 0;
width: calc(100% - 94px);
max-height: 200px;
overflow-y: auto;
background-color: #1a1a1a;
border: 1px solid #444;
border-radius: 0 0 4px 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
z-index: 10;
display: none;
}
.suggestion-item {
padding: 8px 10px;
cursor: pointer;
color: #e0e0e0;
}
.suggestion-item:hover {
background-color: #2a2a2a;
}
/* Styles for position dropdown and input */
#tradingStatus {
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 4px;
background-color: #1a1a1a;
color: #e0e0e0;
width: 150px;
cursor: pointer;
}
#entryPrice {
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 4px;
margin-left: 10px;
width: 100px;
background-color: #1a1a1a;
color: #e0e0e0;
}
/* Styles for price alert section */
.price-alert-container {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #333;
}
.price-alert-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
}
.price-alert-row {
display: flex;
align-items: center;
margin-bottom: 10px;
}
#alertPrice {
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 4px;
width: 100px;
background-color: #1a1a1a;
color: #e0e0e0;
}
#alertCondition {
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 4px;
background-color: #1a1a1a;
color: #e0e0e0;
width: 120px;
margin-left: 10px;
cursor: pointer;
}
.sound-toggle-container {
display: flex;
align-items: center;
margin-left: 10px;
}
.sound-toggle-label {
margin-right: 8px;
font-size: 14px;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background-color: #e0e0e0;
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: #2a7fff;
}
input:checked + .toggle-slider:before {
transform: translateX(20px);
}
/* Technical analysis styles */
.technical-analysis-container {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #333;
}
.technical-analysis-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
}
#output {
width: 100%;
height: 200px;
padding: 8px;
font-size: 14px;
border: 1px solid #444;
border-radius: 4px;
background-color: #1a1a1a;
color: #e0e0e0;
resize: vertical;
white-space: pre-wrap;
font-family: monospace;
overflow-y: auto;
margin-bottom: 10px;
}
#copyBtn {
padding: 8px 16px;
font-size: 14px;
border: 1px solid #444;
border-radius: 4px;
background-color: orange;
color: black;
cursor: pointer;
margin-bottom: 10px;
}
#copyBtn:hover {
background-color: darkorange;
}
/* Alerts list and notifications */
.alert-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px;
margin-bottom: 5px;
background-color: #1a1a1a;
border: 1px solid #333;
border-radius: 4px;
}
.alert-item.triggered {
background-color: #1e3a2a;
}
.remove-alert {
background-color: #c62828;
color: white;
border: none;
border-radius: 3px;
padding: 2px 6px;
cursor: pointer;
}
#alertNotification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px;
background-color: #2e7d32;
color: white;
border-radius: 5px;
display: none;
z-index: 1000;
}
#alertsList {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
}
#tradingStatusDisplay {
margin-top: 10px;
padding: 5px 10px;
background-color: #1a1a1a;
border-radius: 4px;
display: none;
}
button {
background-color: orange;
color: black;
border: 1px solid #444;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
}
button:hover {
background-color: darkorange;
}
.position-container {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #333;
}
.position-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
}
</style>
</head>
<body>
<div class="container">
<!-- Asset Price Chart Section -->
<div class="chart-container">
<canvas id="priceChartEl"></canvas>
</div>
<div class="chart-controls">
<span>1 Minute Timeframe</span>
</div>
<div class="input-container">
<input id="ticker" type="text" placeholder="Enter crypto symbol...">
<button id="connectBtn" onclick="connect()">Connect</button>
<div id="suggestionsCtn"></div>
</div>
<div class="price-pnl-container">
<div id="currentPriceDisplay">$0.00</div>
<div id="pnlEl"></div>
</div>
<!-- Price Alert Section -->
<div class="price-alert-container">
<h3>Price Alert</h3>
<div class="price-alert-row">
<input id="alertPrice" type="number" placeholder="Alert price">
<select id="alertCondition">
<option value="above">Above</option>
<option value="below">Below</option>
</select>
<div class="sound-toggle-container">
<span class="sound-toggle-label">Sound</span>
<label class="toggle-switch">
<input id="soundToggleEl" type="checkbox" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
<button onclick="addAlert()">Add Alert</button>
<button onclick="clearAllAlerts()">Clear All</button>
<div id="alertsList">No notifications set</div>
</div>
<div class="position-container">
<h3>Posisi</h3>
<select id="tradingStatus">
<option value="no-position">No position</option>
<option value="open-long">Open long</option>
<option value="open-short">Open short</option>
<option value="cut-position">Cut position</option>
</select>
<input id="entryPrice" type="number" placeholder="Open price">
<div id="tradingStatusDisplay"></div>
</div>
<!-- Technical Analysis Section -->
<div class="technical-analysis-container">
<h3>Technical Analysis</h3>
<div id="output"></div>
<button id="copyBtn" onclick="copyOutput()">Copy Analysis</button>
</div>
</div>
<!-- Hidden audio element for alerts -->
<audio id="alertSound" src="https://assets.mixkit.co/sfx/preview/mixkit-alert-quick-chime-766.mp3"></audio>
<!-- Alert notification div -->
<div id="alertNotification"></div>
<!-- Include Chart.js library -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Include candlestick chart adapter -->
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-financial"></script>
<!-- Include date adapter for Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<script>
// Declare global chart variable
let chart = null;
// WebSockets and data arrays
let ws1m, ws15m, ws1h, ws4h, wsOrderBook;
let prices1m = [];
let prices15m = [];
let prices1h = [];
let prices4h = [];
let currentPrice = 0;
let previousPrice = 0;
let currentTicker = "";
let priceAlerts = [];
let orderBookText = '';
const MAX_DATA_POINTS_1M = 200; // Match the limit from the example
const MAX_DATA_POINTS_15M = 100;
const MAX_DATA_POINTS_1H = 150;
const MAX_DATA_POINTS_4H = 100;
// DOM elements cache
const elements = {
output: document.getElementById("output"),
currentPriceDisplay: document.getElementById("currentPriceDisplay"),
alertsList: document.getElementById("alertsList"),
alertPrice: document.getElementById("alertPrice"),
alertCondition: document.getElementById("alertCondition"),
alertNotification: document.getElementById("alertNotification"),
alertSound: document.getElementById("alertSound"),
ticker: document.getElementById("ticker"),
copyBtn: document.getElementById("copyBtn"),
tradingStatus: document.getElementById("tradingStatus"),
entryPrice: document.getElementById("entryPrice"),
tradingStatusDisplay: document.getElementById("tradingStatusDisplay"),
pnlEl: document.getElementById("pnlEl")
};
// Function to draw chart
function drawChart(data, symbol) {
const ctx = document.getElementById('priceChartEl').getContext('2d');
const ohlc = data.map(d => ({
x: d.time,
o: d.open,
h: d.high,
l: d.low,
c: d.close
}));
if (chart) chart.destroy();
chart = new Chart(ctx, {
type: 'candlestick',
data: { datasets: [{ label: symbol, data: ohlc }] },
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
time: { unit: 'minute', displayFormats: { minute: 'HH:mm' } },
title: { display: true, text: 'Time', color: '#e0e0e0' }
},
y: {
title: { display: true, text: 'Price (USDT)', color: '#e0e0e0' },
ticks: { color: '#e0e0e0' },
beginAtZero: false
}
},
plugins: {
legend: { labels: { color: '#e0e0e0' } }
},
animation: { duration: 0 } // Disable animation for real-time clarity
}
});
}
// Update fetchDataAndConnect to use the new format
async function fetchDataAndConnect(ticker) {
try {
const [klines1m, klines15m, klines1h, klines4h] = await Promise.all([
fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=1m&limit=${MAX_DATA_POINTS_1M}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
}),
fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=15m&limit=${MAX_DATA_POINTS_15M}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
}),
fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=1h&limit=${MAX_DATA_POINTS_1H}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
}),
fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=4h&limit=${MAX_DATA_POINTS_4H}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
})
]);
// Process kline data in the new format
prices1m = klines1m.map(d => ({
time: parseInt(d[0]), // Open time
open: parseFloat(d[1]),
high: parseFloat(d[2]),
low: parseFloat(d[3]),
close: parseFloat(d[4]),
volume: parseFloat(d[5])
}));
prices15m = klines15m.map(d => ({
time: parseInt(d[0]),
open: parseFloat(d[1]),
high: parseFloat(d[2]),
low: parseFloat(d[3]),
close: parseFloat(d[4]),
volume: parseFloat(d[5])
}));
prices1h = klines1h.map(d => ({
time: parseInt(d[0]),
open: parseFloat(d[1]),
high: parseFloat(d[2]),
low: parseFloat(d[3]),
close: parseFloat(d[4]),
volume: parseFloat(d[5])
}));
prices4h = klines4h.map(d => ({
time: parseInt(d[0]),
open: parseFloat(d[1]),
high: parseFloat(d[2]),
low: parseFloat(d[3]),
close: parseFloat(d[4]),
volume: parseFloat(d[5])
}));
if (prices1m.length > 0) {
currentPrice = prices1m[prices1m.length - 1].close;
previousPrice = currentPrice;
updateCurrentPriceDisplay(ticker, currentPrice);
updatePNL();
}
// Draw initial chart with 1-minute data
drawChart(prices1m, ticker);
// Close existing WebSocket connections
if (ws1m) ws1m.close();
if (ws15m) ws15m.close();
if (ws1h) ws1h.close();
if (ws4h) ws4h.close();
if (wsOrderBook) wsOrderBook.close();
// Setup WebSocket for 1-minute timeframe
ws1m = setupWebSocket(ticker, '1m', (kline) => {
console.log('1m WebSocket data:', kline); // Debug log
currentPrice = parseFloat(kline.c);
updateCurrentPriceDisplay(ticker, currentPrice);
updatePNL();
const newCandle = {
time: parseInt(kline.T), // Close time
open: parseFloat(kline.o),
high: parseFloat(kline.h),
low: parseFloat(kline.l),
close: parseFloat(kline.c),
volume: parseFloat(kline.v)
};
if (kline.x) { // If candle is closed
prices1m.push(newCandle);
if (prices1m.length > MAX_DATA_POINTS_1M) prices1m.shift();
} else if (prices1m.length > 0) {
prices1m[prices1m.length - 1] = newCandle; // Update current candle
} else {
prices1m.push(newCandle);
}
updateAndDisplayData(ticker);
drawChart(prices1m, ticker); // Update chart
checkAlerts();
});
// Setup WebSocket for 15m timeframe (for indicators)
ws15m = setupWebSocket(ticker, '15m', (kline) => {
const newCandle = {
time: parseInt(kline.T),
open: parseFloat(kline.o),
high: parseFloat(kline.h),
low: parseFloat(kline.l),
close: parseFloat(kline.c),
volume: parseFloat(kline.v)
};
if (kline.x) {
prices15m.push(newCandle);
if (prices15m.length > MAX_DATA_POINTS_15M) prices15m.shift();
} else if (prices15m.length > 0) {
prices15m[prices15m.length - 1] = newCandle;
} else {
prices15m.push(newCandle);
}
updateAndDisplayData(ticker);
});
// Setup WebSocket for 1h timeframe
ws1h = setupWebSocket(ticker, '1h', (kline) => {
const newCandle = {
time: parseInt(kline.T),
open: parseFloat(kline.o),
high: parseFloat(kline.h),
low: parseFloat(kline.l),
close: parseFloat(kline.c),
volume: parseFloat(kline.v)
};
if (kline.x) {
prices1h.push(newCandle);
if (prices1h.length > MAX_DATA_POINTS_1H) prices1h.shift();
} else if (prices1h.length > 0) {
prices1h[prices1h.length - 1] = newCandle;
} else {
prices1h.push(newCandle);
}
updateAndDisplayData(ticker);
});
// Setup WebSocket for 4h timeframe
ws4h = setupWebSocket(ticker, '4h', (kline) => {
const newCandle = {
time: parseInt(kline.T),
open: parseFloat(kline.o),
high: parseFloat(kline.h),
low: parseFloat(kline.l),
close: parseFloat(kline.c),
volume: parseFloat(kline.v)
};
if (kline.x) {
prices4h.push(newCandle);
if (prices4h.length > MAX_DATA_POINTS_4H) prices4h.shift();
} else if (prices4h.length > 0) {
prices4h[prices4h.length - 1] = newCandle;
} else {
prices4h.push(newCandle);
}
updateAndDisplayData(ticker);
});
// Setup WebSocket for order book data
const baseTicker = ticker.endsWith('USDT') ? ticker.slice(0, -4) : ticker;
const maxRows = 5;
wsOrderBook = new WebSocket(`wss://stream.binance.com:9443/ws/${ticker.toLowerCase()}@depth`);
wsOrderBook.onmessage = (event) => {
const data = JSON.parse(event.data);
const bids = data.b ? data.b.slice(0, maxRows) : [];
const asks = data.a ? data.a.slice(0, maxRows) : [];
let liquidityText = '\nBids (Buy Orders):\n';
bids.forEach(([price, volume]) => {
liquidityText += ` - ${formatMarketPrice(parseFloat(price))} (${parseFloat(volume)} ${baseTicker})\n`;
});
liquidityText += '\nAsks (Sell Orders):\n';
asks.forEach(([price, volume]) => {
liquidityText += ` - ${formatMarketPrice(parseFloat(price))} (${parseFloat(volume)} ${baseTicker})\n`;
});
orderBookText = liquidityText;
updateAndDisplayData(ticker);
};
currentTicker = ticker;
updateTradingStatusDisplay();
updatePNL();
} catch (error) {
console.error("Failed to fetch historical data or connect:", error);
elements.output.innerText = `Error: ${error.message}. Please check ticker symbol or network connection.`;
elements.currentPriceDisplay.style.display = "none";
}
}
// Function to calculate PNL (Profit and Loss)
function calculatePNL(currentPrice, entryPrice, positionType) {
if (!entryPrice || isNaN(entryPrice) || entryPrice <= 0 || !currentPrice) {
return null; // No valid entry price or current price
}
entryPrice = parseFloat(entryPrice);
currentPrice = parseFloat(currentPrice);
if (positionType === "open-long") {
return ((currentPrice - entryPrice) / entryPrice) * 100;
} else if (positionType === "open-short") {
return ((entryPrice - currentPrice) / entryPrice) * 100;
}
return null; // No position or invalid position type
}
// Function to update PNL display
function updatePNL() {
const positionType = elements.tradingStatus.value;
const entryPrice = elements.entryPrice.value;
if ((positionType === "open-long" || positionType === "open-short") && entryPrice) {
const pnl = calculatePNL(currentPrice, entryPrice, positionType);
if (pnl !== null) {
const pnlText = pnl.toFixed(2) + "%";
const pnlColor = pnl >= 0 ? "#4caf50" : "#ff5252";
elements.pnlEl.innerHTML = `<span style="color:${pnlColor};">PNL: ${pnlText}</span>`;
elements.pnlEl.style.display = "block";
} else {
elements.pnlEl.innerHTML = "";
}
} else {
elements.pnlEl.innerHTML = "";
}
}
// Function to format price with appropriate decimal places
function formatPrice(price) {
price = Number(price);
if (price >= 3) {
return Math.floor(price * 100) / 100;
} else if (price >= 1) {
return Math.floor(price * 10000) / 10000;
} else if (price >= 0.0001) {
const priceStr = price.toString();
const decimalIndex = priceStr.indexOf('.');
if (decimalIndex === -1) {
return priceStr + '.0000000000';
}
const desiredLength = decimalIndex + 1 + 10;
return priceStr.substring(0, Math.min(priceStr.length, desiredLength));
} else {
const priceStr = price.toString();
const match = priceStr.match(/^0\.0*(\d+)/);
if (match) {
const zeroCount = match[0].length - 2 - match[1].length;
return `0.0{${zeroCount}}${match[1]}`;
} else {
return "0.00000000";
}
}
}
// LocalStorage management functions
function saveAlertsToLocalStorage() {
localStorage.priceAlerts = JSON.stringify(priceAlerts);
}
function loadAlertsFromLocalStorage() {
if (localStorage.priceAlerts) {
priceAlerts = JSON.parse(localStorage.priceAlerts);
renderAlerts();
}
}
// Market price formatter
function formatMarketPrice(price) {
price = Number(price);
if (price >= 3) {
return price.toFixed(2);
} else if (price >= 1) {
return price.toFixed(4);
} else if (price >= 0.0001) {
return price.toFixed(10);
} else {
const priceStr = price.toFixed(15).replace(/0+$/, '');
const match = priceStr.match(/^0\.0*(\d+)/);
if (match) {
const zeroCount = match[0].length - 2 - match[1].length;
const digits = match[1].substring(0, 5);
return `0.0{${zeroCount}}${digits}`;
} else {
return "0.00000000";
}
}
}
// LocalStorage management functions
function saveAlertPriceToLocalStorage() {
localStorage.alertPrice = elements.alertPrice.value;
}
function loadAlertPriceFromLocalStorage() {
if (localStorage.alertPrice) {
elements.alertPrice.value = localStorage.alertPrice;
}
}
function saveAlertConditionToLocalStorage() {
localStorage.alertCondition = elements.alertCondition.value;
}
function loadAlertConditionFromLocalStorage() {
if (localStorage.alertCondition) {
elements.alertCondition.value = localStorage.alertCondition;
}
}
function saveTradingStatusToLocalStorage() {
localStorage.tradingStatus = elements.tradingStatus.value;
}
function loadTradingStatusFromLocalStorage() {
if (localStorage.tradingStatus) {
elements.tradingStatus.value = localStorage.tradingStatus;
}
}
function saveEntryPriceToLocalStorage() {
localStorage.entryPrice = elements.entryPrice.value;
}
function loadEntryPriceFromLocalStorage() {
if (localStorage.entryPrice) {
elements.entryPrice.value = localStorage.entryPrice;
}
}
// Update trading status display
function updateTradingStatusDisplay() {
const status = elements.tradingStatus.value;
const price = elements.entryPrice.value;
if (status && price) {
elements.tradingStatusDisplay.textContent = `${status} ${price}`;
elements.tradingStatusDisplay.style.display = "block";
} else if (status) {
elements.tradingStatusDisplay.textContent = status;
elements.tradingStatusDisplay.style.display = "block";
} else {
elements.tradingStatusDisplay.style.display = "none";
}
}
function formatTickerSymbol(input) {
const ticker = input.trim().toUpperCase();
return ticker.includes("USDT") ? ticker : ticker + "USDT";
}
// Helper function to setup WebSockets
function setupWebSocket(ticker, interval, onKline) {
const ws = new WebSocket(`wss://stream.binance.com:9443/ws/${ticker.toLowerCase()}@kline_${interval}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.k) {
onKline(data.k);
}
};
ws.onerror = (error) => {
console.error(`WebSocket ${interval} Error:`, error);
if (interval === '15m') {
elements.output.innerText = `WebSocket ${interval} Error. Please check console.`;
}
};
ws.onclose = () => {
console.log(`WebSocket ${interval} closed.`);
};
return ws;
}
// Update current price display with color change
function updateCurrentPriceDisplay(ticker, price) {
let priceColor = "#e0e0e0";
if (price < previousPrice) {
priceColor = "#ff5252";
} else if (price > previousPrice) {
priceColor = "#4caf50";
}
elements.currentPriceDisplay.innerHTML = `<span style="color:${priceColor};">${formatPrice(price)} USDT</span>`;
elements.currentPriceDisplay.style.display = "block";
previousPrice = price;
}
function connect() {
const userInput = elements.ticker.value;
if (!userInput) {
alert("Enter valid ticker!");
return;
}
const ticker = formatTickerSymbol(userInput);
fetchDataAndConnect(ticker);
}
// Update and display data
function updateAndDisplayData(ticker) {
const indicators15m = calculateIndicators(prices15m);
const indicators1h = calculateIndicators(prices1h);
const indicators4h = calculateIndicators(prices4h);
displayData(
ticker,
indicators15m,
prices15m.length > 0 ? prices15m[prices15m.length - 1].close : 0,
prices15m.length > 0 ? prices15m[prices15m.length - 1].volume : 0,
indicators1h,
prices1h.length > 0 ? prices1h[prices1h.length - 1].close : 0,
prices1h.length > 0 ? prices1h[prices1h.length - 1].volume : 0,
indicators4h,
prices4h.length > 0 ? prices4h[prices4h.length - 1].close : 0,
prices4h.length > 0 ? prices4h[prices4h.length - 1].volume : 0
);
}
// Calculate EMA
function calculateEMA(values, period) {
if (values.length < period) return [];
const k = 2 / (period + 1);
let emaArray = [];
// Calculate initial SMA
let sum = 0;
for (let i = 0; i < period; i++) {
sum += values[i];
}
emaArray.push(sum / period);
// Calculate subsequent EMAs
for (let i = period; i < values.length; i++) {
emaArray.push(values[i] * k + emaArray[emaArray.length - 1] * (1 - k));
}
return emaArray;
}
// Calculate RSI with EMA smoothing
function calculateRSI(data, period) {
if (data.length < period + 1) return null;
let gains = [];
let losses = [];
// Calculate initial gains/losses
for (let i = 1; i < data.length; i++) {
const diff = data[i].close - data[i - 1].close;
gains.push(diff > 0 ? diff : 0);
losses.push(diff < 0 ? Math.abs(diff) : 0);
}
// Get data for RSI period
const periodGains = gains.slice(gains.length - period);
const periodLosses = losses.slice(losses.length - period);
// Calculate first average gain/loss
let avgGain = periodGains.reduce((a, b) => a + b, 0) / period;
let avgLoss = periodLosses.reduce((a, b) => a + b, 0) / period;
// Apply EMA smoothing
for (let i = data.length - period; i < data.length - 1; i++) {
const currentGain = gains[i];
const currentLoss = losses[i];
avgGain = (avgGain * (period - 1) + currentGain) / period;
avgLoss = (avgLoss * (period - 1) + currentLoss) / period;
}
if (avgLoss === 0) return 100;
const rs = avgGain / avgLoss;
return 100 - (100 / (1 + rs));
}
// Calculate Stochastic Oscillator
function calculateStochastic(data, kPeriod = 14, dPeriod = 3) {
if (data.length < kPeriod) return { stochasticK: null, stochasticD: null };
const highs = data.map(d => d.high);
const lows = data.map(d => d.low);
const closes = data.map(d => d.close);
let kValues = [];
for (let i = kPeriod - 1; i < data.length; i++) {
const periodHighs = highs.slice(i - kPeriod + 1, i + 1);
const periodLows = lows.slice(i - kPeriod + 1, i + 1);
const currentClose = closes[i];
const highestHigh = Math.max(...periodHighs);
const lowestLow = Math.min(...periodLows);
// Calculate %K
const k = (highestHigh - lowestLow) === 0
? 50 // Avoid division by zero
: ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100;
kValues.push(k);
}
// Calculate %D (3-period SMA of %K)
let dValues = [];
if (kValues.length >= dPeriod) {
for (let i = dPeriod - 1; i < kValues.length; i++) {
const periodK = kValues.slice(i - dPeriod + 1, i + 1);
const d = periodK.reduce((a, b) => a + b, 0) / dPeriod;
dValues.push(d);
}
}
return {
stochasticK: kValues.length > 0 ? kValues[kValues.length - 1] : null,
stochasticD: dValues.length > 0 ? dValues[dValues.length - 1] : null
};
}
function calculateIndicators(data) {
const length = data.length;
if (length < 26) return {};
const closingPrices = data.map(d => d.close);
// RSI calculations
const rsi14 = calculateRSI(data, 14);
const rsi6 = calculateRSI(data, 6);
// EMA calculations
const ema6Array = calculateEMA(closingPrices, 6);
const ema12Array = calculateEMA(closingPrices, 12);
const ema20Array = calculateEMA(closingPrices, 20);
const ema26Array = calculateEMA(closingPrices, 26);
const ema6 = ema6Array.length > 0 ? ema6Array[ema6Array.length - 1] : null;
const ema12 = ema12Array.length > 0 ? ema12Array[ema12Array.length - 1] : null;
const ema20 = ema20Array.length > 0 ? ema20Array[ema20Array.length - 1] : null;
const ema26 = ema26Array.length > 0 ? ema26Array[ema26Array.length - 1] : null;
const macd = (ema12 && ema26) ? ema12 - ema26 : null;
// MACD Signal Line
let macdLineValues = [];
const minLength = Math.min(ema12Array.length, ema26Array.length);
for(let i = 0; i < minLength; i++) {
macdLineValues.push(ema12Array[i] - ema26Array[i]);
}
const signalArray = calculateEMA(macdLineValues, 9);
const signal = signalArray.length > 0 ? signalArray[signalArray.length - 1] : null;
// Bollinger Bands
const bbPeriod = 20;
if (closingPrices.length < bbPeriod) return {};
const lastBBData = closingPrices.slice(-bbPeriod);
const sma20 = lastBBData.reduce((a, b) => a + b, 0) / bbPeriod;
const stdDev = Math.sqrt(lastBBData.reduce((a, b) => a + Math.pow(b - sma20, 2), 0) / bbPeriod);
const upperBand = sma20 + 2 * stdDev;
const lowerBand = sma20 - 2 * stdDev;
// ADX calculation
const adxPeriod = 14;
if (data.length < adxPeriod + 1) return {
rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx: null,
stochasticK: null, stochasticD: null
};
let trs = [];
let plusDMs = [];
let minusDMs = [];
for (let i = 1; i < length; i++) {
const curr = data[i];
const prev = data[i - 1];
// True Range
const tr = Math.max(
curr.high - curr.low,
Math.abs(curr.high - prev.close),
Math.abs(curr.low - prev.close)
);
trs.push(tr);
// Directional Movement
const upMove = curr.high - prev.high;
const downMove = prev.low - curr.low;
plusDMs.push(upMove > downMove && upMove > 0 ? upMove : 0);
minusDMs.push(downMove > upMove && downMove > 0 ? downMove : 0);
}
// EMA smoothing for indicators
const smoothedTRs = calculateEMA(trs, adxPeriod);
const smoothedPlusDMs = calculateEMA(plusDMs, adxPeriod);
const smoothedMinusDMs = calculateEMA(minusDMs, adxPeriod);
if (smoothedTRs.length === 0 || smoothedPlusDMs.length === 0 || smoothedMinusDMs.length === 0) {
return {
rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx: null,
stochasticK: null, stochasticD: null
};
}
const currentSmoothedTR = smoothedTRs[smoothedTRs.length - 1];
const currentSmoothedPlusDM = smoothedPlusDMs[smoothedPlusDMs.length - 1];
const currentSmoothedMinusDM = smoothedMinusDMs[smoothedMinusDMs.length - 1];
const plusDI = (currentSmoothedTR === 0) ? 0 : (currentSmoothedPlusDM / currentSmoothedTR) * 100;
const minusDI = (currentSmoothedTR === 0) ? 0 : (currentSmoothedMinusDM / currentSmoothedTR) * 100;
// Calculate DX values
const dxValues = [];
for (let i = 0; i < smoothedTRs.length; i++) {
const tempPlusDI = (smoothedTRs[i] === 0) ? 0 : (smoothedPlusDMs[i] / smoothedTRs[i]) * 100;
const tempMinusDI = (smoothedTRs[i] === 0) ? 0 : (smoothedMinusDMs[i] / smoothedTRs[i]) * 100;
const dx = (tempPlusDI + tempMinusDI === 0) ? 0 :
Math.abs(tempPlusDI - tempMinusDI) / (tempPlusDI + tempMinusDI) * 100;
dxValues.push(dx);
}
const adxArray = calculateEMA(dxValues, adxPeriod);
const adx = adxArray.length > 0 ? adxArray[adxArray.length - 1] : null;
// Calculate Stochastic Oscillator
const { stochasticK, stochasticD } = calculateStochastic(data);
return {
rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx,
stochasticK, stochasticD
};
}
function displayData(ticker, ind15m, price15m, volume15m, ind1h, price1h, volume1h, ind4h, price4h, volume4h) {
// Extract base ticker (remove USDT if it's at the end)
const baseTicker = ticker.endsWith('USDT') ? ticker.slice(0, -4) : ticker;
// Check for RSI6 error
const hasRsi6Error =
(ind15m.rsi6 === 0) ||
(ind1h.rsi6 === 0) ||
(ind4h.rsi6 === 0);
const errorMessage = hasRsi6Error ? "FYI RSI6 error\n\n" : "";
// Get trading status and entry price
const tradingStatus = elements.tradingStatus.value;
const entryPrice = elements.entryPrice.value;
// Build trading status text
let tradingStatusText = "";
if (tradingStatus && entryPrice) {
tradingStatusText = `Trading Status: leverage 30x, ${tradingStatus} ${entryPrice}\n\n`;
} else if (tradingStatus) {
tradingStatusText = `Trading Status: ${tradingStatus} buat 1 opsi, no entry agresif, no spekulatif dan no scalping.\nBuat jawaban singkat saja max 500 karakter.\n\n`;
}
const text = `${errorMessage}${tradingStatusText}Ticker: ${ticker}${orderBookText}
---
15-Menit Time Frame:
Harga saat ini: ${formatPrice(price15m)}
Volume: ${formatPrice(volume15m)} ${baseTicker}
RSI (14): ${ind15m.rsi14 !== null ? ind15m.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind15m.rsi6 !== null ? ind15m.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind15m.ema6 ? formatMarketPrice(ind15m.ema6) : 'N/A'}
EMA (12): ${ind15m.ema12 ? formatMarketPrice(ind15m.ema12) : 'N/A'}
EMA (20): ${ind15m.ema20 ? formatMarketPrice(ind15m.ema20) : 'N/A'}
MACD: ${ind15m.macd ? ind15m.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind15m.signal ? ind15m.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
- Upper: ${ind15m.upperBand ? formatMarketPrice(ind15m.upperBand) : 'N/A'}
- SMA (20): ${ind15m.sma20 ? formatMarketPrice(ind15m.sma20) : 'N/A'}
- Lower: ${ind15m.lowerBand ? formatMarketPrice(ind15m.lowerBand) : 'N/A'}
ADX (14): ${ind15m.adx !== null ? ind15m.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
- %K: ${ind15m.stochasticK !== null ? ind15m.stochasticK.toFixed(2) : 'N/A'}
- %D: ${ind15m.stochasticD !== null ? ind15m.stochasticD.toFixed(2) : 'N/A'}
---
1-Jam Time Frame:
Harga saat ini: ${formatPrice(price1h)}
Volume: ${formatPrice(volume1h)} ${baseTicker}
RSI (14): ${ind1h.rsi14 !== null ? ind1h.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind1h.rsi6 !== null ? ind1h.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind1h.ema6 ? formatMarketPrice(ind1h.ema6) : 'N/A'}
EMA (12): ${ind1h.ema12 ? formatMarketPrice(ind1h.ema12) : 'N/A'}
EMA (20): ${ind1h.ema20 ? formatMarketPrice(ind1h.ema20) : 'N/A'}
MACD: ${ind1h.macd ? ind1h.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind1h.signal ? ind1h.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
- Upper: ${ind1h.upperBand ? formatMarketPrice(ind1h.upperBand) : 'N/A'}
- SMA (20): ${ind1h.sma20 ? formatMarketPrice(ind1h.sma20) : 'N/A'}
- Lower: ${ind1h.lowerBand ? formatMarketPrice(ind1h.lowerBand) : 'N/A'}
ADX (14): ${ind1h.adx !== null ? ind1h.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
- %K: ${ind1h.stochasticK !== null ? ind1h.stochasticK.toFixed(2) : 'N/A'}
- %D: ${ind1h.stochasticD !== null ? ind1h.stochasticD.toFixed(2) : 'N/A'}
---
4-Jam Time Frame:
Harga saat ini: ${formatPrice(price4h)}
Volume: ${formatPrice(volume4h)} ${baseTicker}
RSI (14): ${ind4h.rsi14 !== null ? ind4h.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind4h.rsi6 !== null ? ind4h.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind4h.ema6 ? formatMarketPrice(ind4h.ema6) : 'N/A'}
EMA (12): ${ind4h.ema12 ? formatMarketPrice(ind4h.ema12) : 'N/A'}
EMA (20): ${ind4h.ema20 ? formatMarketPrice(ind4h.ema20) : 'N/A'}
MACD: ${ind4h.macd ? ind4h.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind4h.signal ? ind4h.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
- Upper: ${ind4h.upperBand ? formatMarketPrice(ind4h.upperBand) : 'N/A'}
- SMA (20): ${ind4h.sma20 ? formatMarketPrice(ind4h.sma20) : 'N/A'}
- Lower: ${ind4h.lowerBand ? formatMarketPrice(ind4h.lowerBand) : 'N/A'}
ADX (14): ${ind4h.adx !== null ? ind4h.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
- %K: ${ind4h.stochasticK !== null ? ind4h.stochasticK.toFixed(2) : 'N/A'}
- %D: ${ind4h.stochasticD !== null ? ind4h.stochasticD.toFixed(2) : 'N/A'}
`;
elements.output.textContent = text;
}
function copyOutput() {
const outputText = elements.output.textContent;
navigator.clipboard.writeText(outputText)
.then(() => {
elements.copyBtn.textContent = "Copied!";
setTimeout(() => {
elements.copyBtn.textContent = "Copy Analysis";
}, 2000);
})
.catch(err => {
console.error('Failed to copy:', err);
alert('Failed to copy text to clipboard');
});
}
// Price alert functions
function addAlert() {
const price = parseFloat(elements.alertPrice.value);
const condition = elements.alertCondition.value;
if (isNaN(price) || price <= 0) {
alert("Please enter a valid price");
return;
}
// Generate unique ID
const alertId = Date.now().toString();
// Create alert object
const alertObj = {
id: alertId,
price: price,
condition: condition,
ticker: currentTicker,
triggered: false
};
// Add to alerts array
priceAlerts.push(alertObj);
// Save to localStorage
saveAlertsToLocalStorage();
// Update alerts display
renderAlerts();
}
function removeAlert(alertId) {
priceAlerts = priceAlerts.filter(alert => alert.id !== alertId);
saveAlertsToLocalStorage();
renderAlerts();
}
function clearAllAlerts() {
priceAlerts = [];
elements.alertPrice.value = '';
localStorage.removeItem('alertPrice');
saveAlertsToLocalStorage();
renderAlerts();
}
function renderAlerts() {
if (priceAlerts.length === 0) {
elements.alertsList.innerHTML = "No notifications set";
return;
}
let html = "";
priceAlerts.forEach(alert => {
const condition = alert.condition === "above" ? ">" : "<";
const className = alert.triggered ? "alert-item triggered" : "alert-item";
html += `
<div class="${className}" data-alert-id="${alert.id}">
<span>${alert.ticker}: Price ${condition} ${formatPrice(alert.price)}</span>
<button class="remove-alert" onclick="removeAlert('${alert.id}')">X</button>
</div>
`;
});
elements.alertsList.innerHTML = html;
}
function checkAlerts() {
let alertTriggered = false;
let triggeredMessage = "";
priceAlerts.forEach(alert => {
let isTriggered = false;
if (alert.condition === "above" && currentPrice > alert.price && !alert.triggered) {
isTriggered = true;
triggeredMessage = `${currentTicker} price is now above ${formatPrice(alert.price)}`;
} else if (alert.condition === "below" && currentPrice < alert.price && !alert.triggered) {
isTriggered = true;
triggeredMessage = `${currentTicker} price is now below ${formatPrice(alert.price)}`;
}
if (isTriggered) {
alert.triggered = true;
alertTriggered = true;
// Save updated alert status
saveAlertsToLocalStorage();
// Update display
renderAlerts();
// Show notification
showNotification(triggeredMessage);
}
});
return alertTriggered;
}
function showNotification(message) {
elements.alertNotification.innerText = message;
elements.alertNotification.style.display = "block";
// Try to play sound
try {
elements.alertSound.play().catch(e => console.log("Audio playback error:", e));
} catch (e) {
console.log("Sound notification not supported or audio file error", e);
}
// Hide after 5 seconds
setTimeout(() => {
elements.alertNotification.style.display = "none";
}, 5000);
}
// Initialize localStorage when document is ready
window.addEventListener('DOMContentLoaded', () => {
loadAlertsFromLocalStorage();
loadAlertPriceFromLocalStorage();
loadAlertConditionFromLocalStorage();
loadTradingStatusFromLocalStorage();
loadEntryPriceFromLocalStorage();
// Add event listeners
elements.alertPrice.addEventListener('input', saveAlertPriceToLocalStorage);
elements.alertCondition.addEventListener('change', saveAlertConditionToLocalStorage);
elements.tradingStatus.addEventListener('change', () => {
saveTradingStatusToLocalStorage();
updateTradingStatusDisplay();
updatePNL(); // Update PNL when trading status changes
if (currentTicker) updateAndDisplayData(currentTicker);
});
elements.entryPrice.addEventListener('input', () => {
saveEntryPriceToLocalStorage();
updateTradingStatusDisplay();
updatePNL(); // Update PNL when entry price changes
if (currentTicker) updateAndDisplayData(currentTicker);
});
});
</script>
</body>
</html>
No comments:
Post a Comment