Download script.js from MavinGC/candlesniffer-pattern-detective: direct link, hf CLI and curl.
- Browser
- Download file 13.4 kB
-
https://huggingface.co/spaces/MavinGC/candlesniffer-pattern-detective/resolve/main/script.js
- Command line
-
hf download hf://spaces/MavinGC/candlesniffer-pattern-detective/script.js
-
curl -L -o script.js https://huggingface.co/spaces/MavinGC/candlesniffer-pattern-detective/resolve/main/script.js
13.4 kB
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize canvas for pattern drawing | |
| const canvas = document.getElementById('drawingCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let isDrawing = false; | |
| let lastX = 0; | |
| let lastY = 0; | |
| let drawingHistory = []; | |
| let currentStep = -1; | |
| // Set canvas dimensions | |
| function resizeCanvas() { | |
| const rect = canvas.getBoundingClientRect(); | |
| canvas.width = rect.width; | |
| canvas.height = rect.height; | |
| redrawCanvas(); | |
| } | |
| // Redraw canvas from history | |
| function redrawCanvas() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.strokeStyle = '#4f46e5'; | |
| ctx.lineWidth = 2; | |
| ctx.lineJoin = 'round'; | |
| ctx.lineCap = 'round'; | |
| for(let i = 0; i <= currentStep; i++) { | |
| const path = drawingHistory[i]; | |
| if(path.length > 0) { | |
| ctx.beginPath(); | |
| ctx.moveTo(path[0].x, path[0].y); | |
| for(let j = 1; j < path.length; j++) { | |
| ctx.lineTo(path[j].x, path[j].y); | |
| } | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| // Canvas event listeners | |
| canvas.addEventListener('mousedown', startDrawing); | |
| canvas.addEventListener('touchstart', handleTouchStart); | |
| canvas.addEventListener('mousemove', draw); | |
| canvas.addEventListener('touchmove', handleTouchMove); | |
| canvas.addEventListener('mouseup', stopDrawing); | |
| canvas.addEventListener('mouseout', stopDrawing); | |
| canvas.addEventListener('touchend', stopDrawing); | |
| function startDrawing(e) { | |
| isDrawing = true; | |
| const pos = getPosition(e); | |
| lastX = pos.x; | |
| lastY = pos.y; | |
| // Start a new path in history | |
| currentStep++; | |
| drawingHistory = drawingHistory.slice(0, currentStep); | |
| drawingHistory.push([]); | |
| drawingHistory[currentStep].push({x: lastX, y: lastY}); | |
| } | |
| function draw(e) { | |
| if(!isDrawing) return; | |
| const pos = getPosition(e); | |
| ctx.beginPath(); | |
| ctx.moveTo(lastX, lastY); | |
| ctx.lineTo(pos.x, pos.y); | |
| ctx.stroke(); | |
| lastX = pos.x; | |
| lastY = pos.y; | |
| drawingHistory[currentStep].push({x: lastX, y: lastY}); | |
| } | |
| function stopDrawing() { | |
| isDrawing = false; | |
| } | |
| function handleTouchStart(e) { | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const mouseEvent = new MouseEvent('mousedown', { | |
| clientX: touch.clientX, | |
| clientY: touch.clientY | |
| }); | |
| canvas.dispatchEvent(mouseEvent); | |
| } | |
| function handleTouchMove(e) { | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const mouseEvent = new MouseEvent('mousemove', { | |
| clientX: touch.clientX, | |
| clientY: touch.clientY | |
| }); | |
| canvas.dispatchEvent(mouseEvent); | |
| } | |
| function getPosition(e) { | |
| const rect = canvas.getBoundingClientRect(); | |
| return { | |
| x: (e.clientX || e.touches[0].clientX) - rect.left, | |
| y: (e.clientY || e.touches[0].clientY) - rect.top | |
| }; | |
| } | |
| // Clear and Undo buttons | |
| document.getElementById('clearCanvas').addEventListener('click', function() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| drawingHistory = []; | |
| currentStep = -1; | |
| }); | |
| document.getElementById('undoCanvas').addEventListener('click', function() { | |
| if(currentStep >= 0) { | |
| currentStep--; | |
| redrawCanvas(); | |
| } | |
| }); | |
| // File upload handling | |
| document.getElementById('patternUpload').addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if(file) { | |
| document.getElementById('fileName').textContent = file.name; | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| const img = new Image(); | |
| img.onload = function() { | |
| // Clear canvas and draw the image | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.drawImage(img, 0, 0, canvas.width, canvas.height); | |
| // Reset drawing history | |
| drawingHistory = []; | |
| currentStep = -1; | |
| }; | |
| img.src = event.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // Platform selection | |
| const platformButtons = document.querySelectorAll('.platform-btn'); | |
| platformButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| platformButtons.forEach(btn => btn.classList.remove('active')); | |
| this.classList.add('active'); | |
| }); | |
| }); | |
| // Range sliders | |
| const matchStrength = document.getElementById('matchStrength'); | |
| const matchValue = document.getElementById('matchValue'); | |
| const lookbackPeriod = document.getElementById('lookbackPeriod'); | |
| const lookbackValue = document.getElementById('lookbackValue'); | |
| matchStrength.addEventListener('input', function() { | |
| matchValue.textContent = `${this.value}% match`; | |
| }); | |
| lookbackPeriod.addEventListener('input', function() { | |
| lookbackValue.textContent = `${this.value} candles`; | |
| }); | |
| // Save pattern | |
| document.getElementById('savePattern').addEventListener('click', function() { | |
| const patternName = document.getElementById('patternName').value.trim(); | |
| const patternType = document.getElementById('patternType').value; | |
| if(!patternName) { | |
| alert('Please enter a pattern name'); | |
| return; | |
| } | |
| if(drawingHistory.length === 0) { | |
| alert('Please draw or upload a pattern first'); | |
| return; | |
| } | |
| // In a real app, you would save this to a database | |
| // For demo, we'll just add it to the pattern selector | |
| const option = document.createElement('option'); | |
| option.value = patternName.toLowerCase().replace(/\s+/g, '-'); | |
| option.textContent = `${patternName} (${patternType})`; | |
| document.getElementById('selectedPattern').appendChild(option); | |
| // Show success message | |
| alert(`Pattern "${patternName}" saved successfully!`); | |
| }); | |
| // Start scan | |
| document.getElementById('startScan').addEventListener('click', function() { | |
| const selectedPattern = document.getElementById('selectedPattern').value; | |
| const instrument = document.getElementById('marketInstrument').value.trim(); | |
| const timeframe = document.getElementById('timeframe').value; | |
| const timeFilter = document.getElementById('timeFilter').value; | |
| if(selectedPattern === 'Select a saved pattern...') { | |
| alert('Please select a pattern to scan for'); | |
| return; | |
| } | |
| if(!instrument) { | |
| alert('Please enter a market instrument'); | |
| return; | |
| } | |
| // Show loading state | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| resultsContainer.innerHTML = ` | |
| <div class="flex flex-col items-center justify-center py-12"> | |
| <div class="w-12 h-12 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mb-4"></div> | |
| <p class="text-gray-600 dark:text-gray-400">Scanning market for patterns...</p> | |
| </div> | |
| `; | |
| // Show results section | |
| document.getElementById('resultsSection').classList.remove('hidden'); | |
| // Simulate API call with timeout | |
| setTimeout(() => { | |
| // Mock results with timestamps that respect the time filter | |
| const now = new Date(); | |
| const mockResults = []; | |
| // Generate results based on time filter | |
| if (timeFilter === '0' || timeFilter === '24') { | |
| mockResults.push({ | |
| match: 92, | |
| date: new Date(now.getTime() - 6 * 60 * 60 * 1000).toISOString().replace('T', ' ').substring(0, 16), | |
| timeframe: '1h', | |
| instrument: instrument, | |
| preview: 'http://static.photos/finance/320x240/1' | |
| }); | |
| } | |
| if (timeFilter === '0' || timeFilter === '8' || timeFilter === '24') { | |
| mockResults.push({ | |
| match: 87, | |
| date: new Date(now.getTime() - 3 * 60 * 60 * 1000).toISOString().replace('T', ' ').substring(0, 16), | |
| timeframe: '4h', | |
| instrument: instrument, | |
| preview: 'http://static.photos/finance/320x240/2' | |
| }); | |
| } | |
| if (timeFilter === '0') { | |
| mockResults.push({ | |
| match: 85, | |
| date: new Date(now.getTime() - 48 * 60 * 60 * 1000).toISOString().replace('T', ' ').substring(0, 16), | |
| timeframe: timeframe, | |
| instrument: instrument, | |
| preview: 'http://static.photos/finance/320x240/3' | |
| }); | |
| } | |
| // Add recent results if time filter is short | |
| if (timeFilter !== '0') { | |
| const hoursAgo = parseFloat(timeFilter); | |
| const maxAge = hoursAgo * 60 * 60 * 1000; | |
| // Add some recent results | |
| for (let i = 1; i <= 3; i++) { | |
| const minutesAgo = Math.floor(Math.random() * hoursAgo * 60); | |
| if (minutesAgo * 60 * 1000 <= maxAge) { | |
| mockResults.push({ | |
| match: 80 + Math.floor(Math.random() * 15), | |
| date: new Date(now.getTime() - minutesAgo * 60 * 1000).toISOString().replace('T', ' ').substring(0, 16), | |
| timeframe: ['5m','15m','30m','1h'][Math.floor(Math.random() * 4)], | |
| instrument: instrument, | |
| preview: `http://static.photos/finance/320x240/${3+i}` | |
| }); | |
| } | |
| } | |
| } | |
| // Display results | |
| displayResults(mockResults); | |
| }, 2000); | |
| }); | |
| // Display scan results | |
| function displayResults(results) { | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| if(results.length === 0) { | |
| resultsContainer.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i data-feather="alert-circle" class="w-12 h-12 mx-auto mb-4"></i> | |
| <p>No matching patterns found</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| return; | |
| } | |
| resultsContainer.innerHTML = results.map(result => ` | |
| <div class="result-card bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border-l-4 border-indigo-500 shadow-sm"> | |
| <div class="flex flex-col md:flex-row md:items-center justify-between gap-4"> | |
| <div class="flex items-center space-x-4"> | |
| <div class="w-16 h-16 bg-gray-200 dark:bg-gray-600 rounded overflow-hidden"> | |
| <img src="${result.preview}" alt="Pattern preview" class="w-full h-full object-cover"> | |
| </div> | |
| <div> | |
| <h3 class="font-medium text-gray-800 dark:text-white">${result.instrument} (${result.timeframe})</h3> | |
| <p class="text-sm text-gray-600 dark:text-gray-300">${result.date}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="text-center"> | |
| <span class="block text-2xl font-bold text-indigo-600 dark:text-indigo-400">${result.match}%</span> | |
| <span class="text-xs text-gray-500 dark:text-gray-400">Match</span> | |
| </div> | |
| <button class="view-chart-btn px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition text-sm" | |
| data-instrument="${result.instrument}" | |
| data-timeframe="${result.timeframe}" | |
| data-match="${result.match}"> | |
| View Chart | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| // Add event listeners to view chart buttons | |
| document.querySelectorAll('.view-chart-btn').forEach(button => { | |
| button.addEventListener('click', () => { | |
| const detail = { | |
| instrument: button.dataset.instrument, | |
| timeframe: button.dataset.timeframe, | |
| match: button.dataset.match | |
| }; | |
| const event = new CustomEvent('viewChart', { detail }); | |
| document.dispatchEvent(event); | |
| }); | |
| }); | |
| } | |
| // Initialize | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| }); |