Files
gh-jeremylongshore-claude-c…/commands/scan-movers.md
2025-11-29 18:53:19 +08:00

900 lines
30 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: Scan for top market movers across crypto, stocks, and forex with real-time updates
shortcut: sm
---
# Scan Market Movers
Comprehensive market scanner identifying top gainers, losers, volume leaders, and unusual activity across multiple asset classes.
## Usage
When the user wants to scan for market movers, implement a real-time scanning system with these capabilities:
### Scan Parameters
- **Markets**: crypto, stocks, forex, all
- **Timeframe**: 1h, 4h, 24h, 7d, 30d
- **Categories**: gainers, losers, volume, volatility, unusual
- **Limit**: Top N results (default: 20)
- **Filters**: Market cap, volume thresholds, price ranges
- **Sort**: By percentage, volume, market cap, volatility
## Implementation
### 1. Market Scanner Engine
```javascript
class MarketMoversScanner {
constructor() {
this.dataSources = {
crypto: {
coingecko: 'https://api.coingecko.com/api/v3',
coinmarketcap: process.env.CMC_API_KEY,
messari: 'https://data.messari.io/api/v1',
binance: 'https://api.binance.com/api/v3'
},
stocks: {
yahoo: 'https://query1.finance.yahoo.com/v1',
alphavantage: process.env.ALPHA_VANTAGE_API,
iex: 'https://api.iextrading.com/1.0',
polygon: process.env.POLYGON_API_KEY
},
forex: {
oanda: process.env.OANDA_API_KEY,
fixer: process.env.FIXER_API_KEY,
currencylayer: process.env.CURRENCY_LAYER_API
}
};
this.scanCache = new Map();
this.updateInterval = 60000; // 1 minute
this.lastUpdate = {};
}
async scanMarkets(params) {
const {
markets = 'all',
timeframe = '24h',
categories = ['gainers', 'losers', 'volume'],
limit = 20,
filters = {},
sortBy = 'percentage'
} = params;
// Initialize scan results
const results = {
timestamp: Date.now(),
timeframe,
markets: markets === 'all' ? ['crypto', 'stocks', 'forex'] : [markets],
data: {
gainers: [],
losers: [],
volumeLeaders: [],
volatilityLeaders: [],
unusual: [],
breakouts: [],
newHighs: [],
newLows: []
},
statistics: {},
alerts: []
};
// Scan each market
for (const market of results.markets) {
const marketData = await this.scanMarket(market, timeframe, filters);
results.data = this.mergeMarketData(results.data, marketData);
}
// Process categories
for (const category of categories) {
results.data[category] = await this.processCategory(
category,
results.data,
limit,
sortBy
);
}
// Calculate statistics
results.statistics = this.calculateStatistics(results.data);
// Identify alerts
results.alerts = this.identifyAlerts(results.data);
return results;
}
async scanMarket(market, timeframe, filters) {
const cacheKey = `${market}_${timeframe}`;
// Check cache
if (this.scanCache.has(cacheKey)) {
const cached = this.scanCache.get(cacheKey);
if (Date.now() - cached.timestamp < this.updateInterval) {
return cached.data;
}
}
// Fetch fresh data
let marketData;
switch (market) {
case 'crypto':
marketData = await this.scanCrypto(timeframe, filters);
break;
case 'stocks':
marketData = await this.scanStocks(timeframe, filters);
break;
case 'forex':
marketData = await this.scanForex(timeframe, filters);
break;
default:
throw new Error(`Unsupported market: ${market}`);
}
// Update cache
this.scanCache.set(cacheKey, {
timestamp: Date.now(),
data: marketData
});
return marketData;
}
async scanCrypto(timeframe, filters) {
const assets = [];
try {
// Fetch from CoinGecko
const cgData = await this.fetchCoinGeckoData(timeframe);
for (const coin of cgData) {
if (this.applyFilters(coin, filters)) {
assets.push({
symbol: coin.symbol.toUpperCase(),
name: coin.name,
market: 'crypto',
price: coin.current_price,
change: this.getChangeForTimeframe(coin, timeframe),
volume: coin.total_volume,
marketCap: coin.market_cap,
high24h: coin.high_24h,
low24h: coin.low_24h,
ath: coin.ath,
athDate: coin.ath_date,
circulatingSupply: coin.circulating_supply,
rank: coin.market_cap_rank,
sparkline: coin.sparkline_in_7d?.price || [],
metrics: {
volatility: this.calculateVolatility(coin),
momentum: this.calculateMomentum(coin),
relativeVolume: coin.total_volume / coin.market_cap,
priceScore: this.calculatePriceScore(coin)
}
});
}
}
// Fetch from Binance for real-time data
const binanceData = await this.fetchBinanceData();
this.enrichWithBinanceData(assets, binanceData);
} catch (error) {
console.error('Error scanning crypto:', error);
}
return assets;
}
async fetchCoinGeckoData(timeframe) {
const periods = {
'1h': '1h',
'24h': '24h',
'7d': '7d',
'30d': '30d'
};
const response = await fetch(
`${this.dataSources.crypto.coingecko}/coins/markets?` +
`vs_currency=usd&order=market_cap_desc&per_page=500&` +
`price_change_percentage=${periods[timeframe] || '24h'},7d,30d&` +
`sparkline=true`
);
return response.json();
}
async fetchBinanceData() {
const response = await fetch(
`${this.dataSources.crypto.binance}/ticker/24hr`
);
const data = await response.json();
const processed = {};
for (const ticker of data) {
if (ticker.symbol.endsWith('USDT')) {
const symbol = ticker.symbol.replace('USDT', '');
processed[symbol] = {
price: parseFloat(ticker.lastPrice),
change24h: parseFloat(ticker.priceChangePercent),
volume: parseFloat(ticker.volume),
quoteVolume: parseFloat(ticker.quoteVolume),
count: parseInt(ticker.count),
weightedAvgPrice: parseFloat(ticker.weightedAvgPrice)
};
}
}
return processed;
}
getChangeForTimeframe(coin, timeframe) {
const changeMap = {
'1h': coin.price_change_percentage_1h_in_currency,
'24h': coin.price_change_percentage_24h,
'7d': coin.price_change_percentage_7d_in_currency,
'30d': coin.price_change_percentage_30d_in_currency
};
return changeMap[timeframe] || coin.price_change_percentage_24h || 0;
}
calculateVolatility(asset) {
if (!asset.sparkline_in_7d?.price || asset.sparkline_in_7d.price.length < 2) {
return 0;
}
const prices = asset.sparkline_in_7d.price;
const returns = [];
for (let i = 1; i < prices.length; i++) {
returns.push((prices[i] - prices[i-1]) / prices[i-1]);
}
const mean = returns.reduce((a, b) => a + b) / returns.length;
const variance = returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / returns.length;
return Math.sqrt(variance) * 100; // Percentage volatility
}
calculateMomentum(asset) {
const weights = {
'24h': 0.4,
'7d': 0.3,
'30d': 0.3
};
const momentum =
(asset.price_change_percentage_24h || 0) * weights['24h'] +
(asset.price_change_percentage_7d_in_currency || 0) * weights['7d'] +
(asset.price_change_percentage_30d_in_currency || 0) * weights['30d'];
return momentum;
}
calculatePriceScore(asset) {
// Score based on price position relative to range
const range = asset.high_24h - asset.low_24h;
if (range === 0) return 50;
const position = (asset.current_price - asset.low_24h) / range;
return position * 100;
}
async scanStocks(timeframe, filters) {
const assets = [];
try {
// Fetch major indices components
const indices = ['SPY', 'QQQ', 'DIA']; // S&P 500, NASDAQ, Dow ETFs
const stockList = await this.fetchStockList(indices);
for (const symbol of stockList) {
const stockData = await this.fetchStockData(symbol, timeframe);
if (stockData && this.applyFilters(stockData, filters)) {
assets.push({
symbol: stockData.symbol,
name: stockData.name,
market: 'stocks',
price: stockData.price,
change: stockData.changePercent,
volume: stockData.volume,
marketCap: stockData.marketCap,
high52w: stockData.week52High,
low52w: stockData.week52Low,
pe: stockData.peRatio,
eps: stockData.eps,
dividend: stockData.dividendYield,
beta: stockData.beta,
metrics: {
rsi: stockData.rsi,
volumeRatio: stockData.volume / stockData.avgVolume,
priceToHigh: stockData.price / stockData.week52High,
earningsGrowth: stockData.earningsGrowth
}
});
}
}
} catch (error) {
console.error('Error scanning stocks:', error);
}
return assets;
}
async processCategory(category, data, limit, sortBy) {
let categoryData = [];
switch (category) {
case 'gainers':
categoryData = this.findTopGainers(data, limit);
break;
case 'losers':
categoryData = this.findTopLosers(data, limit);
break;
case 'volume':
categoryData = this.findVolumeLeaders(data, limit);
break;
case 'volatility':
categoryData = this.findVolatilityLeaders(data, limit);
break;
case 'unusual':
categoryData = this.findUnusualActivity(data, limit);
break;
case 'breakouts':
categoryData = this.findBreakouts(data, limit);
break;
case 'momentum':
categoryData = this.findMomentumPlays(data, limit);
break;
}
return this.sortResults(categoryData, sortBy);
}
findTopGainers(data, limit) {
const allAssets = [...data.gainers, ...data.losers, ...data.volumeLeaders];
const uniqueAssets = this.removeDuplicates(allAssets);
return uniqueAssets
.filter(asset => asset.change > 0)
.sort((a, b) => b.change - a.change)
.slice(0, limit)
.map(asset => ({
...asset,
category: 'GAINER',
signal: this.generateSignal(asset, 'GAINER')
}));
}
findTopLosers(data, limit) {
const allAssets = [...data.gainers, ...data.losers, ...data.volumeLeaders];
const uniqueAssets = this.removeDuplicates(allAssets);
return uniqueAssets
.filter(asset => asset.change < 0)
.sort((a, b) => a.change - b.change)
.slice(0, limit)
.map(asset => ({
...asset,
category: 'LOSER',
signal: this.generateSignal(asset, 'LOSER')
}));
}
findVolumeLeaders(data, limit) {
const allAssets = this.getAllAssets(data);
return allAssets
.filter(asset => asset.metrics?.volumeRatio > 2)
.sort((a, b) => b.metrics.volumeRatio - a.metrics.volumeRatio)
.slice(0, limit)
.map(asset => ({
...asset,
category: 'VOLUME_LEADER',
volumeMultiple: asset.metrics.volumeRatio.toFixed(2) + 'x',
signal: this.generateSignal(asset, 'VOLUME')
}));
}
findUnusualActivity(data, limit) {
const allAssets = this.getAllAssets(data);
const unusual = [];
for (const asset of allAssets) {
const signals = [];
// Check for unusual volume
if (asset.metrics?.volumeRatio > 5) {
signals.push('EXTREME_VOLUME');
}
// Check for large price movement
if (Math.abs(asset.change) > 20) {
signals.push('LARGE_MOVE');
}
// Check for volatility spike
if (asset.metrics?.volatility > 10) {
signals.push('HIGH_VOLATILITY');
}
// Check for new highs/lows
if (asset.price >= asset.high52w * 0.95) {
signals.push('NEAR_52W_HIGH');
}
if (asset.price <= asset.low52w * 1.05) {
signals.push('NEAR_52W_LOW');
}
if (signals.length > 0) {
unusual.push({
...asset,
category: 'UNUSUAL',
signals,
unusualScore: signals.length * 25
});
}
}
return unusual
.sort((a, b) => b.unusualScore - a.unusualScore)
.slice(0, limit);
}
findBreakouts(data, limit) {
const allAssets = this.getAllAssets(data);
const breakouts = [];
for (const asset of allAssets) {
const breakoutSignals = [];
// Volume breakout
if (asset.metrics?.volumeRatio > 3 && asset.change > 5) {
breakoutSignals.push({
type: 'VOLUME_BREAKOUT',
strength: 'HIGH'
});
}
// Price breakout (near 52-week high)
if (asset.high52w && asset.price > asset.high52w * 0.98) {
breakoutSignals.push({
type: 'PRICE_BREAKOUT',
strength: 'VERY_HIGH',
target: asset.high52w * 1.1
});
}
// Momentum breakout
if (asset.metrics?.momentum > 15) {
breakoutSignals.push({
type: 'MOMENTUM_BREAKOUT',
strength: 'MEDIUM'
});
}
if (breakoutSignals.length > 0) {
breakouts.push({
...asset,
category: 'BREAKOUT',
breakoutSignals,
breakoutScore: this.calculateBreakoutScore(breakoutSignals)
});
}
}
return breakouts
.sort((a, b) => b.breakoutScore - a.breakoutScore)
.slice(0, limit);
}
generateSignal(asset, category) {
const signals = {
strength: 'MEDIUM',
action: 'WATCH',
confidence: 50,
reasons: []
};
// Analyze based on category
if (category === 'GAINER') {
if (asset.change > 20) {
signals.strength = 'STRONG';
signals.action = 'MOMENTUM_PLAY';
signals.confidence = 75;
signals.reasons.push('Strong upward momentum');
}
if (asset.metrics?.volumeRatio > 3) {
signals.confidence += 10;
signals.reasons.push('High volume confirmation');
}
} else if (category === 'LOSER') {
if (asset.change < -20) {
signals.strength = 'STRONG';
signals.action = 'OVERSOLD_BOUNCE';
signals.confidence = 60;
signals.reasons.push('Potential oversold bounce');
}
} else if (category === 'VOLUME') {
signals.strength = 'HIGH';
signals.action = 'INVESTIGATE';
signals.confidence = 70;
signals.reasons.push('Unusual volume activity');
}
return signals;
}
identifyAlerts(data) {
const alerts = [];
// Market-wide alerts
const gainersCount = data.gainers?.filter(a => a.change > 10).length || 0;
const losersCount = data.losers?.filter(a => a.change < -10).length || 0;
if (gainersCount > 50) {
alerts.push({
type: 'MARKET_RALLY',
message: `Strong market rally detected: ${gainersCount} assets up >10%`,
severity: 'INFO'
});
}
if (losersCount > 50) {
alerts.push({
type: 'MARKET_SELLOFF',
message: `Market selloff detected: ${losersCount} assets down >10%`,
severity: 'WARNING'
});
}
// Individual asset alerts
for (const category of Object.values(data)) {
if (!Array.isArray(category)) continue;
for (const asset of category) {
if (asset.change > 50) {
alerts.push({
type: 'EXTREME_GAIN',
symbol: asset.symbol,
message: `${asset.symbol} up ${asset.change.toFixed(2)}% - extreme movement`,
severity: 'HIGH'
});
}
if (asset.change < -30) {
alerts.push({
type: 'EXTREME_LOSS',
symbol: asset.symbol,
message: `${asset.symbol} down ${Math.abs(asset.change).toFixed(2)}% - potential crash`,
severity: 'CRITICAL'
});
}
if (asset.metrics?.volumeRatio > 10) {
alerts.push({
type: 'VOLUME_EXPLOSION',
symbol: asset.symbol,
message: `${asset.symbol} volume ${asset.metrics.volumeRatio.toFixed(1)}x average`,
severity: 'HIGH'
});
}
}
}
return alerts;
}
}
```
### 2. Display Interface
```javascript
class MoversDisplay {
displayResults(results) {
const output = `
╔════════════════════════════════════════════════════════════════╗
║ MARKET MOVERS SCANNER ║
╠════════════════════════════════════════════════════════════════╣
║ Timeframe: ${results.timeframe.padEnd(48)}
║ Markets: ${results.markets.join(', ').padEnd(48)}
║ Last Update: ${new Date(results.timestamp).toLocaleString().padEnd(48)}
╠════════════════════════════════════════════════════════════════╣
║ TOP GAINERS ║
╠════════════════════════════════════════════════════════════════╣
${this.formatMovers(results.data.gainers, 'gain')}
╠════════════════════════════════════════════════════════════════╣
║ TOP LOSERS ║
╠════════════════════════════════════════════════════════════════╣
${this.formatMovers(results.data.losers, 'loss')}
╠════════════════════════════════════════════════════════════════╣
║ VOLUME LEADERS ║
╠════════════════════════════════════════════════════════════════╣
${this.formatVolumeLeaders(results.data.volumeLeaders)}
╠════════════════════════════════════════════════════════════════╣
║ UNUSUAL ACTIVITY ║
╠════════════════════════════════════════════════════════════════╣
${this.formatUnusual(results.data.unusual)}
╠════════════════════════════════════════════════════════════════╣
║ ALERTS ║
╠════════════════════════════════════════════════════════════════╣
${this.formatAlerts(results.alerts)}
╚════════════════════════════════════════════════════════════════╝
`;
return output;
}
formatMovers(movers, type) {
if (!movers || movers.length === 0) {
return '║ No significant movers found ║';
}
const lines = [];
for (const mover of movers.slice(0, 5)) {
const changeStr = type === 'gain'
? `+${mover.change.toFixed(2)}%`
: `${mover.change.toFixed(2)}%`;
const emoji = type === 'gain' ? '' : '';
lines.push(
`${emoji} ${mover.symbol.padEnd(8)} ${changeStr.padEnd(10)} ` +
`$${this.formatPrice(mover.price).padEnd(12)} ${this.formatVolume(mover.volume).padEnd(12)}`
);
}
return lines.join('\n');
}
formatVolumeLeaders(leaders) {
if (!leaders || leaders.length === 0) {
return '║ No volume leaders found ║';
}
const lines = [];
for (const leader of leaders.slice(0, 5)) {
lines.push(
`${leader.symbol.padEnd(8)} ${leader.volumeMultiple.padEnd(6)} ` +
`${this.formatChange(leader.change).padEnd(10)} Signal: ${leader.signal.action.padEnd(15)}`
);
}
return lines.join('\n');
}
formatUnusual(unusual) {
if (!unusual || unusual.length === 0) {
return '║ No unusual activity detected ║';
}
const lines = [];
for (const item of unusual.slice(0, 3)) {
const signals = item.signals.join(', ');
lines.push(
` ${item.symbol.padEnd(8)} ${signals.padEnd(45)}`
);
}
return lines.join('\n');
}
formatAlerts(alerts) {
if (!alerts || alerts.length === 0) {
return '║ No alerts at this time ║';
}
const lines = [];
const severityEmoji = {
'INFO': '',
'WARNING': '',
'HIGH': '',
'CRITICAL': ''
};
for (const alert of alerts.slice(0, 3)) {
const emoji = severityEmoji[alert.severity] || '';
lines.push(
`${emoji} ${alert.message.padEnd(55)}`
);
}
return lines.join('\n');
}
formatPrice(price) {
if (price > 10000) return price.toFixed(0);
if (price > 100) return price.toFixed(2);
if (price > 1) return price.toFixed(4);
return price.toFixed(8);
}
formatVolume(volume) {
if (volume > 1e9) return `${(volume / 1e9).toFixed(2)}B`;
if (volume > 1e6) return `${(volume / 1e6).toFixed(2)}M`;
if (volume > 1e3) return `${(volume / 1e3).toFixed(2)}K`;
return volume.toFixed(0);
}
formatChange(change) {
const formatted = change.toFixed(2);
if (change > 0) return `+${formatted}%`;
return `${formatted}%`;
}
}
```
### 3. Real-Time Updates
```javascript
class RealTimeScanner {
constructor() {
this.scanner = new MarketMoversScanner();
this.display = new MoversDisplay();
this.updateInterval = 30000; // 30 seconds
this.isRunning = false;
}
async start(params) {
this.isRunning = true;
console.log('Starting real-time market scanner...');
while (this.isRunning) {
try {
// Scan markets
const results = await this.scanner.scanMarkets(params);
// Clear console and display
console.clear();
console.log(this.display.displayResults(results));
// Check for critical alerts
this.checkCriticalAlerts(results.alerts);
// Wait for next update
await this.sleep(this.updateInterval);
} catch (error) {
console.error('Scanner error:', error);
await this.sleep(5000); // Retry after 5 seconds
}
}
}
checkCriticalAlerts(alerts) {
const critical = alerts.filter(a => a.severity === 'CRITICAL' || a.severity === 'HIGH');
if (critical.length > 0) {
console.log('\n CRITICAL ALERTS:');
for (const alert of critical) {
console.log(`- ${alert.message}`);
}
// Could trigger notifications here
}
}
stop() {
this.isRunning = false;
console.log('Scanner stopped.');
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
```
### 4. Advanced Filtering
```javascript
class ScannerFilters {
applyFilters(asset, filters) {
// Market cap filter
if (filters.minMarketCap && asset.marketCap < filters.minMarketCap) {
return false;
}
if (filters.maxMarketCap && asset.marketCap > filters.maxMarketCap) {
return false;
}
// Volume filter
if (filters.minVolume && asset.volume < filters.minVolume) {
return false;
}
// Price filter
if (filters.minPrice && asset.price < filters.minPrice) {
return false;
}
if (filters.maxPrice && asset.price > filters.maxPrice) {
return false;
}
// Change filter
if (filters.minChange && asset.change < filters.minChange) {
return false;
}
if (filters.maxChange && asset.change > filters.maxChange) {
return false;
}
// Custom filters
if (filters.excludeStablecoins && this.isStablecoin(asset.symbol)) {
return false;
}
if (filters.onlyTop100 && asset.rank > 100) {
return false;
}
return true;
}
isStablecoin(symbol) {
const stablecoins = ['USDT', 'USDC', 'BUSD', 'DAI', 'TUSD', 'USDP', 'USDD'];
return stablecoins.includes(symbol.toUpperCase());
}
createSmartFilters(scanType) {
const filters = {
dayTrading: {
minVolume: 10000000,
minPrice: 0.01,
maxPrice: 100000,
minChange: 2,
excludeStablecoins: true
},
swingTrading: {
minMarketCap: 100000000,
minVolume: 5000000,
minChange: 5,
onlyTop100: true
},
pennyStocks: {
maxPrice: 5,
minVolume: 1000000,
minChange: 10
},
blueChips: {
minMarketCap: 10000000000,
minVolume: 100000000
}
};
return filters[scanType] || {};
}
}
```
## Error Handling
```javascript
try {
const scanner = new RealTimeScanner();
await scanner.start({
markets: 'all',
timeframe: '24h',
categories: ['gainers', 'losers', 'volume', 'unusual'],
limit: 20,
filters: {
minVolume: 1000000,
excludeStablecoins: true
},
sortBy: 'percentage'
});
// Graceful shutdown
process.on('SIGINT', () => {
scanner.stop();
process.exit(0);
});
} catch (error) {
console.error('Failed to start scanner:', error);
process.exit(1);
}
```
This command provides comprehensive market scanning with real-time updates, advanced filtering, and alert detection across multiple asset classes.