Last active
May 4, 2025 16:16
-
-
Save themanyone/36d2874c17797866dd681eb004b5f93a to your computer and use it in GitHub Desktop.
Tradingview Sort - Fixes gainers, volume gainers not being able to sort by columns.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* jQuery-based table sorting utility for TradingView-like watchlist | |
* Supports sorting by multiple columns with configurable settings | |
*/ | |
(function($) { | |
// Configuration object | |
const CONFIG = { | |
selectors: { | |
rowWrapper: 'div.wrap-IEe5qpW4', | |
columns: { | |
changePercent: { | |
selector: 'span.cell-RsFlttSS.changeInPercents-RsFlttSS span.inner-RsFlttSS', | |
parse: function(text, $element) { | |
// Use minus-RsFlttSS class to detect negative values | |
const isNegative = $element.hasClass('minus-RsFlttSS') || text.startsWith('-'); | |
const cleaned = text.replace(/[%−–—-]/g, '').trim(); | |
const num = parseFloat(cleaned); | |
if (isNaN(num)) { | |
console.warn(`JQ: Failed to parse Chg% value: "${text}"`); | |
return -Infinity; | |
} | |
return isNegative ? -num : num; | |
}, | |
type: 'number', | |
default: -Infinity | |
}, | |
symbol: { | |
selector: 'span.inner-RsFlttSS.symbolNameText-RsFlttSS', | |
parse: text => text.trim().toLowerCase() || '', | |
type: 'string', | |
default: '' | |
}, | |
change: { | |
selector: 'span.cell-RsFlttSS.change-RsFlttSS span.inner-RsFlttSS', | |
parse: function(text, $element) { | |
const isNegative = $element.hasClass('minus-RsFlttSS') || text.startsWith('-'); | |
const cleaned = text.replace(/[−+–—-]/g, '').trim(); | |
const num = parseFloat(cleaned); | |
return isNaN(num) ? 0 : (isNegative ? -num : num); | |
}, | |
type: 'number', | |
default: 0 | |
}, | |
last: { | |
selector: 'span.cell-RsFlttSS.last-RsFlttSS span.inner-RsFlttSS', | |
parse: text => { | |
const cleaned = text.replace(/[$€,]/g, '').trim(); | |
const num = parseFloat(cleaned); | |
if (isNaN(num)) { | |
console.warn(`JQ: Failed to parse Last value: "${text}"`); | |
return 0; | |
} | |
return num; | |
}, | |
type: 'number', | |
default: 0 | |
} | |
} | |
}, | |
layout: { | |
rowHeight: 30, // pixels | |
initialTopOffset: 6 // pixels | |
} | |
}; | |
/** | |
* Sorts the watchlist table by specified column | |
* @param {string} columnKey - The column to sort by (e.g., 'changePercent', 'symbol') | |
* @param {boolean} ascending - Sort direction (true for ascending, false for descending) | |
* @returns {Array<Object>|null} Sorted row data or null on failure | |
*/ | |
function sortWatchlist(columnKey, ascending = false) { | |
console.log(`JQ: Sorting by ${columnKey} (${ascending ? 'ASC' : 'DESC'})...`); | |
const columnConfig = CONFIG.selectors.columns[columnKey]; | |
if (!columnConfig) { | |
console.error(`JQ: Invalid column key: ${columnKey}`); | |
return null; | |
} | |
// Find all row containers | |
const $rowElements = $(CONFIG.selectors.rowWrapper) | |
.parent() | |
.filter((_, el) => $(el).css('position') === 'absolute' && $(el).css('top')); | |
if ($rowElements.length === 0) { | |
console.error(`JQ: No rows found using selector: ${CONFIG.selectors.rowWrapper}`); | |
return null; | |
} | |
console.log(`JQ: Found ${$rowElements.length} rows`); | |
const $tableBodyContainer = $rowElements.first().parent(); | |
if ($tableBodyContainer.length === 0) { | |
console.error("JQ: Could not find table container"); | |
return null; | |
} | |
// Extract row data | |
const rowData = []; | |
$rowElements.each((index, rowEl) => { | |
const $rowEl = $(rowEl); | |
const $cell = $rowEl.find(columnConfig.selector); | |
let sortValue = columnConfig.default; | |
const data = { element: $rowEl }; | |
if ($cell.length && $cell.text()) { | |
sortValue = columnConfig.parse($cell.text(), $cell); | |
} else { | |
console.warn(`JQ: Could not parse ${columnKey} for row ${index}. Selector: ${columnConfig.selector}`); | |
} | |
// Store all column values | |
Object.keys(CONFIG.selectors.columns).forEach(key => { | |
const $keyCell = $rowEl.find(CONFIG.selectors.columns[key].selector); | |
data[key] = key === columnKey ? sortValue : | |
($keyCell.length ? $keyCell.text().trim() : CONFIG.selectors.columns[key].default); | |
}); | |
rowData.push(data); | |
}); | |
// Sort data | |
rowData.sort((a, b) => { | |
const aVal = a[columnKey]; | |
const bVal = b[columnKey]; | |
if (columnConfig.type === 'number') { | |
return ascending ? aVal - bVal : bVal - aVal; | |
} else { | |
return ascending ? | |
aVal.localeCompare(bVal) : | |
bVal.localeCompare(aVal); | |
} | |
}); | |
// Update DOM positions | |
rowData.forEach((data, index) => { | |
const newTop = CONFIG.layout.initialTopOffset + (index * CONFIG.layout.rowHeight); | |
data.element.css('top', `${newTop}px`); | |
}); | |
console.log(`JQ: Sorting by ${columnKey} complete. ${rowData.length} rows repositioned.`); | |
return rowData; | |
} | |
/** | |
* Logs top N symbols from sorted data | |
* @param {Array<Object>} sortedData - Sorted row data | |
* @param {number} count - Number of symbols to log | |
*/ | |
function logTopSymbols(sortedData, count = 10) { | |
if (!sortedData || sortedData.length === 0) { | |
console.log("JQ: No valid sorted data"); | |
return; | |
} | |
const numToLog = Math.min(count, sortedData.length); | |
const symbols = sortedData | |
.slice(0, numToLog) | |
.map(item => item.symbol) | |
.filter(symbol => symbol); | |
if (symbols.length) { | |
console.log(`JQ: Top ${symbols.length} Symbols: ${symbols.join(' ')}`); | |
} else { | |
console.log(`JQ: No valid symbols found in top ${numToLog} rows`); | |
} | |
} | |
// Event listener setup | |
$(function() { | |
const sortButtons = { | |
changePercent: 'button[data-column-type="change_percent"]', | |
symbol: 'button[data-column-type="short_name"]', | |
change: 'button[data-column-type="change"]', | |
last: 'button[data-column-type="last_price"]' | |
}; | |
let sortStates = {}; // Track sort direction per column | |
Object.entries(sortButtons).forEach(([columnKey, selector]) => { | |
const $buttons = $(selector); | |
if ($buttons.length === 0) { | |
console.warn(`JQ: Button not found for ${columnKey} with selector: ${selector}`); | |
return; | |
} | |
console.log(`JQ: Found ${$buttons.length} button(s) for ${columnKey} with selector: ${selector}`); | |
$(document).on('click', selector, function(e) { | |
e.preventDefault(); | |
sortStates[columnKey] = !(sortStates[columnKey] || false); | |
console.log(`JQ: ${columnKey} button clicked (selector: ${selector})`); | |
const sortedData = sortWatchlist(columnKey, sortStates[columnKey]); | |
if (sortedData) { | |
logTopSymbols(sortedData, 10); | |
} else { | |
console.error(`JQ: Sorting failed for ${columnKey}`); | |
} | |
}); | |
console.log(`JQ: Listener attached to ${selector} for ${columnKey}`); | |
}); | |
}); | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment