Skip to content

Instantly share code, notes, and snippets.

@themanyone
Last active May 4, 2025 16:16
Show Gist options
  • Save themanyone/36d2874c17797866dd681eb004b5f93a to your computer and use it in GitHub Desktop.
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.
/**
* 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