Initial commit
This commit is contained in:
16
.claude-plugin/plugin.json
Normal file
16
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "market-movers-scanner",
|
||||||
|
"description": "Scan for top market movers - gainers, losers, volume spikes, and unusual activity",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Intent Solutions IO",
|
||||||
|
"email": "jeremy@intentsolutions.ai",
|
||||||
|
"url": "https://intentsolutions.ai"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# market-movers-scanner
|
||||||
|
|
||||||
|
Scan for top market movers - gainers, losers, volume spikes, and unusual activity
|
||||||
900
commands/scan-movers.md
Normal file
900
commands/scan-movers.md
Normal file
@@ -0,0 +1,900 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
85
plugin.lock.json
Normal file
85
plugin.lock.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/crypto/market-movers-scanner",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "814f8034a2dccd664d5aa222ec5d2332a047eceb",
|
||||||
|
"treeHash": "a5164506e2680193c85244d6fea07667ca7673949ca47d82074344b297662ff9",
|
||||||
|
"generatedAt": "2025-11-28T10:18:33.223596Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "market-movers-scanner",
|
||||||
|
"description": "Scan for top market movers - gainers, losers, volume spikes, and unusual activity",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "e93a83e44812b0136c8d16d735637b7b1bd62528de7e29bbcab7a66afc7cb6c8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "bda9b847b7bebba99374bf52f2d578ff52e8f3f4daeef6829fbdfe2a696cee9f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/scan-movers.md",
|
||||||
|
"sha256": "a4961527b08bcd4c2fe0103c0f233bef3ff0fdd79742aa36ce8f67e48cb15e54"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/examples.md",
|
||||||
|
"sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/best-practices.md",
|
||||||
|
"sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/README.md",
|
||||||
|
"sha256": "25b8ed650dc743ee9fa66a86b867cd2a10c53811d2237c4226080c4b6e98add9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/helper-template.sh",
|
||||||
|
"sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/validation.sh",
|
||||||
|
"sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/README.md",
|
||||||
|
"sha256": "1d488cc92b8d86764ce598c4f70814f840c905310738e50d96b63da942aa298c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/test-data.json",
|
||||||
|
"sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/README.md",
|
||||||
|
"sha256": "d3c725146e5bc2372dba3e3cf2c957157ca66821f1a881626ebc608a3783da9e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/skill-schema.json",
|
||||||
|
"sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/config-template.json",
|
||||||
|
"sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "a5164506e2680193c85244d6fea07667ca7673949ca47d82074344b297662ff9"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
7
skills/skill-adapter/assets/README.md
Normal file
7
skills/skill-adapter/assets/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Assets
|
||||||
|
|
||||||
|
Bundled resources for market-movers-scanner skill
|
||||||
|
|
||||||
|
- [ ] alert_templates/: Templates for alert notifications, including email, SMS, and push notifications.
|
||||||
|
- [ ] configuration_examples/: Example configuration files for different use cases and market conditions.
|
||||||
|
- [ ] sample_data/: Sample market data for testing and demonstration purposes.
|
||||||
32
skills/skill-adapter/assets/config-template.json
Normal file
32
skills/skill-adapter/assets/config-template.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"skill": {
|
||||||
|
"name": "skill-name",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"enabled": true,
|
||||||
|
"settings": {
|
||||||
|
"verbose": false,
|
||||||
|
"autoActivate": true,
|
||||||
|
"toolRestrictions": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"triggers": {
|
||||||
|
"keywords": [
|
||||||
|
"example-trigger-1",
|
||||||
|
"example-trigger-2"
|
||||||
|
],
|
||||||
|
"patterns": []
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"allowed": [
|
||||||
|
"Read",
|
||||||
|
"Grep",
|
||||||
|
"Bash"
|
||||||
|
],
|
||||||
|
"restricted": []
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Plugin Author",
|
||||||
|
"category": "general",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
28
skills/skill-adapter/assets/skill-schema.json
Normal file
28
skills/skill-adapter/assets/skill-schema.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Claude Skill Configuration",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "description"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9-]+$",
|
||||||
|
"maxLength": 64,
|
||||||
|
"description": "Skill identifier (lowercase, hyphens only)"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1024,
|
||||||
|
"description": "What the skill does and when to use it"
|
||||||
|
},
|
||||||
|
"allowed-tools": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Comma-separated list of allowed tools"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
||||||
|
"description": "Semantic version (x.y.z)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
skills/skill-adapter/assets/test-data.json
Normal file
27
skills/skill-adapter/assets/test-data.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"testCases": [
|
||||||
|
{
|
||||||
|
"name": "Basic activation test",
|
||||||
|
"input": "trigger phrase example",
|
||||||
|
"expected": {
|
||||||
|
"activated": true,
|
||||||
|
"toolsUsed": ["Read", "Grep"],
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Complex workflow test",
|
||||||
|
"input": "multi-step trigger example",
|
||||||
|
"expected": {
|
||||||
|
"activated": true,
|
||||||
|
"steps": 3,
|
||||||
|
"toolsUsed": ["Read", "Write", "Bash"],
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fixtures": {
|
||||||
|
"sampleInput": "example data",
|
||||||
|
"expectedOutput": "processed result"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
skills/skill-adapter/references/README.md
Normal file
8
skills/skill-adapter/references/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# References
|
||||||
|
|
||||||
|
Bundled resources for market-movers-scanner skill
|
||||||
|
|
||||||
|
- [ ] market_data_api.md: Detailed documentation of the market data APIs used by the plugin, including endpoints, parameters, and response formats.
|
||||||
|
- [ ] alert_configuration.md: Comprehensive guide to configuring alerts, including available options, thresholds, and notification methods.
|
||||||
|
- [ ] supported_exchanges.md: List of supported cryptocurrency exchanges, stock exchanges, and forex brokers.
|
||||||
|
- [ ] data_aggregation_methods.md: Explanation of the data aggregation methods used by the plugin to ensure data reliability.
|
||||||
69
skills/skill-adapter/references/best-practices.md
Normal file
69
skills/skill-adapter/references/best-practices.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Skill Best Practices
|
||||||
|
|
||||||
|
Guidelines for optimal skill usage and development.
|
||||||
|
|
||||||
|
## For Users
|
||||||
|
|
||||||
|
### Activation Best Practices
|
||||||
|
|
||||||
|
1. **Use Clear Trigger Phrases**
|
||||||
|
- Match phrases from skill description
|
||||||
|
- Be specific about intent
|
||||||
|
- Provide necessary context
|
||||||
|
|
||||||
|
2. **Provide Sufficient Context**
|
||||||
|
- Include relevant file paths
|
||||||
|
- Specify scope of analysis
|
||||||
|
- Mention any constraints
|
||||||
|
|
||||||
|
3. **Understand Tool Permissions**
|
||||||
|
- Check allowed-tools in frontmatter
|
||||||
|
- Know what the skill can/cannot do
|
||||||
|
- Request appropriate actions
|
||||||
|
|
||||||
|
### Workflow Optimization
|
||||||
|
|
||||||
|
- Start with simple requests
|
||||||
|
- Build up to complex workflows
|
||||||
|
- Verify each step before proceeding
|
||||||
|
- Use skill consistently for related tasks
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
### Skill Development Guidelines
|
||||||
|
|
||||||
|
1. **Clear Descriptions**
|
||||||
|
- Include explicit trigger phrases
|
||||||
|
- Document all capabilities
|
||||||
|
- Specify limitations
|
||||||
|
|
||||||
|
2. **Proper Tool Permissions**
|
||||||
|
- Use minimal necessary tools
|
||||||
|
- Document security implications
|
||||||
|
- Test with restricted tools
|
||||||
|
|
||||||
|
3. **Comprehensive Documentation**
|
||||||
|
- Provide usage examples
|
||||||
|
- Document common pitfalls
|
||||||
|
- Include troubleshooting guide
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- Keep version updated
|
||||||
|
- Test after tool updates
|
||||||
|
- Monitor user feedback
|
||||||
|
- Iterate on descriptions
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
- Scope skills to specific domains
|
||||||
|
- Avoid overlapping trigger phrases
|
||||||
|
- Keep descriptions under 1024 chars
|
||||||
|
- Test activation reliability
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Never include secrets in skill files
|
||||||
|
- Validate all inputs
|
||||||
|
- Use read-only tools when possible
|
||||||
|
- Document security requirements
|
||||||
70
skills/skill-adapter/references/examples.md
Normal file
70
skills/skill-adapter/references/examples.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Skill Usage Examples
|
||||||
|
|
||||||
|
This document provides practical examples of how to use this skill effectively.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Example 1: Simple Activation
|
||||||
|
|
||||||
|
**User Request:**
|
||||||
|
```
|
||||||
|
[Describe trigger phrase here]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Skill Response:**
|
||||||
|
1. Analyzes the request
|
||||||
|
2. Performs the required action
|
||||||
|
3. Returns results
|
||||||
|
|
||||||
|
### Example 2: Complex Workflow
|
||||||
|
|
||||||
|
**User Request:**
|
||||||
|
```
|
||||||
|
[Describe complex scenario]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
1. Step 1: Initial analysis
|
||||||
|
2. Step 2: Data processing
|
||||||
|
3. Step 3: Result generation
|
||||||
|
4. Step 4: Validation
|
||||||
|
|
||||||
|
## Advanced Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Chaining Operations
|
||||||
|
|
||||||
|
Combine this skill with other tools:
|
||||||
|
```
|
||||||
|
Step 1: Use this skill for [purpose]
|
||||||
|
Step 2: Chain with [other tool]
|
||||||
|
Step 3: Finalize with [action]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Error Handling
|
||||||
|
|
||||||
|
If issues occur:
|
||||||
|
- Check trigger phrase matches
|
||||||
|
- Verify context is available
|
||||||
|
- Review allowed-tools permissions
|
||||||
|
|
||||||
|
## Tips & Best Practices
|
||||||
|
|
||||||
|
- ✅ Be specific with trigger phrases
|
||||||
|
- ✅ Provide necessary context
|
||||||
|
- ✅ Check tool permissions match needs
|
||||||
|
- ❌ Avoid vague requests
|
||||||
|
- ❌ Don't mix unrelated tasks
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
**Issue:** Skill doesn't activate
|
||||||
|
**Solution:** Use exact trigger phrases from description
|
||||||
|
|
||||||
|
**Issue:** Unexpected results
|
||||||
|
**Solution:** Check input format and context
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- Main SKILL.md for full documentation
|
||||||
|
- scripts/ for automation helpers
|
||||||
|
- assets/ for configuration examples
|
||||||
8
skills/skill-adapter/scripts/README.md
Normal file
8
skills/skill-adapter/scripts/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Scripts
|
||||||
|
|
||||||
|
Bundled resources for market-movers-scanner skill
|
||||||
|
|
||||||
|
- [ ] alert_manager.py: Manages and triggers alerts based on market movements, allowing users to configure thresholds and notification methods.
|
||||||
|
- [ ] data_aggregator.py: Aggregates data from multiple sources to ensure data reliability and accuracy.
|
||||||
|
- [ ] cache_optimizer.py: Optimizes data caching to improve performance and reduce latency.
|
||||||
|
- [ ] websocket_manager.py: Manages WebSocket connections for real-time data updates.
|
||||||
42
skills/skill-adapter/scripts/helper-template.sh
Executable file
42
skills/skill-adapter/scripts/helper-template.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Helper script template for skill automation
|
||||||
|
# Customize this for your skill's specific needs
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function show_usage() {
|
||||||
|
echo "Usage: $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo " -v, --verbose Enable verbose output"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
VERBOSE=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Your skill logic here
|
||||||
|
if [ "$VERBOSE" = true ]; then
|
||||||
|
echo "Running skill automation..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Complete"
|
||||||
32
skills/skill-adapter/scripts/validation.sh
Executable file
32
skills/skill-adapter/scripts/validation.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Skill validation helper
|
||||||
|
# Validates skill activation and functionality
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔍 Validating skill..."
|
||||||
|
|
||||||
|
# Check if SKILL.md exists
|
||||||
|
if [ ! -f "../SKILL.md" ]; then
|
||||||
|
echo "❌ Error: SKILL.md not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate frontmatter
|
||||||
|
if ! grep -q "^---$" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: No frontmatter found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required fields
|
||||||
|
if ! grep -q "^name:" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: Missing 'name' field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^description:" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: Missing 'description' field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Skill validation passed"
|
||||||
Reference in New Issue
Block a user