diff --git a/plugins/tokenMetricsPlugin/CHANGELOG.md b/plugins/tokenMetricsPlugin/CHANGELOG.md new file mode 100644 index 00000000..439d54a7 --- /dev/null +++ b/plugins/tokenMetricsPlugin/CHANGELOG.md @@ -0,0 +1,83 @@ +# Changelog + +All notable changes to the Token Metrics Virtuals Plugin will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2024-12-19 + +### ๐ŸŽ‰ Initial Release + +#### Added +- โœจ Complete integration with Virtuals Protocol GAME framework +- ๐Ÿ”Œ 17 Token Metrics API endpoints as AI-callable functions +- ๐Ÿค– Full GameAgent compatibility with natural language processing +- ๐Ÿ“Š Comprehensive cryptocurrency analysis capabilities +- ๐Ÿ’ฌ Interactive chat interface for testing and exploration +- ๐Ÿ›ก๏ธ Built-in error handling and rate limiting +- ๐Ÿ“š Comprehensive documentation and examples +- ๐Ÿงช Complete testing suite with 17+ example files +- ๐Ÿ”ท Full TypeScript support with type definitions +- โšก Beautiful formatted responses with color coding + +#### Core Functions +- `getTokens` - Get supported cryptocurrencies and TOKEN_IDs +- `getTopMarketCapTokens` - Retrieve top cryptos by market cap +- `getPriceData` - Get current market prices +- `getTraderGrades` - AI-powered trader performance grades +- `getInvestorGrades` - Investor performance analysis +- `getTradingSignals` - Buy/sell/hold recommendations +- `getMarketMetrics` - Comprehensive market analysis +- `getQuantmetrics` - Quantitative trading metrics +- `getHourlyOhlcv` - Hourly OHLC data with volume +- `getDailyOhlcv` - Daily OHLC with technical indicators +- `getAiReports` - AI-generated market reports +- `getTokenMetricsAi` - Custom AI analysis queries +- `getSentiments` - Market sentiment analysis +- `getCryptoInvestors` - Crypto investor performance +- `getResistanceSupport` - Resistance and support levels +- `getScenarioAnalysis` - Scenario-based projections +- `getCorrelation` - Crypto correlation analysis + +#### Documentation +- ๐Ÿ“– Comprehensive README with quick start guide +- ๐Ÿ› ๏ธ Developer Integration Guide with step-by-step instructions +- ๐Ÿงช Complete Test Guide covering all endpoints +- ๐Ÿ“‹ API Reference with detailed function documentation +- ๐Ÿ”ง Environment setup with example configuration +- ๐Ÿ“„ MIT License for open source usage + +#### Developer Experience +- ๐Ÿš€ 5-step integration process +- ๐ŸŽ›๏ธ Flexible configuration options +- ๐Ÿ”ง Multiple integration approaches (full vs. selective functions) +- ๐Ÿงช Comprehensive testing tools and examples +- ๐Ÿ’ฌ Interactive chat interface for hands-on testing +- ๐Ÿ›ก๏ธ Production-ready error handling and logging + +--- + +## [Unreleased] + +### Planned Features +- ๐Ÿ“ˆ Additional technical analysis indicators +- ๐Ÿ”” Real-time notifications and alerts +- ๐Ÿ“Š Enhanced data visualization capabilities +- ๐Ÿค– Advanced AI conversation features +- ๐Ÿ”„ Webhook support for real-time updates + +--- + +## Contributing + +When contributing to this project, please: +1. Update the CHANGELOG.md with your changes +2. Follow semantic versioning for version numbers +3. Include comprehensive tests for new features +4. Update documentation as needed + +## Version History + +- **v1.0.0** - Initial release with 17 Token Metrics endpoints +- **Future versions** - See [Unreleased] section above diff --git a/plugins/tokenMetricsPlugin/COMPREHENSIVE_TEST_GUIDE.md b/plugins/tokenMetricsPlugin/COMPREHENSIVE_TEST_GUIDE.md new file mode 100644 index 00000000..d26728c9 --- /dev/null +++ b/plugins/tokenMetricsPlugin/COMPREHENSIVE_TEST_GUIDE.md @@ -0,0 +1,285 @@ +# Comprehensive Token Metrics Plugin Test Guide + +## ๐Ÿงช **Complete Endpoint Testing - All 17 Endpoints** + +### **๐Ÿ“‹ Core Data Endpoints (Direct API Access)** + +#### **1. Tokens List** (`getTokens`) +```bash +npm run example:tokens +``` +**What it does**: Get list of all supported cryptocurrencies with their TOKEN_IDs +**Use case**: Find token IDs for other API calls + +#### **2. Top Market Cap Tokens** (`getTopMarketCapTokens`) +```bash +npm run example:top-market-cap +``` +**What it does**: Get top cryptocurrencies by market capitalization +**Use case**: Identify market leaders and trending tokens + +#### **3. Price Data** (`getPriceData`) +```bash +npm run example:price-data +``` +**What it does**: Get current prices for specific token IDs +**Use case**: Real-time price monitoring + +#### **4. Trader Grades** (`getTraderGrades`) +```bash +npm run example:trader-grades +``` +**What it does**: AI-generated trading scores for tokens +**Use case**: Short-term trading decisions + +#### **5. Investor Grades** (`getInvestorGrades`) +```bash +npm run example:investor-grades +``` +**What it does**: Long-term investment ratings +**Use case**: Portfolio building and investment decisions + +#### **6. Market Metrics** (`getMarketMetrics`) +```bash +npm run example:market-metrics +``` +**What it does**: Overall market indicators and trends +**Use case**: Market timing and macro analysis + +#### **7. Quantmetrics** (`getQuantmetrics`) +```bash +npm run example:quantmetrics +``` +**What it does**: Quantitative analysis and metrics +**Use case**: Advanced technical analysis + +#### **8. Hourly OHLCV** (`getHourlyOhlcv`) +```bash +npm run example:hourly-ohlcv +``` +**What it does**: Hourly price data (Open, High, Low, Close, Volume) +**Use case**: Short-term technical analysis + +#### **9. Daily OHLCV** (`getDailyOhlcv`) +```bash +npm run example:daily-ohlcv +``` +**What it does**: Daily price data for longer-term analysis +**Use case**: Long-term trend analysis + +#### **10. AI Reports** (`getAiReports`) +```bash +npm run example:ai-reports +``` +**What it does**: Comprehensive AI-generated analysis reports +**Use case**: Deep fundamental analysis + +#### **11. Crypto Investors** (`getCryptoInvestors`) +```bash +npm run example:crypto-investors +``` +**What it does**: Institutional investor data and insights +**Use case**: Following smart money + +#### **12. Resistance/Support** (`getResistanceSupport`) +```bash +npm run example:resistance-support +``` +**What it does**: Technical analysis support and resistance levels +**Use case**: Entry/exit point identification + +#### **13. Trading Signals** (`getTradingSignals`) +```bash +npm run example:trading-signals +``` +**What it does**: AI-generated buy/sell recommendations +**Use case**: Automated trading decisions + +--- + +### **๐Ÿš€ New Advanced Endpoints (Chat Interface + Direct)** + +#### **14. Token Metrics AI Chat** (`getTokenMetricsAi`) +```bash +npm run example:tokenmetrics-ai +``` +**Chat prompts**: +- `"What is the next 100x coin?"` +- `"How does DeFi work?"` +- `"Should I invest in Bitcoin?"` +- `"AI explain blockchain technology"` + +#### **15. Sentiment Analysis** (`getSentiments`) +```bash +npm run example:sentiments +``` +**Chat prompts**: +- `"market sentiment"` +- `"sentiment analysis"` +- `"social media sentiment"` +- `"twitter sentiment"` + +#### **16. Scenario Analysis** (`getScenarioAnalysis`) +```bash +npm run example:scenario-analysis +``` +**Chat prompts**: +- `"price prediction"` +- `"scenario analysis"` +- `"bitcoin price scenarios"` +- `"price forecast"` + +#### **17. Correlation Analysis** (`getCorrelation`) +```bash +npm run example:correlation +``` +**Chat prompts**: +- `"correlations"` +- `"portfolio correlations"` +- `"diversification analysis"` +- `"token relationships"` + +--- + +## ๐ŸŽฏ **Quick Test Sequence** + +### **Method 1: Individual Examples (All 17)** +```bash +# Core data endpoints +npm run example:tokens +npm run example:top-market-cap +npm run example:price-data +npm run example:trader-grades +npm run example:investor-grades +npm run example:market-metrics +npm run example:quantmetrics +npm run example:hourly-ohlcv +npm run example:daily-ohlcv +npm run example:ai-reports +npm run example:crypto-investors +npm run example:resistance-support +npm run example:trading-signals + +# New advanced endpoints +npm run example:tokenmetrics-ai +npm run example:sentiments +npm run example:scenario-analysis +npm run example:correlation +``` + +### **Method 2: Interactive Chat Interface** +```bash +npm run chat +``` + +**Then test these prompts in order:** + +1. **Basic AI Chat**: `"What is Bitcoin?"` +2. **Trading Signals**: `"trading signals"` +3. **Sentiment**: `"market sentiment"` +4. **Price Prediction**: `"bitcoin price scenarios"` +5. **Correlations**: `"portfolio correlations"` +6. **Market Overview**: `"market overview"` + +### **Method 3: Automated Test Suite** +```bash +# Run all tests +npm run test:all + +# Individual test suites +npm run test:individual +npm run test:integration +``` + +### **Method 4: Demo Scenarios** +```bash +# Trading bot simulation +npm run demo:trading-bot + +# Research agent simulation +npm run demo:research-agent + +# New endpoints showcase +npm run demo:new-endpoints +``` + +--- + +## ๐Ÿ“Š **Expected Results for Each Endpoint** + +### **Tokens List**: +- List of 50+ cryptocurrencies +- TOKEN_ID, name, symbol, category +- Used to find IDs for other calls + +### **Top Market Cap**: +- Top 10-100 tokens by market cap +- Current rankings and values +- Market leaders identification + +### **Price Data**: +- Real-time prices for specified tokens +- Current market values +- Price change indicators + +### **Trader/Investor Grades**: +- AI scores (0-100) +- Buy/sell/hold recommendations +- Risk assessments + +### **Trading Signals**: +- Buy/sell signals with strength +- AI-generated recommendations +- Signal confidence levels + +### **Sentiment Analysis**: +- Twitter, Reddit, News sentiment +- Sentiment scores and summaries +- Social media insights + +### **Scenario Analysis**: +- Multiple timeframe predictions +- Bear/base/bull scenarios +- ROI calculations and insights + +### **Correlations**: +- Token relationship analysis +- Portfolio diversification insights +- Risk management data + +--- + +## โœ… **Success Indicators** + +For each test, look for: +- โœ… **No errors** in API calls +- โœ… **Real-time data** with current timestamps +- โœ… **Proper formatting** (tables, charts, colors) +- โœ… **Correct endpoint URLs** in logs +- โœ… **Rate limiting** working (no 429 errors) +- โœ… **Data completeness** (all expected fields) + +--- + +## ๐Ÿš€ **Quick Start Testing** + +**Start here for fastest comprehensive test:** + +```bash +# 1. Test basic functionality +npm run example:tokens + +# 2. Test price data +npm run example:price-data + +# 3. Test trading features +npm run example:trading-signals + +# 4. Test new advanced features +npm run chat +# Then type: "trading signals" +# Then type: "market sentiment" +# Then type: "bitcoin price scenarios" +``` + +This will verify all core functionality is working correctly! diff --git a/plugins/tokenMetricsPlugin/DEVELOPER_INTEGRATION_GUIDE.md b/plugins/tokenMetricsPlugin/DEVELOPER_INTEGRATION_GUIDE.md new file mode 100644 index 00000000..bee71e18 --- /dev/null +++ b/plugins/tokenMetricsPlugin/DEVELOPER_INTEGRATION_GUIDE.md @@ -0,0 +1,253 @@ +# ๐Ÿš€ Developer Integration Guide + +## Quick Integration Test โœ… + +**VERIFIED**: Developers can successfully integrate this plugin into their AI agents in **5 simple steps**! + +--- + +## ๐Ÿ“‹ Integration Steps + +### 1๏ธโƒฃ **Install the Plugin** + +```bash +npm install tokenmetrics-virtuals-plugin +``` + +### 2๏ธโƒฃ **Set Up Environment Variables** + +```bash +# Copy the example environment file +cp env.example .env + +# Edit .env with your API keys: +TOKENMETRICS_API_KEY=tm-your-tokenmetrics-api-key-here +GAME_API_KEY=your-game-api-key-here +``` + +### 3๏ธโƒฃ **Import and Initialize** + +```typescript +import { config } from "dotenv"; +config({ path: "./.env" }); +import TokenMetricsPlugin from "tokenmetrics-virtuals-plugin"; +import { GameAgent } from "@virtuals-protocol/game"; + +// Initialize the plugin +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); +``` + +### 4๏ธโƒฃ **Create Your AI Agent** + +```typescript +// Create AI agent with Token Metrics capabilities +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "๐Ÿš€ Crypto Analysis Agent", + goal: "Provide comprehensive cryptocurrency market analysis and trading insights", + description: "You are an AI agent specialized in cryptocurrency analysis. Use Token Metrics API to help users make informed trading decisions! ๐Ÿ“Š", + workers: [tokenMetricsPlugin.getWorker({})], // Include ALL 17 functions +}); +``` + +### 5๏ธโƒฃ **Initialize and Use** + +```typescript +(async () => { + // Optional: Set up logging + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– [${agent.name}] ${message}`); + }); + + await agent.init(); + + // Your agent now has 17 TokenMetrics functions! + const result = await agent.run("Show me the available tokens"); + console.log(result); +})(); +``` + +--- + +## ๐ŸŽ›๏ธ Advanced Integration Options + +### **Custom Function Selection** + +```typescript +// Use only specific functions +const customWorker = tokenMetricsPlugin.getWorker({ + functions: [ + tokenMetricsPlugin.getTokens, + tokenMetricsPlugin.getPriceData, + tokenMetricsPlugin.getTradingSignals, + ], +}); + +const focusedAgent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "Trading Signals Agent", + goal: "Provide focused trading signals and price data", + description: "Specialized agent for trading signals", + workers: [customWorker], +}); +``` + +### **Custom Configuration** + +```typescript +// Advanced plugin configuration +const advancedPlugin = new TokenMetricsPlugin({ + id: "my_custom_tokenmetrics", + name: "My Custom Token Metrics Worker", + description: "Custom TokenMetrics integration for my specific use case", + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + baseApiUrl: "https://api.tokenmetrics.com/v2", // Optional custom endpoint + }, +}); +``` + +### **Environment Configuration** + +```typescript +// Add custom environment variables +const workerWithEnv = tokenMetricsPlugin.getWorker({ + getEnvironment: async () => ({ + customSetting: "value", + userPreferences: "advanced_analysis", + riskTolerance: "moderate", + }), +}); +``` + +--- + +## ๐ŸŽฏ What Your Agent Can Do + +Once integrated, your AI agent will have access to **17 powerful functions**: + +### **๐Ÿ“Š Core Data Functions** +- `getTokens` - Get supported cryptocurrencies and TOKEN_IDs +- `getTopMarketCapTokens` - Retrieve top cryptos by market cap +- `getPriceData` - Get current market prices + +### **๐Ÿ“ˆ Trading & Investment Analysis** +- `getTraderGrades` - AI-powered trader performance grades +- `getInvestorGrades` - Investor performance analysis +- `getTradingSignals` - Buy/sell/hold recommendations +- `getMarketMetrics` - Comprehensive market analysis + +### **๐Ÿ”ฌ Advanced Analytics** +- `getQuantmetrics` - Quantitative trading metrics +- `getHourlyOhlcv` - Hourly OHLC data with volume +- `getDailyOhlcv` - Daily OHLC with technical indicators + +### **๐Ÿค– AI & Research** +- `getAiReports` - AI-generated market reports +- `getTokenMetricsAi` - Custom AI analysis queries +- `getSentiments` - Market sentiment analysis +- `getCryptoInvestors` - Crypto investor performance + +### **๐Ÿ“ˆ Technical Analysis** +- `getResistanceSupport` - Resistance and support levels +- `getScenarioAnalysis` - Scenario-based projections +- `getCorrelation` - Crypto correlation analysis + +--- + +## ๐Ÿ’ฌ Example Queries Your Agent Can Handle + +Once integrated, users can ask your agent: + +- ๐Ÿ’ฐ *"What's the price of Bitcoin?"* +- ๐Ÿ“Š *"Show me trading signals for the top 10 cryptocurrencies"* +- ๐Ÿ† *"Get trader grades for Ethereum"* +- ๐Ÿ˜Š *"Analyze market sentiment for DeFi tokens"* +- ๐Ÿ“ *"Generate an AI report for Solana"* +- ๐Ÿ”— *"Show me correlation analysis between Bitcoin and altcoins"* +- ๐ŸŽฏ *"What are the resistance and support levels for BTC?"* +- ๐Ÿ”ฎ *"Give me scenario analysis for the next bull market"* + +--- + +## ๐Ÿงช Testing Your Integration + +### **Quick Test** +```bash +npm run test:setup +``` + +### **Test Individual Functions** +```bash +npm run example:tokens +npm run example:price-data +npm run example:trading-signals +``` + +### **Interactive Testing** +```bash +npm run chat +``` + +--- + +## ๐Ÿ›ก๏ธ Built-in Features + +Your integration automatically includes: + +- โœ… **Error Handling**: Graceful API error management +- โœ… **Rate Limiting**: Automatic request throttling +- โœ… **Retry Logic**: Exponential backoff for failed requests +- โœ… **Input Validation**: Parameter validation for all functions +- โœ… **TypeScript Support**: Full type safety and IntelliSense +- โœ… **Logging**: Comprehensive request/response logging + +--- + +## ๐ŸŽ‰ Integration Success Checklist + +- [ ] Plugin installed via npm +- [ ] Environment variables configured +- [ ] Plugin imported and initialized +- [ ] GameAgent created with plugin worker +- [ ] Agent initialized successfully +- [ ] All 17 functions available +- [ ] Agent responds to crypto queries + +**โœ… If all boxes are checked, your integration is complete!** + +--- + +## ๐Ÿ†˜ Troubleshooting + +### **Common Issues:** + +1. **Missing API Keys** + ```bash + # Ensure .env file has both keys: + TOKENMETRICS_API_KEY=tm-your-key-here + GAME_API_KEY=your-game-key-here + ``` + +2. **Import Errors** + ```typescript + // Use correct import syntax: + import TokenMetricsPlugin from "tokenmetrics-virtuals-plugin"; + ``` + +3. **Agent Initialization** + ```typescript + // Always await agent.init(): + await agent.init(); + ``` + +### **Need Help?** +- ๐Ÿ“š Check the comprehensive examples in `/src/examples/` +- ๐Ÿงช Run the test suite: `npm run test:all` +- ๐Ÿ’ฌ Try the interactive chat: `npm run chat` + +--- + +**๐Ÿš€ Your AI agent is now powered by Token Metrics! Happy coding!** diff --git a/plugins/tokenMetricsPlugin/LICENSE b/plugins/tokenMetricsPlugin/LICENSE new file mode 100644 index 00000000..a2fecb88 --- /dev/null +++ b/plugins/tokenMetricsPlugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Token Metrics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/tokenMetricsPlugin/README.md b/plugins/tokenMetricsPlugin/README.md new file mode 100644 index 00000000..0acef570 --- /dev/null +++ b/plugins/tokenMetricsPlugin/README.md @@ -0,0 +1,1039 @@ +# ๐Ÿš€ Token Metrics Plugin for Virtuals Protocol + +> ๐ŸŽฏ **Supercharge your Virtuals agents with AI-powered cryptocurrency analysis!** + +The Token Metrics plugin seamlessly empowers Virtuals Protocol agents with comprehensive cryptocurrency analysis capabilities using the Token Metrics API, enabling the retrieval of AI-powered market data, trading signals, and investment insights for intelligent trading and investment decisions. + +--- + +## ๐Ÿš€ Quick Start for Developers + +### 1. Installation +```bash +npm install @tokenmetrics/tokenmetrics-virtuals-plugin +``` + +### 2. Environment Setup +Create a `.env` file with your API keys: +```env +# Virtuals Protocol API Key (required) +GAME_API_KEY=your-virtuals-api-key-here + +# TokenMetrics API Key (required) +TOKENMETRICS_API_KEY=your-tokenmetrics-api-key-here +``` + +### 3. Basic Agent Integration +```typescript +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "@tokenmetrics/tokenmetrics-virtuals-plugin"; + +// Initialize the plugin +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create your Virtuals agent +const agent = new GameAgent(process.env.GAME_API_KEY!, { + name: "Crypto Analysis Agent", + goal: "Provide intelligent cryptocurrency analysis using TokenMetrics data", + description: "AI agent specialized in crypto market analysis and trading insights", + workers: [tokenMetricsPlugin.getWorker()], // All 21 functions available +}); + +// Start the agent +await agent.init(); +await agent.step({ verbose: true }); +``` + +### 4. Natural Language Interaction +Your agent can now understand and respond to queries like: +- "What are the next moonshot tokens?" +- "Show me the TM grade for Bitcoin" +- "Get fundamental analysis for Ethereum" +- "What's the current market sentiment?" +- "Find me trading signals for top cryptocurrencies" + +--- + +## โœจ Features + +- ๐Ÿ“Š **Comprehensive Token Data**: Access to 21 Token Metrics API endpoints +- ๐Ÿค– **AI-Powered Analysis**: Get AI reports, sentiment analysis, and market insights +- ๐Ÿ“ˆ **Trading Intelligence**: Retrieve trader grades, investor grades, and trading signals +- ๐Ÿ“‰ **Market Analytics**: Access quantmetrics, OHLCV data, and correlation analysis +- ๐Ÿ“Š **Crypto Indices**: Track crypto indices, holdings, and performance data +- โšก **Real-time Data**: Current prices, market metrics, and resistance/support levels +- ๐Ÿ’ฌ **Interactive Chat Interface**: Built-in chat system for testing and exploration +- ๐Ÿ›ก๏ธ **Robust Error Handling**: Built-in error handling and rate limiting + +--- + +## ๐Ÿ“š Examples + +### ๐ŸŒ™ Moonshot Tokens Analysis +```typescript +// Create a moonshot discovery agent +const moonshotAgent = new GameAgent(process.env.GAME_API_KEY!, { + name: "Moonshot Discovery Agent", + goal: "Identify high-potential cryptocurrency moonshots", + description: "AI agent specialized in finding next 100x tokens", + workers: [tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getMoonshotTokens] + })], +}); + +// Natural language query +await moonshotAgent.step({ + input: "What are the next moonshot tokens with high breakout potential?" +}); +``` + +### ๐Ÿ“Š Comprehensive Market Analysis +```typescript +// Create a full market analysis agent +const marketAgent = new GameAgent(process.env.GAME_API_KEY!, { + name: "Market Analysis Agent", + goal: "Provide comprehensive crypto market analysis", + description: "AI agent for complete market intelligence", + workers: [tokenMetricsPlugin.getWorker()], // All functions +}); + +// Multi-function analysis +await marketAgent.step({ + input: "Analyze Bitcoin's TM grade, fundamental strength, and current market sentiment" +}); +``` + +### ๐ŸŽฏ Trading Signal Agent +```typescript +// Create a trading signals agent +const tradingAgent = new GameAgent(process.env.GAME_API_KEY!, { + name: "Trading Signals Agent", + goal: "Provide AI-powered trading recommendations", + description: "AI agent for trading signals and market timing", + workers: [tokenMetricsPlugin.getWorker({ + functions: [ + tokenMetricsPlugin.getTradingSignals, + tokenMetricsPlugin.getHourlyTradingSignals, + tokenMetricsPlugin.getMarketMetrics + ] + })], +}); + +// Get trading insights +await tradingAgent.step({ + input: "Show me the latest trading signals for top cryptocurrencies" +}); +``` + +--- + +## ๐Ÿ”ง Available Functions + +### ๐Ÿ“‹ Core Data Functions +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getTokens` | Get supported cryptocurrencies and TOKEN_IDs | ๐Ÿช™ Token Discovery | +| `getTopMarketCapTokens` | Retrieve top cryptos by market cap | ๐Ÿ‘‘ Market Leaders | +| `getPriceData` | Get current market prices | ๐Ÿ’ฐ Price Tracking | + +### ๐Ÿš€ Moonshot & Grade Analysis +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getMoonshotTokens` | AI-curated high-potential token picks | ๐ŸŒ™ Moonshot Discovery | +| `getTechnologyGrade` | Technology quality and development scores | ๐Ÿ”ง Tech Analysis | +| `getTmGrade` | Comprehensive TM grade with signals & momentum | ๐Ÿ“Š Grade Analysis | +| `getFundamentalGrade` | Fundamental strength and tokenomics analysis | ๐Ÿ’Ž Fundamental Analysis | + +### ๐Ÿ“Š Trading & Investment Analysis +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getTradingSignals` | Buy/sell/hold recommendations | ๐Ÿ“ก Trading Signals | +| `getHourlyTradingSignals` | Hourly AI trading signals with confidence | โฐ Real-time Signals | +| `getMarketMetrics` | Comprehensive market analysis | ๐Ÿ“Š Market Overview | + +### ๐Ÿ”ฌ Advanced Analytics +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getQuantmetrics` | Quantitative trading metrics | ๐Ÿงฎ Quant Analysis | +| `getHourlyOhlcv` | Hourly OHLC data with volume | โฐ Short-term Data | +| `getDailyOhlcv` | Daily OHLC with technical indicators | ๐Ÿ“… Long-term Data | + +### ๐Ÿค– AI & Research +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getAiReports` | AI-generated market reports | ๐Ÿ“ Research Reports | +| `getTokenMetricsAi` | Custom AI analysis queries | ๐Ÿง  Custom Analysis | +| `getSentiments` | Market sentiment analysis | ๐Ÿ˜Š Sentiment Tracking | +| `getCryptoInvestors` | Crypto investor performance | ๐Ÿ’ผ Investor Data | + +### ๐Ÿ“ˆ Technical Analysis +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getResistanceSupport` | Resistance and support levels | ๐ŸŽฏ Key Levels | +| `getScenarioAnalysis` | Scenario-based projections | ๐Ÿ”ฎ Future Scenarios | +| `getCorrelation` | Crypto correlation analysis | ๐Ÿ”— Relationship Analysis | + +### ๐Ÿ“Š Crypto Indices (NEW!) +| Function | Description | ๐ŸŽฏ Purpose | +|----------|-------------|-----------| +| `getIndices` | Get crypto indices with performance data | ๐Ÿ“Š Index Overview | +| `getIndicesHoldings` | Get index portfolio composition and weights | ๐Ÿฆ Portfolio Analysis | +| `getIndicesPerformance` | Get historical index performance and ROI | ๐Ÿ“ˆ Performance Tracking | + +--- + +## ๐Ÿš€ Quick Start + +### ๐Ÿ“ฆ Installation + +```bash +# Using npm +npm install tokenmetrics-virtuals-plugin + +# Using yarn +yarn add tokenmetrics-virtuals-plugin +``` + +### ๐Ÿ”‘ Environment Setup + +Set up your environment variables: + +```bash +# Copy the example environment file +cp env.example .env + +# Edit the .env file with your API keys +export TOKENMETRICS_API_KEY="your-tokenmetrics-api-key" +export GAME_API_KEY="your-game-api-key" +``` + +**Required Environment Variables:** +- `TOKENMETRICS_API_KEY`: Your Token Metrics API key (get one from [Token Metrics](https://tokenmetrics.com/api)) +- `GAME_API_KEY`: Your Virtuals Protocol GAME API key + +> ๐Ÿ’ก **How to Get API Keys:** +> +> **๐Ÿ”‘ Token Metrics API Key:** +> 1. Visit [Token Metrics.com](https://tokenmetrics.com/api) +> 2. Sign up for an account or log in +> 3. Navigate to API section in your dashboard +> 4. Generate your API key (starts with `tm-`) +> +> **๐ŸŽฎ GAME API Key:** +> 1. Visit [Virtuals Protocol](https://virtuals.io) +> 2. Create an account and access the developer portal +> 3. Generate your GAME framework API key +> +> **๐Ÿ“ง Need Help?** Contact Token Metrics support for API access assistance. + +### โœ… Installation Verification + +Verify your installation works correctly: + +```bash +# Test the plugin setup +npm run test:setup + +# Run a quick example +npm run example:tokens +``` + +### ๐Ÿ’ป Basic Usage + +```typescript +import { config } from "dotenv"; +config({ path: "./.env" }); +import TokenMetricsPlugin from "tokenmetrics-virtuals-plugin"; +import { GameAgent } from "@virtuals-protocol/game"; + +// ๐Ÿ”ง Initialize the plugin +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// ๐Ÿค– Create your crypto analysis agent +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "๐Ÿš€ Crypto Analysis Agent", + goal: "Provide comprehensive cryptocurrency market analysis and trading insights", + description: "You are an AI agent specialized in cryptocurrency analysis. Use Token Metrics API to help users make informed trading decisions! ๐Ÿ“Š", + workers: [tokenMetricsPlugin.getWorker({})], // Include ALL 21 functions +}); + +// ๐ŸŽฏ Run your agent +(async () => { + // ๐Ÿ“ Optional: Set up logging + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– [${agent.name}] ๐Ÿ“ข`); + console.log(message); + }); + + await agent.init(); + + // ๐Ÿš€ Example: Get token list + const result = await agent.run("Show me the available tokens"); + console.log(result); +})(); +``` + +### โš™๏ธ Custom Configuration + +```typescript +// ๐ŸŽ›๏ธ Customize your worker +const customWorker = tokenMetricsPlugin.getWorker({ + functions: [ + tokenMetricsPlugin.getTokens, + tokenMetricsPlugin.getPriceData, + tokenMetricsPlugin.getTradingSignals, + tokenMetricsPlugin.getIndices, + tokenMetricsPlugin.getIndicesHoldings, + ], + getEnvironment: async () => ({ + customSetting: "value", + }), +}); +``` + +### ๐Ÿ“‹ Configuration Options + +```typescript +interface ITokenMetricsPluginOptions { + id?: string; // ๐Ÿ†” Custom worker ID + name?: string; // ๐Ÿ“› Custom worker name + description?: string; // ๐Ÿ“ Custom worker description + apiClientConfig: { + apiKey: string; // ๐Ÿ”‘ Token Metrics API key + baseApiUrl?: string; // ๐ŸŒ Custom API endpoint (optional) + }; +} +``` + +--- + +## ๐Ÿ› ๏ธ Development + +### ๐Ÿ—๏ธ Setup + +```bash +# 1๏ธโƒฃ Clone the repository +git clone + +# 2๏ธโƒฃ Install dependencies +npm install + +# 3๏ธโƒฃ Set up environment variables +cp env.example .env +# Edit .env file with your API keys: +# TOKENMETRICS_API_KEY=tm-your-tokenmetrics-api-key-here +# GAME_API_KEY=your-game-api-key-here + +# 4๏ธโƒฃ Build the plugin +npm run build + +# 5๏ธโƒฃ Run the example +npm run example:full-agent +# or +npm run dev +``` + +--- + +## ๐Ÿ’ฌ Interactive Chat Interface + +Launch the interactive terminal for testing: + +```bash +npm run chat +``` + +### ๐ŸŽฎ Example Chat Commands + +- ๐Ÿ’ฐ `"What's the price of Bitcoin?"` +- ๐Ÿ“Š `"Show me trading signals"` +- โฐ `"Show me hourly trading signals"` (NEW!) +- ๐Ÿ† `"Get trader grades for top tokens"` +- ๐Ÿ˜Š `"Analyze market sentiment"` +- ๐Ÿ“ `"Show me AI reports"` +- ๐Ÿ”— `"Get correlation analysis"` +- ๐Ÿ“Š `"Show me crypto indices data"` (NEW!) +- ๐Ÿฆ `"What are the holdings of crypto index 1?"` (NEW!) +- ๐Ÿ“ˆ `"Show me performance data for crypto index 1"` (NEW!) + +--- + +## ๐ŸŽฏ Examples & Testing + +### ๐Ÿ“Š Basic Token Data +```bash +npm run example:tokens # ๐Ÿช™ Get token list +npm run example:price-data # ๐Ÿ’ฐ Get price data +npm run example:top-market-cap # ๐Ÿ‘‘ Get top market cap tokens +``` + +### ๐Ÿ“ˆ Trading Analysis +```bash +npm run example:trader-grades # ๐Ÿ† Get trader performance grades +npm run example:investor-grades # ๐ŸŽฏ Get investor analysis +npm run example:trading-signals # ๐Ÿ“ก Get trading recommendations +npm run example:hourly-trading-signals # โฐ Get hourly AI trading signals (NEW!) +``` + +### ๐Ÿ“‰ Market Analytics +```bash +npm run example:market-metrics # ๐Ÿ“Š Get market analysis +npm run example:quantmetrics # ๐Ÿงฎ Get quantitative metrics +npm run example:hourly-ohlcv # โฐ Get hourly price data +npm run example:daily-ohlcv # ๐Ÿ“… Get daily price data +``` + +### ๐Ÿค– AI & Research +```bash +npm run example:ai-reports # ๐Ÿ“ Get AI-generated reports +npm run example:tokenmetrics-ai # ๐Ÿง  Query Token Metrics AI +npm run example:sentiments # ๐Ÿ˜Š Get sentiment analysis +npm run example:crypto-investors # ๐Ÿ’ผ Get crypto investor data +``` + +### ๐Ÿ“ˆ Technical Analysis +```bash +npm run example:resistance-support # ๐ŸŽฏ Get support/resistance levels +npm run example:scenario-analysis # ๐Ÿ”ฎ Get scenario projections +npm run example:correlation # ๐Ÿ”— Get correlation analysis +``` + +### ๐Ÿ“Š Crypto Indices (NEW!) +```bash +npm run example:indices # ๐Ÿ“Š Get crypto indices overview +npm run example:indices-holdings # ๐Ÿฆ Get index portfolio composition +npm run example:indices-performance # ๐Ÿ“ˆ Get historical index performance +``` + +### ๐Ÿงช Testing Suite + +```bash +npm run test:all # ๐Ÿงช Run all tests +npm run test:individual # ๐Ÿ” Test individual functions +npm run test:integration # ๐Ÿ”„ Test integration scenarios +``` + +### ๐ŸŽฎ Demo Scenarios + +```bash +npm run demo:trading-bot # ๐Ÿค– Trading bot simulation +npm run demo:research-agent # ๐Ÿ”ฌ Research agent demo +npm run demo:new-endpoints # โœจ New endpoints demonstration +npm run demo:indices # ๐Ÿ“Š Crypto indices demo (NEW!) +``` + +--- + +## ๐Ÿ“š API Reference + +### ๐Ÿ”ง Core Functions + +#### ๐Ÿช™ getTokens(args) +Get the list of supported cryptocurrencies. + +**Parameters:** +- `limit` (number): Number of items to return (default: 50) +- `page` (number): Page number for pagination (default: 1) +- `token_name` (string): Comma-separated crypto asset names +- `symbol` (string): Comma-separated token symbols +- `category` (string): Comma-separated category names + +#### ๐Ÿ’ฐ getPriceData(args) +Get current market prices for specified tokens. + +**Parameters:** +- `token_id` (string): Comma-separated Token IDs (required) + +#### ๐Ÿ“ก getTradingSignals(args) +Get AI-powered trading recommendations. + +**Parameters:** +- `token_id` (string): Comma-separated Token IDs +- `limit` (number): Number of signals to return +- `page` (number): Page number for pagination + +### ๐Ÿ”ฌ Advanced Functions + +#### ๐Ÿงฎ getQuantmetrics(args) +Get comprehensive quantitative trading metrics. + +**Parameters:** +- `token_id` (string): Comma-separated Token IDs +- `limit` (number): Number of results to return +- `page` (number): Page number for pagination + +#### ๐Ÿ“ getAiReports(args) +Get AI-generated comprehensive market reports. + +**Parameters:** +- `token_id` (string): Comma-separated Token IDs +- `limit` (number): Number of reports to return +- `page` (number): Page number for pagination + +### ๐Ÿ“Š Crypto Indices Functions (NEW!) + +#### ๐Ÿ“Š getIndices(args) +Get crypto indices with performance data. + +**Parameters:** +- `limit` (number): Number of indices to return (default: 50) +- `page` (number): Page number for pagination (default: 1) + +#### ๐Ÿฆ getIndicesHoldings(args) +Get current holdings of a given index with weights. + +**Parameters:** +- `id` (string): Index ID (required) +- `limit` (number): Number of holdings to return (default: 50) +- `page` (number): Page number for pagination (default: 1) + +#### ๐Ÿ“ˆ getIndicesPerformance(args) +Get historical performance data for an index with ROI over time. + +**Parameters:** +- `id` (string): Index ID (required) +- `limit` (number): Number of performance records to return (default: 50) +- `page` (number): Page number for pagination (default: 1) + +> ๐Ÿ“– **Complete Documentation**: [Token Metrics API Documentation](https://developers.tokenmetrics.com/) + +--- + +## ๐Ÿ›ก๏ธ Error Handling + +The plugin includes comprehensive error handling: + +- โฑ๏ธ **Rate Limiting**: Automatic rate limiting with configurable delays +- ๐Ÿ”„ **Retry Logic**: Exponential backoff for failed requests +- โœ… **Validation**: Input validation for all parameters +- ๐ŸŽฏ **Graceful Degradation**: Fallback responses for API failures + +--- + +## ๐Ÿ”ง Troubleshooting + +### **๐Ÿšจ Common Issues & Solutions** + +#### **1. API Key Issues** +```bash +# โŒ Error: "Invalid API key" or "Unauthorized" +# โœ… Solution: +# 1. Verify your Token Metrics API key is correct +# 2. Check that your API key starts with 'tm-' +# 3. Ensure no extra spaces in your .env file +export TOKENMETRICS_API_KEY="tm-your-actual-key-here" +``` + +#### **2. Environment Variables Not Loading** +```bash +# โŒ Error: "API key is undefined" +# โœ… Solution: +# 1. Ensure .env file is in the correct location +# 2. Check that dotenv is properly configured +import { config } from "dotenv"; +config({ path: "./.env" }); // Must be before other imports +``` + +#### **3. Network/Connection Issues** +```bash +# โŒ Error: "Network timeout" or "Connection refused" +# โœ… Solutions: +# 1. Check your internet connection +# 2. Verify Token Metrics API is accessible +curl -H "x-api-key: your-key" https://api.tokenmetrics.com/v2/tokens + +# 3. Check for firewall/proxy issues +# 4. Try with a different network +``` + +#### **4. Rate Limiting** +```bash +# โŒ Error: "Rate limit exceeded" or "Too many requests" +# โœ… Solutions: +# 1. Reduce request frequency +# 2. Implement delays between calls +# 3. Use pagination with smaller limits +# 4. Contact Token Metrics for higher rate limits +``` + +#### **5. Invalid Token IDs** +```bash +# โŒ Error: "Token not found" or "Invalid token_id" +# โœ… Solution: +# 1. First get valid token IDs: +npm run example:tokens + +# 2. Use the TOKEN_ID from the response (e.g., 3375 for BTC) +# 3. Ensure comma-separated format: "3375,3306" +``` + +#### **6. Build/TypeScript Issues** +```bash +# โŒ Error: TypeScript compilation errors +# โœ… Solutions: +# 1. Ensure TypeScript is installed +npm install -g typescript + +# 2. Check Node.js version (requires 16+) +node --version + +# 3. Clear and rebuild +rm -rf dist/ node_modules/ +npm install +npm run build +``` + +#### **7. GAME Framework Integration Issues** +```bash +# โŒ Error: "GameAgent not found" or GAME-related errors +# โœ… Solutions: +# 1. Verify GAME API key is set +export GAME_API_KEY="your-game-api-key" + +# 2. Check @virtuals-protocol/game version +npm list @virtuals-protocol/game + +# 3. Ensure proper initialization order +await agent.init(); // Must be called before agent.run() +``` + +### **๐Ÿ” Debug Mode** + +Enable detailed logging for troubleshooting: + +```typescript +// Enable debug logging +const agent = new GameAgent(process.env.GAME_API_KEY!, { + // ... config +}); + +agent.setLogger((agent, message) => { + console.log(`๐Ÿ› [DEBUG] ${agent.name}:`); + console.log(message); +}); +``` + +### **๐Ÿ“Š Testing Your Setup** + +Run these commands to verify everything works: + +```bash +# 1. Test environment setup +npm run test:setup + +# 2. Test individual functions +npm run test:individual + +# 3. Test integration +npm run test:integration + +# 4. Interactive testing +npm run chat +``` + +### **๐Ÿ†˜ Getting Help** + +If you're still having issues: + +1. **๐Ÿ“‹ Check Examples**: Review our 22+ example files +2. **๐Ÿงช Run Tests**: Use our comprehensive test suite +3. **๐Ÿ’ฌ Interactive Mode**: Try `npm run chat` for hands-on testing +4. **๐Ÿ“– Documentation**: Check [Token Metrics API Docs](https://developers.tokenmetrics.com/) +5. **๐Ÿ› Report Issues**: Create a GitHub issue with: + - Error message + - Your environment (Node.js version, OS) + - Steps to reproduce + - Relevant code snippets + +### **โšก Performance Tips** + +- **Batch Requests**: Use comma-separated token IDs instead of multiple calls +- **Pagination**: Use appropriate `limit` and `page` parameters +- **Caching**: Cache responses for frequently requested data +- **Error Handling**: Always wrap API calls in try-catch blocks + +--- + +## ๐Ÿงช Dev Testing + +### **๐Ÿ“ธ Screenshots & Visual Demonstrations** + +#### **๐Ÿš€ Plugin Installation & Setup** +*Visual demonstration of successful plugin installation and initial setup process* + +![Plugin Installation Setup - Step 1](screenshots/Setup-1.png) +> **Initial Setup**: Shows the plugin installation process, dependency setup, and environment configuration. + +![Plugin Installation Setup - Step 2](screenshots/Setup-2.png) +> **Setup Verification**: Complete setup verification showing successful build, test execution, and plugin readiness confirmation. + +#### **๐Ÿ’ฌ Interactive Chat Interface** +*Screenshots of the interactive terminal chat interface in action* + +![Interactive Chat Interface - Main](screenshots/Chat-1.png) +> **Chat Interface**: The main Token Metrics AI chat interface showing the welcome screen, available commands, and user interaction prompts. + +![Interactive Chat Interface - Responses](screenshots/Chat-2.png) +> **AI Responses**: Example of comprehensive AI responses with color-coded output, data visualization, and detailed analysis results from Token Metrics API. + +#### **๐Ÿ“Š Crypto Market Sentiment Analysis** +*Visual examples of sentiment analysis output and market data visualization* + +![Market Sentiment Analysis](screenshots/Market%20Sentiment%20Example.png) +> **Sentiment Dashboard**: Real-time market sentiment analysis with color-coded indicators, sentiment scores, confidence levels, and comprehensive market mood visualization. + +#### **๐Ÿฆ Quantitative Metrics Analysis** +*Advanced quantitative analysis visualization and performance metrics* + +![Quantitative Metrics Dashboard](screenshots/Quantmetics%20Example.png) +> **Quant Analysis**: Advanced quantitative metrics including Sharpe ratios, volatility analysis, drawdown calculations, risk-adjusted returns with visual progress bars, and color-coded performance indicators. + +### **๐Ÿงช Testing Steps** + +#### **1. Basic Setup Testing** +```bash +npm install tokenmetrics-virtuals-plugin +npm run test:setup +# Expected: All checks pass โœ… +``` + +#### **2. Function Testing** +```bash +npm run test:individual +# Expected: All 21 functions execute successfully +``` + +#### **3. Integration Testing** +```bash +npm run test:integration +# Expected: GAME framework integration works +``` + +#### **4. Interactive Testing** +```bash +npm run chat +# Test various queries and verify responses +``` + +### **๐Ÿ“Š Performance Benchmarks** + +``` +Function Performance (Average Response Time): +โ€ข getTokens(): ~245ms +โ€ข getPriceData(): ~180ms +โ€ข getTradingSignals(): ~320ms +โ€ข getTokenMetricsAi(): ~850ms +โ€ข getIndices(): ~210ms + +Memory Usage: ~45MB (typical) +Rate Limiting: 60 requests/minute (within limits) +``` + +### **๐Ÿ› Error Handling Examples** + +#### **Invalid API Key** +```bash +โŒ Invalid API key provided +๐Ÿ”ง Solution: Verify API key format and permissions +``` + +#### **Network Issues** +```bash +โŒ Network timeout +๐Ÿ”„ Automatic retry with exponential backoff +โœ… Connection restored +``` + +### **๐Ÿ–ฅ๏ธ Cross-Platform Testing** + +- โœ… **macOS** (Intel & Apple Silicon) +- โœ… **Linux** (Ubuntu 20.04+) +- โœ… **Windows** (10/11) +- โœ… **Node.js** versions: 16.x, 18.x, 20.x + +--- + +## ๐Ÿค Contributing + +We welcome contributions to the Token Metrics Virtuals Plugin! Here's how to contribute effectively: + +### **๐Ÿš€ Quick Start for Contributors** + +1. **๐Ÿด Fork & Clone** + ```bash + git clone https://github.com/your-username/tokenmetrics-virtuals-plugin.git + cd tokenmetrics-virtuals-plugin + ``` + +2. **๐Ÿ“ฆ Install Dependencies** + ```bash + npm install + ``` + +3. **๐Ÿ”‘ Setup Environment** + ```bash + cp env.example .env + # Add your API keys to .env file + ``` + +4. **โœ… Verify Setup** + ```bash + npm run test:setup + npm run build + ``` + +### **๐ŸŽฏ Contribution Types** + +#### **๐Ÿ”ง Bug Fixes** +- Fix API integration issues +- Resolve TypeScript compilation errors +- Improve error handling +- Fix documentation inconsistencies + +#### **โœจ New Features** +- Add new Token Metrics API endpoints +- Enhance response formatting +- Improve chat interface functionality +- Add new testing scenarios + +#### **๐Ÿ“š Documentation** +- Improve README sections +- Add more usage examples +- Enhance API documentation +- Create tutorial content + +#### **๐Ÿงช Testing** +- Add test cases for new functions +- Improve test coverage +- Create integration test scenarios +- Add performance benchmarks + +### **๐Ÿ“‹ Development Guidelines** + +#### **๐Ÿ—๏ธ Code Standards** +- **TypeScript**: All code must be properly typed +- **ESLint**: Follow existing linting rules +- **Formatting**: Use consistent code formatting +- **Comments**: Document complex logic and API integrations + +#### **๐ŸŽฏ Plugin-Specific Requirements** +- **GAME Integration**: All new functions must use `GameFunction` pattern +- **Error Handling**: Implement proper `ExecutableGameFunctionResponse` handling +- **API Consistency**: Follow existing Token Metrics API patterns +- **Response Formatting**: Use color-coded console output for user-friendly responses + +#### **๐Ÿงช Testing Requirements** +- **Unit Tests**: Test individual functions +- **Integration Tests**: Test GAME framework integration +- **Example Files**: Create example usage files +- **Documentation**: Update README with new features + +### **๐Ÿ“ Pull Request Process** + +#### **Before Submitting:** +1. **๐Ÿ” Test Your Changes** + ```bash + npm run test:all + npm run build + npm run example:your-new-feature + ``` + +2. **๐Ÿ“– Update Documentation** + - Update README.md if adding new features + - Add example files for new functions + - Update API reference section + - Update CHANGELOG.md + +3. **โœ… Code Quality Checks** + ```bash + # Ensure TypeScript compiles + npm run build + + # Run all tests + npm run test:all + + # Test examples work + npm run example:tokens + ``` + +#### **PR Requirements:** +- **๐Ÿ“‹ Clear Description**: Explain what your PR adds/fixes +- **๐Ÿงช Testing Evidence**: Include screenshots, logs, or test results +- **๐Ÿ“š Documentation**: Update relevant documentation +- **๐Ÿ”— Issue Reference**: Link to related issues if applicable + +### **๐ŸŽฏ Specific Areas for Contribution** + +#### **๐Ÿ”ฅ High Priority** +- **New API Endpoints**: Add missing Token Metrics endpoints +- **Enhanced Error Handling**: Improve error messages and recovery +- **Performance Optimization**: Optimize API calls and response processing +- **Testing Coverage**: Expand test scenarios + +#### **๐Ÿ’ก Enhancement Ideas** +- **Real-time Updates**: WebSocket integration for live data +- **Data Visualization**: Add chart/graph generation capabilities +- **Caching Layer**: Implement intelligent response caching +- **Batch Processing**: Optimize multiple token requests + +#### **๐Ÿ“– Documentation Needs** +- **Video Tutorials**: Create setup and usage videos +- **Advanced Examples**: Complex trading bot scenarios +- **API Migration Guides**: Help users migrate from other APIs +- **Best Practices**: Performance and usage optimization guides + +### **๐Ÿ› ๏ธ Development Workflow** + +1. **๐ŸŒŸ Create Feature Branch** + ```bash + git checkout -b feature/new-endpoint-integration + ``` + +2. **๐Ÿ’ป Develop & Test** + ```bash + # Make your changes + # Test thoroughly + npm run test:individual + npm run example:your-feature + ``` + +3. **๐Ÿ“ Document Changes** + ```bash + # Update README.md + # Add example files + # Update CHANGELOG.md + ``` + +4. **๐Ÿ”„ Submit PR** + ```bash + git add . + git commit -m "feat: add new Token Metrics endpoint integration" + git push origin feature/new-endpoint-integration + ``` + +### **๐ŸŽ–๏ธ Recognition** + +Contributors will be: +- **๐Ÿ“œ Listed in CHANGELOG.md** +- **๐ŸŒŸ Credited in README.md** +- **๐Ÿ† Recognized in release notes** +- **๐Ÿ’ฌ Mentioned in community channels** + +### **๐Ÿ“ž Getting Help** + +Need help contributing? +- **๐Ÿ’ฌ Discussions**: Use GitHub Discussions for questions +- **๐Ÿ› Issues**: Create issues for bugs or feature requests +- **๐Ÿ“ง Direct Contact**: Reach out to maintainers +- **๐Ÿ“– Documentation**: Check our comprehensive guides + +### **๐Ÿ”„ Review Process** + +1. ๐Ÿด Fork the repository +2. ๐ŸŒŸ Create your feature branch (`git checkout -b feature/amazing-feature`) +3. ๐Ÿ’พ Commit your changes (`git commit -m 'Add some amazing feature'`) +4. ๐Ÿ“ค Push to the branch (`git push origin feature/amazing-feature`) +5. ๐Ÿ” Open a Pull Request + +--- + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the LICENSE file for details. + +--- + +## ๐Ÿ†˜ Support + +Need help? We've got you covered: + +- ๐Ÿ› **Bug Reports**: Create an issue on GitHub +- ๐Ÿ”ง **API Questions**: Contact Token Metrics support +- ๐Ÿ“š **Documentation**: Check our comprehensive examples and tests +- ๐Ÿ’ฌ **Community**: Join our discussions + +--- + +## ๐Ÿ“ˆ Changelog + +### ๐ŸŽ‰ v1.0.0 +- โœจ Initial release with 21 Token Metrics API endpoints +- ๐Ÿ†• **NEW: Hourly Trading Signals** - Real-time AI trading recommendations updated hourly +- ๐Ÿ’ฌ Interactive chat interface +- ๐Ÿ“š Comprehensive examples and tests +- ๐Ÿ›ก๏ธ Built-in error handling and rate limiting +- ๐Ÿ”ท Full TypeScript support +- ๐Ÿ“Š Crypto indices tracking with holdings and performance data + +--- + +## ๐Ÿ“‹ Quick Reference + +### **๐Ÿš€ Essential Commands** +```bash +# Installation +npm install tokenmetrics-virtuals-plugin + +# Setup +cp env.example .env + +# Test installation +npm run test:setup + +# Interactive testing +npm run chat + +# Build for production +npm run build +``` + +### **โšก Quick Integration Template** +```typescript +import { config } from "dotenv"; +config({ path: "./.env" }); +import TokenMetricsPlugin from "tokenmetrics-virtuals-plugin"; +import { GameAgent } from "@virtuals-protocol/game"; + +const plugin = new TokenMetricsPlugin({ + apiClientConfig: { apiKey: process.env.TOKENMETRICS_API_KEY! } +}); + +const agent = new GameAgent(process.env.GAME_API_KEY!, { + name: "Crypto AI Agent", + goal: "Provide crypto analysis", + workers: [plugin.getWorker({})] +}); + +await agent.init(); +``` + +### **๐ŸŽฏ Most Used Functions** +- `getTokens()` - Get all supported cryptocurrencies +- `getPriceData(token_id)` - Get current prices +- `getTradingSignals()` - Get buy/sell recommendations +- `getTokenMetricsAi(message)` - Chat with Token Metrics AI +- `getMarketMetrics()` - Get market overview +- `getIndices()` - Get crypto indices data (NEW!) +- `getIndicesHoldings(id)` - Get index portfolio composition (NEW!) + +--- + +
+ +**๐Ÿš€ Ready to revolutionize your crypto analysis?** + +[๐Ÿ“– Documentation](https://developers.tokenmetrics.com/) + +--- + +*Made with โค๏ธ by the Token Metrics team* + +
diff --git a/plugins/tokenMetricsPlugin/env.example b/plugins/tokenMetricsPlugin/env.example new file mode 100644 index 00000000..0c6c29ad --- /dev/null +++ b/plugins/tokenMetricsPlugin/env.example @@ -0,0 +1,8 @@ +# TokenMetrics API Configuration +TOKENMETRICS_API_KEY=tm-your-tokenmetrics-api-key-here + +# Virtuals Protocol GAME API Configuration +GAME_API_KEY=your-game-api-key-here + +# Optional: Custom TokenMetrics API Base URL (if using different endpoint) +# TOKENMETRICS_BASE_URL=https://api.tokenmetrics.com/v2 diff --git a/plugins/tokenMetricsPlugin/manual-endpoint-tests.md b/plugins/tokenMetricsPlugin/manual-endpoint-tests.md new file mode 100644 index 00000000..b7d89c5a --- /dev/null +++ b/plugins/tokenMetricsPlugin/manual-endpoint-tests.md @@ -0,0 +1,166 @@ +# ๐Ÿš€ TokenMetrics Endpoint Testing Guide + +## Manual Testing Checklist - All 21 Endpoints + +Test each endpoint by running: `echo "[PROMPT]" | npm run chat` + +### โœ… **1. Tokens Database** +```bash +echo "Show me the tokens database" | npm run chat +``` +**Expected**: List of tokens with TOKEN_ID, TOKEN_NAME, TOKEN_SYMBOL + +### โœ… **2. Top Market Cap Tokens** +```bash +echo "Get top cryptocurrencies by market capitalization" | npm run chat +``` +**Expected**: Bitcoin, Ethereum, etc. with prices and market caps + +### โœ… **3. Price Data** +```bash +echo "What is the current price of Bitcoin?" | npm run chat +``` +**Expected**: Bitcoin price in USD with BTC symbol + +### โœ… **4. Trader Grades** +```bash +echo "Show me trader grades for cryptocurrencies" | npm run chat +``` +**Expected**: TA_GRADE, QUANT_GRADE with color-coded formatting + +### โœ… **5. Investor Grades** +```bash +echo "What are the investor grades for top cryptocurrencies?" | npm run chat +``` +**Expected**: TM_INVESTOR_GRADE with investment recommendations + +### โœ… **6. Trading Signals** +```bash +echo "Show me trading signals for cryptocurrencies" | npm run chat +``` +**Expected**: BUY/SELL/HOLD signals with trend analysis + +### โœ… **7. Hourly Trading Signals** +```bash +echo "Show me hourly trading signals" | npm run chat +``` +**Expected**: Hourly trading signals with trend analysis + +### โœ… **8. Market Metrics** +```bash +echo "Get market metrics for cryptocurrency analysis" | npm run chat +``` +**Expected**: Volume, market cap, and other market metrics + +### โœ… **9. Quantmetrics** +```bash +echo "Show me quantmetrics data for crypto analysis" | npm run chat +``` +**Expected**: Quantitative metrics and analysis data + +### โœ… **10. Hourly OHLCV** +```bash +echo "Get hourly OHLCV data for Bitcoin" | npm run chat +``` +**Expected**: Open, High, Low, Close, Volume data by hour + +### โœ… **11. Daily OHLCV** +```bash +echo "Show me daily OHLCV data for Ethereum" | npm run chat +``` +**Expected**: Daily OHLCV data with date ranges + +### โœ… **12. AI Reports** +```bash +echo "Generate AI reports for cryptocurrency market" | npm run chat +``` +**Expected**: AI-generated analysis and recommendations + +### โœ… **13. Crypto Investors** +```bash +echo "Show me crypto investors data and analysis" | npm run chat +``` +**Expected**: Investor data and investment patterns + +### โœ… **14. Resistance & Support** +```bash +echo "What are the resistance and support levels for Bitcoin?" | npm run chat +``` +**Expected**: Price levels for resistance and support + +### โœ… **15. TokenMetrics AI** +```bash +echo "Ask TokenMetrics AI about the best cryptocurrency investments" | npm run chat +``` +**Expected**: AI recommendations and investment analysis + +### โœ… **16. Sentiments** +```bash +echo "Analyze cryptocurrency market sentiment" | npm run chat +``` +**Expected**: Positive/negative/neutral sentiment analysis + +### โœ… **17. Scenario Analysis** +```bash +echo "Show me scenario analysis for Bitcoin price predictions" | npm run chat +``` +**Expected**: Bearish/bullish/base scenarios with ROI predictions + +### โœ… **18. Correlation** +```bash +echo "Show me correlation analysis between cryptocurrencies" | npm run chat +``` +**Expected**: Correlation coefficients and portfolio insights + +### โœ… **19. Indices** +```bash +echo "Show me crypto indices data" | npm run chat +``` +**Expected**: Active and passive indices with performance data, ID, name, type, status + +### โœ… **20. Indices Holdings** +```bash +echo "What are the holdings of crypto index 1?" | npm run chat +``` +**Expected**: Index composition with symbols, weights, prices, and values + +### โœ… **21. Indices Performance** +```bash +echo "Show me performance data for crypto index 1" | npm run chat +``` +**Expected**: Historical ROI data with performance trends and analysis + +--- + +## ๐Ÿ“Š Testing Results Template + +| # | Endpoint | Status | Response Time | Notes | +|---|----------|--------|---------------|-------| +| 1 | Tokens Database | โณ | - | - | +| 2 | Top Market Cap | โณ | - | - | +| 3 | Price Data | โณ | - | - | +| 4 | Trader Grades | โณ | - | - | +| 5 | Investor Grades | โณ | - | - | +| 6 | Trading Signals | โณ | - | - | +| 7 | Hourly Trading Signals | โณ | - | - | +| 8 | Market Metrics | โณ | - | - | +| 9 | Quantmetrics | โณ | - | - | +| 10 | Hourly OHLCV | โณ | - | - | +| 11 | Daily OHLCV | โณ | - | - | +| 12 | AI Reports | โณ | - | - | +| 13 | Crypto Investors | โณ | - | - | +| 14 | Resistance & Support | โณ | - | - | +| 15 | TokenMetrics AI | โณ | - | - | +| 16 | Sentiments | โณ | - | - | +| 17 | Scenario Analysis | โณ | - | - | +| 18 | Correlation | โณ | - | - | +| 19 | Indices | โณ | - | - | +| 20 | Indices Holdings | โณ | - | - | +| 21 | Indices Performance | โณ | - | - | + +## ๐ŸŽฏ Success Criteria +- โœ… Endpoint responds without errors +- โœ… Data is properly formatted with colors and structure +- โœ… Response contains expected keywords/data types +- โœ… Response time under 10 seconds +- โœ… Beautiful UX formatting applied diff --git a/plugins/tokenMetricsPlugin/package-lock.json b/plugins/tokenMetricsPlugin/package-lock.json new file mode 100644 index 00000000..322ef99d --- /dev/null +++ b/plugins/tokenMetricsPlugin/package-lock.json @@ -0,0 +1,831 @@ +{ + "name": "tokenmetrics-virtuals-plugin", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tokenmetrics-virtuals-plugin", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@virtuals-protocol/game": "^0.1.14", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@virtuals-protocol/game": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@virtuals-protocol/game/-/game-0.1.14.tgz", + "integrity": "sha512-Y6QPVEcUFrk6aYijmV4pBsWhb4Xag0cAnQ+7fcz0qBIO1UNqZdFpVA0ezfEnjNcZuM5ypqc9dGPHbkNJ7qhhVA==", + "dependencies": { + "axios": "^1.7.9", + "dotenv": "^16.4.7" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "dev": true, + "requires": { + "undici-types": "~6.19.2" + } + }, + "@virtuals-protocol/game": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@virtuals-protocol/game/-/game-0.1.14.tgz", + "integrity": "sha512-Y6QPVEcUFrk6aYijmV4pBsWhb4Xag0cAnQ+7fcz0qBIO1UNqZdFpVA0ezfEnjNcZuM5ypqc9dGPHbkNJ7qhhVA==", + "requires": { + "axios": "^1.7.9", + "dotenv": "^16.4.7" + } + }, + "acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==" + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/plugins/tokenMetricsPlugin/package.json b/plugins/tokenMetricsPlugin/package.json new file mode 100644 index 00000000..f7a83b5e --- /dev/null +++ b/plugins/tokenMetricsPlugin/package.json @@ -0,0 +1,77 @@ +{ + "name": "tokenmetrics-virtuals-plugin", + "version": "1.0.0", + "description": "Complete TokenMetrics integration plugin for Virtuals Protocol", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "README.md", + "LICENSE", + "env.example" + ], + "scripts": { + "build": "tsc", + "start": "node dist/examples/example.fullAgent.js", + "dev": "ts-node src/examples/example.fullAgent.ts", + "chat": "ts-node src/chat.ts", + + "test:setup": "ts-node tests/test-setup.ts", + "test:individual": "ts-node tests/test-individual.ts", + "test:integration": "ts-node tests/test-integration.ts", + "test:all": "npm run test:individual && npm run test:integration", + + "demo:trading-bot": "ts-node tests/test-scenarios/trading-bot.ts", + "demo:research-agent": "ts-node tests/test-scenarios/research-agent.ts", + "demo:new-endpoints": "ts-node tests/test-scenarios/new-endpoints-demo.ts", + "demo:indices": "ts-node tests/test-scenarios/indices-endpoints-demo.ts", + + "example:tokens": "ts-node src/examples/example.tokens.ts", + "example:moonshot-tokens": "ts-node src/examples/example.moonshotTokens.ts", + "example:technology-grade": "ts-node src/examples/example.technologyGrade.ts", + "example:tm-grade": "ts-node src/examples/example.tmGrade.ts", + "example:fundamental-grade": "ts-node src/examples/example.fundamentalGrade.ts", + "example:trading-signals": "npx tsx src/examples/example.tradingSignals.ts", + "example:hourly-trading-signals": "ts-node src/examples/example.hourlyTradingSignals.ts", + "example:market-metrics": "npx tsx src/examples/example.marketMetrics.ts", + "example:price-data": "ts-node src/examples/example.priceData.ts", + "example:quantmetrics": "ts-node src/examples/example.quantmetrics.ts", + "example:hourly-ohlcv": "ts-node src/examples/example.hourlyOhlcv.ts", + "example:daily-ohlcv": "ts-node src/examples/example.dailyOhlcv.ts", + "example:ai-reports": "ts-node src/examples/example.aiReports.ts", + "example:crypto-investors": "ts-node src/examples/example.cryptoInvestors.ts", + "example:resistance-support": "ts-node src/examples/example.resistanceSupport.ts", + "example:top-market-cap": "ts-node src/examples/example.topMarketCap.ts", + "example:tokenmetrics-ai": "ts-node src/examples/example.tokenmetricsAi.ts", + "example:sentiments": "ts-node src/examples/example.sentiments.ts", + "example:scenario-analysis": "ts-node src/examples/example.scenarioAnalysis.ts", + "example:correlation": "ts-node src/examples/example.correlation.ts", + "example:indices": "ts-node src/examples/example.indices.ts", + "example:indices-holdings": "ts-node src/examples/example.indicesHoldings.ts", + "example:indices-performance": "ts-node src/examples/example.indicesPerformance.ts", + "example:full-agent": "ts-node src/examples/example.fullAgent.ts" + }, + "keywords": ["tokenmetrics", "virtuals-protocol", "crypto", "ai", "trading", "blockchain", "defi", "game-framework"], + "author": "TokenMetrics", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/tokenmetrics/tokenmetrics-virtuals-plugin.git" + }, + "homepage": "https://github.com/tokenmetrics/tokenmetrics-virtuals-plugin#readme", + "bugs": { + "url": "https://github.com/tokenmetrics/tokenmetrics-virtuals-plugin/issues" + }, + "engines": { + "node": ">=16.0.0" + }, + "dependencies": { + "@virtuals-protocol/game": "latest", + "dotenv": "^16.0.3" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "ts-node": "^10.9.0" + } +} diff --git a/plugins/tokenMetricsPlugin/plugin_metadata.yml b/plugins/tokenMetricsPlugin/plugin_metadata.yml new file mode 100644 index 00000000..6a965e14 --- /dev/null +++ b/plugins/tokenMetricsPlugin/plugin_metadata.yml @@ -0,0 +1,19 @@ +plugin_name: "Token Metrics Virtuals Plugin" +author: "Token Metrics Engineering Team" +logo_url: "" +release_date: "2025-09" + +short_description: "Complete Token Metrics integration plugin for Virtuals Protocol GAME framework providing AI-powered cryptocurrency analysis with comprehensive API endpoints" +detailed_description: "A comprehensive plugin that integrates Token Metrics' AI-powered cryptocurrency analysis platform with the Virtuals Protocol GAME framework. Provides API endpoints covering token discovery, moonshot analysis, trading signals, market metrics, AI reports, technical analysis, and crypto indices. Features include real-time pricing data, AI-generated trading recommendations, sentiment analysis, and quantitative metrics for informed crypto trading decisions." + +plugin_logo_url: "" +screenshots: + - "" + - "" +demo_video_url: "" +documentation_url: "https://developers.tokenmetrics.com/" +changelog_url: "" + +x_account_handle: "@tokenmetricsinc" +support_contact: "support@tokenmetrics.com" +community_url: "" diff --git a/plugins/tokenMetricsPlugin/screenshots/Chat-1.png b/plugins/tokenMetricsPlugin/screenshots/Chat-1.png new file mode 100644 index 00000000..75703a21 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Chat-1.png differ diff --git a/plugins/tokenMetricsPlugin/screenshots/Chat-2.png b/plugins/tokenMetricsPlugin/screenshots/Chat-2.png new file mode 100644 index 00000000..465bc212 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Chat-2.png differ diff --git a/plugins/tokenMetricsPlugin/screenshots/Market Sentiment Example.png b/plugins/tokenMetricsPlugin/screenshots/Market Sentiment Example.png new file mode 100644 index 00000000..13a5a5d6 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Market Sentiment Example.png differ diff --git a/plugins/tokenMetricsPlugin/screenshots/Quantmetics Example.png b/plugins/tokenMetricsPlugin/screenshots/Quantmetics Example.png new file mode 100644 index 00000000..8358cda3 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Quantmetics Example.png differ diff --git a/plugins/tokenMetricsPlugin/screenshots/Setup-1.png b/plugins/tokenMetricsPlugin/screenshots/Setup-1.png new file mode 100644 index 00000000..318c69b3 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Setup-1.png differ diff --git a/plugins/tokenMetricsPlugin/screenshots/Setup-2.png b/plugins/tokenMetricsPlugin/screenshots/Setup-2.png new file mode 100644 index 00000000..c3809506 Binary files /dev/null and b/plugins/tokenMetricsPlugin/screenshots/Setup-2.png differ diff --git a/plugins/tokenMetricsPlugin/src/chat.ts b/plugins/tokenMetricsPlugin/src/chat.ts new file mode 100644 index 00000000..e6f67924 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/chat.ts @@ -0,0 +1,4610 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import * as readline from 'readline'; +import TokenMetricsPlugin from "./index"; + +// Color coding for beautiful UX +const colors = { + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m' +}; + +class TokenMetricsChatInterface { + private plugin: TokenMetricsPlugin; + private rl: readline.Interface; + private lastRequestTime: number = 0; + private minRequestInterval: number = 2000; // 2 seconds between requests + private tokenCache: { [key: string]: string } = {}; // Cache for dynamic token mappings + private tokenCacheExpiry: number = 0; // Cache expiry timestamp + private readonly CACHE_DURATION = 30 * 60 * 1000; // 30 minutes cache + + constructor() { + this.plugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, + }); + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + } + + private async rateLimitDelay(): Promise { + const now = Date.now(); + const timeSinceLastRequest = now - this.lastRequestTime; + + if (timeSinceLastRequest < this.minRequestInterval) { + const waitTime = this.minRequestInterval - timeSinceLastRequest; + console.log(`${colors.yellow}โณ Rate limiting: waiting ${Math.ceil(waitTime/1000)} seconds...${colors.reset}`); + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + + this.lastRequestTime = Date.now(); + } + + private async retryWithBackoff( + operation: () => Promise, + maxRetries: number = 3, + baseDelay: number = 5000 + ): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + await this.rateLimitDelay(); + return await operation(); + } catch (error) { + const errorStr = error?.toString() || ''; + + if (errorStr.includes('429') || errorStr.includes('Too Many Requests')) { + if (attempt === maxRetries) { + throw new Error(`Rate limit exceeded. Please try again in a few minutes. The TokenMetrics API has usage limits to ensure fair access for all users.`); + } + + const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff + console.log(`${colors.yellow}โš ๏ธ Rate limit hit (attempt ${attempt}/${maxRetries}). Retrying in ${delay/1000} seconds...${colors.reset}`); + await new Promise(resolve => setTimeout(resolve, delay)); + } else { + throw error; + } + } + } + throw new Error('Max retries exceeded'); + } + + private formatHeader() { + console.clear(); + console.log(`${colors.cyan}${colors.bright}โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—${colors.reset}`); + console.log(`${colors.cyan}${colors.bright}โ•‘ ๐Ÿš€ Token Metrics AI Chat โ•‘${colors.reset}`); + console.log(`${colors.cyan}${colors.bright}โ•‘ Powered by Virtuals Protocol GAME โ•‘${colors.reset}`); + console.log(`${colors.cyan}${colors.bright}โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${colors.reset}`); + console.log(); + console.log(`${colors.yellow}๐Ÿ’ก Ask me anything about cryptocurrency markets, trading, or investments!${colors.reset}`); + console.log(`${colors.dim}Examples: "What's the next 100x coin?", "Analyze Bitcoin sentiment", "Show me trading signals"${colors.reset}`); + console.log(`${colors.dim}Type 'help' for available commands, 'quit' to exit${colors.reset}`); + console.log(); + } + + private formatResponse(response: string, type: 'ai' | 'data' | 'error' = 'ai') { + const timestamp = new Date().toLocaleTimeString(); + + switch (type) { + case 'ai': + console.log(`${colors.green}${colors.bright}๐Ÿค– Token Metrics AI Response [${timestamp}]${colors.reset}`); + console.log(`${colors.green}${'โ•'.repeat(60)}${colors.reset}`); + break; + case 'data': + console.log(`${colors.blue}${colors.bright}๐Ÿ“Š Data Analysis [${timestamp}]${colors.reset}`); + console.log(`${colors.blue}${'โ•'.repeat(60)}${colors.reset}`); + break; + case 'error': + console.log(`${colors.red}${colors.bright}โŒ Error [${timestamp}]${colors.reset}`); + console.log(`${colors.red}${'โ•'.repeat(60)}${colors.reset}`); + break; + } + + // Format the response with proper line breaks and indentation + const formattedResponse = response + .replace(/\n/g, '\n ') + .replace(/\*\*(.*?)\*\*/g, `${colors.bright}$1${colors.reset}`) + .replace(/\*(.*?)\*/g, `${colors.yellow}$1${colors.reset}`); + + console.log(` ${formattedResponse}`); + console.log(`${colors.dim}${'โ”€'.repeat(60)}${colors.reset}`); + console.log(); + } + + private async analyzePrompt(prompt: string): Promise { + const lowerPrompt = prompt.toLowerCase(); + + try { + // Determine the best endpoint(s) to use based on the prompt + // Order matters - more specific matches first! + + // 1. TOKEN LIST - /tokens endpoint (more specific matching) + if ((lowerPrompt.includes('token') && (lowerPrompt.includes('list') || lowerPrompt.includes('id') || lowerPrompt.includes('database') || lowerPrompt.includes('show'))) || + (lowerPrompt.includes('supported') && lowerPrompt.includes('cryptocurrencies')) || + (lowerPrompt.includes('all') && lowerPrompt.includes('cryptocurrencies')) || + (lowerPrompt.includes('tokens') && (lowerPrompt.includes('database') || lowerPrompt.includes('available') || lowerPrompt.includes('show'))) || + lowerPrompt.includes('token database') || + lowerPrompt.includes('tokens database')) { + + console.log(`${colors.yellow}๐Ÿ” Getting supported tokens list...${colors.reset}`); + await this.getTokensList(); + + // 2. MOONSHOT TOKENS - /moonshot-tokens endpoint (new API) + } else if ((lowerPrompt.includes('moonshot') || lowerPrompt.includes('100x') || lowerPrompt.includes('breakout')) || + (lowerPrompt.includes('high potential') && lowerPrompt.includes('token')) || + (lowerPrompt.includes('next') && (lowerPrompt.includes('gem') || lowerPrompt.includes('coin')))) { + + console.log(`${colors.yellow}๐Ÿ” Getting moonshot tokens...${colors.reset}`); + await this.getMoonshotTokens(prompt); + + // 3. TM GRADE - /tm-grade endpoint (new API) + } else if ((lowerPrompt.includes('tm grade') || lowerPrompt.includes('tm-grade')) || + (lowerPrompt.includes('comprehensive') && lowerPrompt.includes('grade')) || + (lowerPrompt.includes('momentum') && lowerPrompt.includes('grade'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting TM grade analysis...${colors.reset}`); + await this.getTmGrade(prompt); + + // 4. FUNDAMENTAL GRADE - /fundamental-grade endpoint (new API) + } else if ((lowerPrompt.includes('fundamental') && lowerPrompt.includes('grade')) || + (lowerPrompt.includes('tokenomics') && lowerPrompt.includes('analysis')) || + (lowerPrompt.includes('fundamental') && lowerPrompt.includes('strength'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting fundamental grade analysis...${colors.reset}`); + await this.getFundamentalGrade(prompt); + + // 5. TECHNOLOGY GRADE - /technology-grade endpoint (new API) + } else if ((lowerPrompt.includes('technology') && lowerPrompt.includes('grade')) || + (lowerPrompt.includes('tech') && lowerPrompt.includes('score')) || + (lowerPrompt.includes('development') && lowerPrompt.includes('quality'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting technology grade analysis...${colors.reset}`); + await this.getTechnologyGrade(prompt); + + // 6. TRADER GRADES - /trader-grades endpoint (more specific matching - moved up) + } else if ((lowerPrompt.includes('trader') && (lowerPrompt.includes('grade') || lowerPrompt.includes('score') || lowerPrompt.includes('rating'))) || + (lowerPrompt.includes('trading') && lowerPrompt.includes('score'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting trader grades...${colors.reset}`); + await this.getTraderGrades(); + + // 3. INVESTOR GRADES - /investor-grades endpoint (more specific matching - moved up) + } else if ((lowerPrompt.includes('investor') && (lowerPrompt.includes('grade') || lowerPrompt.includes('score') || lowerPrompt.includes('rating'))) || + (lowerPrompt.includes('investment') && lowerPrompt.includes('rating'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting investor grades...${colors.reset}`); + await this.getInvestorGrades(); + + // 4. TOP MARKET CAP - /top-market-cap endpoint (moved down to avoid conflicts) + } else if ((lowerPrompt.includes('top') && (lowerPrompt.includes('market') || lowerPrompt.includes('cap') || lowerPrompt.includes('crypto')) && !lowerPrompt.includes('grade') && !lowerPrompt.includes('score') && !lowerPrompt.includes('rating')) || + (lowerPrompt.includes('market') && lowerPrompt.includes('cap') && !lowerPrompt.includes('grade')) || + (lowerPrompt.includes('biggest') && lowerPrompt.includes('crypto')) || + lowerPrompt.includes('market leaders')) { + + console.log(`${colors.yellow}๐Ÿ” Getting top market cap tokens...${colors.reset}`); + await this.getTopMarketCapTokens(); + + // 5. PRICE DATA - /price-data endpoint + } else if ((lowerPrompt.includes('price') && !lowerPrompt.includes('prediction') && !lowerPrompt.includes('forecast') && !lowerPrompt.includes('scenario')) || + lowerPrompt.includes('current price') || + (lowerPrompt.includes('get') && lowerPrompt.includes('price'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting current price data...${colors.reset}`); + await this.getPriceData(prompt); + + // 6. HOURLY TRADING SIGNALS - /hourly-trading-signals endpoint (more specific first) + } else if ((lowerPrompt.includes('hourly') && lowerPrompt.includes('signal')) || + (lowerPrompt.includes('hourly') && lowerPrompt.includes('trading')) || + lowerPrompt.includes('hourly trading signals')) { + + console.log(`${colors.yellow}๐Ÿ” Getting hourly trading signals...${colors.reset}`); + await this.getHourlyTradingSignals(prompt); + + // 7. TRADING SIGNALS - /trading-signals endpoint + } else if (lowerPrompt.includes('signal') || + (lowerPrompt.includes('trading') && !lowerPrompt.includes('score') && !lowerPrompt.includes('hourly')) || + lowerPrompt.includes('buy') || + lowerPrompt.includes('sell') || + (lowerPrompt.includes('trade') && !lowerPrompt.includes('trader'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting trading signals...${colors.reset}`); + await this.getTradingSignals(); + + // 8. MARKET METRICS - /market-metrics endpoint + } else if ((lowerPrompt.includes('market') && lowerPrompt.includes('metric')) || + (lowerPrompt.includes('market') && lowerPrompt.includes('indicator')) || + lowerPrompt.includes('market data')) { + + console.log(`${colors.yellow}๐Ÿ” Getting market metrics...${colors.reset}`); + await this.getMarketMetrics(prompt); + + // 9. SENTIMENT ANALYSIS - /sentiments endpoint + } else if (lowerPrompt.includes('sentiment') || lowerPrompt.includes('social') || + lowerPrompt.includes('twitter') || lowerPrompt.includes('reddit') || lowerPrompt.includes('news')) { + + console.log(`${colors.yellow}๐Ÿ” Analyzing market sentiment...${colors.reset}`); + await this.getSentimentAnalysis(); + + // 10. SCENARIO ANALYSIS - /scenario-analysis endpoint + } else if (lowerPrompt.includes('price') && (lowerPrompt.includes('prediction') || lowerPrompt.includes('forecast') || lowerPrompt.includes('scenario')) || + lowerPrompt.includes('scenario') || lowerPrompt.includes('prediction')) { + + console.log(`${colors.yellow}๐Ÿ” Analyzing price scenarios...${colors.reset}`); + await this.getScenarioAnalysis(prompt); + + // 11. CORRELATION ANALYSIS - /correlation endpoint + } else if (lowerPrompt.includes('correlation') || lowerPrompt.includes('relationship') || + lowerPrompt.includes('portfolio') || lowerPrompt.includes('diversif')) { + + console.log(`${colors.yellow}๐Ÿ” Analyzing token correlations...${colors.reset}`); + await this.getCorrelationAnalysis(prompt); + + // 12. INDICES - /indices endpoint + } else if ((lowerPrompt.includes('indices') || lowerPrompt.includes('index')) && + !lowerPrompt.includes('holding') && !lowerPrompt.includes('performance')) { + + console.log(`${colors.yellow}๐Ÿ” Getting crypto indices...${colors.reset}`); + await this.getIndices(prompt); + + // 13. INDICES HOLDINGS - /indices-holdings endpoint + } else if ((lowerPrompt.includes('indices') || lowerPrompt.includes('index')) && + (lowerPrompt.includes('holding') || lowerPrompt.includes('composition') || lowerPrompt.includes('weight'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting index holdings...${colors.reset}`); + await this.getIndicesHoldings(prompt); + + // 14. INDICES PERFORMANCE - /indices-performance endpoint + } else if ((lowerPrompt.includes('indices') || lowerPrompt.includes('index')) && + (lowerPrompt.includes('performance') || lowerPrompt.includes('return') || lowerPrompt.includes('roi'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting index performance...${colors.reset}`); + await this.getIndicesPerformance(prompt); + + // 15. QUANTMETRICS - /quantmetrics endpoint + } else if (lowerPrompt.includes('quant') || lowerPrompt.includes('quantitative') || + (lowerPrompt.includes('technical') && lowerPrompt.includes('analysis'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting quantitative metrics...${colors.reset}`); + await this.getQuantmetrics(prompt); + + // 16. HOURLY OHLCV - /hourly-ohlcv endpoint + } else if ((lowerPrompt.includes('hourly') && (lowerPrompt.includes('ohlcv') || lowerPrompt.includes('price'))) || + lowerPrompt.includes('hourly data') || lowerPrompt.includes('hourly chart')) { + + console.log(`${colors.yellow}๐Ÿ” Getting hourly OHLCV data...${colors.reset}`); + await this.getHourlyOhlcv(prompt); + + // 17. DAILY OHLCV - /daily-ohlcv endpoint + } else if ((lowerPrompt.includes('daily') && (lowerPrompt.includes('ohlcv') || lowerPrompt.includes('price'))) || + lowerPrompt.includes('daily data') || lowerPrompt.includes('daily chart')) { + + console.log(`${colors.yellow}๐Ÿ” Getting daily OHLCV data...${colors.reset}`); + await this.getDailyOhlcv(prompt); + + // 18. AI REPORTS - /ai-reports endpoint + } else if ((lowerPrompt.includes('ai') && lowerPrompt.includes('report')) || + lowerPrompt.includes('analysis report') || lowerPrompt.includes('detailed analysis')) { + + console.log(`${colors.yellow}๐Ÿ” Getting AI reports...${colors.reset}`); + await this.getAiReports(prompt); + + // 19. CRYPTO INVESTORS - /crypto-investors endpoint + } else if ((lowerPrompt.includes('investor') && !lowerPrompt.includes('grade') && !lowerPrompt.includes('rating')) || + lowerPrompt.includes('institutional') || lowerPrompt.includes('smart money')) { + + console.log(`${colors.yellow}๐Ÿ” Getting crypto investors data...${colors.reset}`); + await this.getCryptoInvestors(); + + // 20. RESISTANCE/SUPPORT - /resistance-support endpoint + } else if (lowerPrompt.includes('resistance') || lowerPrompt.includes('support') || + (lowerPrompt.includes('technical') && (lowerPrompt.includes('level') || lowerPrompt.includes('analysis')))) { + + console.log(`${colors.yellow}๐Ÿ” Getting resistance/support levels...${colors.reset}`); + await this.getResistanceSupport(prompt); + + // 21. MARKET OVERVIEW - Multiple endpoints + // 20. MARKET OVERVIEW - Multiple endpoints + } else if (lowerPrompt.includes('market') && (lowerPrompt.includes('overview') || lowerPrompt.includes('general'))) { + + console.log(`${colors.yellow}๐Ÿ” Getting market overview...${colors.reset}`); + await this.getMarketOverview(); + + // 21. AI CHAT - /tmai endpoint (for general questions) + } else if (lowerPrompt.includes('ai') || lowerPrompt.includes('chat') || lowerPrompt.includes('ask') || + lowerPrompt.includes('what') || lowerPrompt.includes('how') || lowerPrompt.includes('why') || + lowerPrompt.includes('100x') || lowerPrompt.includes('recommend') || lowerPrompt.includes('explain')) { + + console.log(`${colors.yellow}๐Ÿ” Consulting TokenMetrics AI...${colors.reset}`); + await this.queryTokenMetricsAI(prompt); + + } else { + // Default to AI chat for general questions + console.log(`${colors.yellow}๐Ÿ” Consulting TokenMetrics AI...${colors.reset}`); + await this.queryTokenMetricsAI(prompt); + } + + } catch (error) { + this.formatResponse(`Failed to process your request: ${error}`, 'error'); + } + } + + private async queryTokenMetricsAI(prompt: string): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTokenMetricsAi.executable( + { user_message: prompt }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Extract the actual AI response from the JSON + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + this.formatResponse(responseData.answer || responseData.message || result.feedback, 'ai'); + } else { + this.formatResponse(result.feedback, 'ai'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`AI query failed: ${error}`, 'error'); + } + } + + private async getSentimentAnalysis(): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getSentiments.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the sentiment data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + const sentimentData = responseData.data[0]; + this.formatSentimentResponse(sentimentData); + } else { + this.formatResponse("No sentiment data available at the moment.", 'data'); + } + } else { + this.formatResponse("Latest market sentiment analysis retrieved successfully. Check the detailed data above for sentiment scores from Twitter, Reddit, and news sources.", 'data'); + } + } catch (parseError) { + this.formatResponse("Latest market sentiment analysis retrieved successfully. Check the detailed data above for sentiment scores from Twitter, Reddit, and news sources.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Sentiment analysis failed: ${error}`, 'error'); + } + } + + private formatSentimentResponse(data: any): void { + const timestamp = new Date(data.DATETIME).toLocaleString(); + + console.log(`${colors.blue}${colors.bright}๐Ÿ˜Š Market Sentiment Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.blue}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“… Data Time: ${timestamp}${colors.reset}\n`); + + // Overall Market Sentiment + const marketGrade = data.MARKET_SENTIMENT_GRADE; + const marketLabel = data.MARKET_SENTIMENT_LABEL; + const marketColor = this.getSentimentColor(marketLabel); + const marketEmoji = this.getSentimentEmoji(marketLabel); + + console.log(`${colors.bright}๐ŸŒ OVERALL MARKET SENTIMENT${colors.reset}`); + console.log(`${marketColor}${marketEmoji} ${marketLabel.toUpperCase()} (${marketGrade}/100)${colors.reset}`); + console.log(`${this.getSentimentBar(marketGrade)}\n`); + + // Twitter Sentiment + const twitterGrade = data.TWITTER_SENTIMENT_GRADE; + const twitterLabel = data.TWITTER_SENTIMENT_LABEL; + const twitterColor = this.getSentimentColor(twitterLabel); + const twitterEmoji = this.getSentimentEmoji(twitterLabel); + + console.log(`${colors.bright}๐Ÿฆ TWITTER SENTIMENT${colors.reset}`); + console.log(`${twitterColor}${twitterEmoji} ${twitterLabel.toUpperCase()} (${twitterGrade}/100)${colors.reset}`); + console.log(`${this.getSentimentBar(twitterGrade)}`); + if (data.TWITTER_SUMMARY) { + console.log(`${colors.dim}๐Ÿ“ ${this.truncateText(data.TWITTER_SUMMARY, 200)}${colors.reset}\n`); + } + + // Reddit Sentiment + const redditGrade = data.REDDIT_SENTIMENT_GRADE; + const redditLabel = data.REDDIT_SENTIMENT_LABEL; + const redditColor = this.getSentimentColor(redditLabel); + const redditEmoji = this.getSentimentEmoji(redditLabel); + + console.log(`${colors.bright}๐Ÿ“ฑ REDDIT SENTIMENT${colors.reset}`); + console.log(`${redditColor}${redditEmoji} ${redditLabel.toUpperCase()} (${redditGrade}/100)${colors.reset}`); + console.log(`${this.getSentimentBar(redditGrade)}`); + if (data.REDDIT_SUMMARY) { + console.log(`${colors.dim}๐Ÿ“ ${this.truncateText(data.REDDIT_SUMMARY, 200)}${colors.reset}\n`); + } + + // News Sentiment + const newsGrade = data.NEWS_SENTIMENT_GRADE; + const newsLabel = data.NEWS_SENTIMENT_LABEL; + const newsColor = this.getSentimentColor(newsLabel); + const newsEmoji = this.getSentimentEmoji(newsLabel); + + console.log(`${colors.bright}๐Ÿ“ฐ NEWS SENTIMENT${colors.reset}`); + console.log(`${newsColor}${newsEmoji} ${newsLabel.toUpperCase()} (${newsGrade}/100)${colors.reset}`); + console.log(`${this.getSentimentBar(newsGrade)}`); + if (data.NEWS_SUMMARY) { + console.log(`${colors.dim}๐Ÿ“ ${this.truncateText(data.NEWS_SUMMARY, 200)}${colors.reset}\n`); + } + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(); + } + + private getSentimentColor(label: string): string { + switch (label.toLowerCase()) { + case 'very positive': + case 'positive': + return colors.green; + case 'neutral': + return colors.yellow; + case 'negative': + case 'very negative': + return colors.red; + default: + return colors.reset; + } + } + + private getSentimentEmoji(label: string): string { + switch (label.toLowerCase()) { + case 'very positive': + return '๐Ÿš€'; + case 'positive': + return '๐Ÿ˜Š'; + case 'neutral': + return '๐Ÿ˜'; + case 'negative': + return '๐Ÿ˜Ÿ'; + case 'very negative': + return '๐Ÿ˜ฐ'; + default: + return 'โ“'; + } + } + + private getSentimentBar(grade: number): string { + const barLength = 30; + const filledLength = Math.round((grade / 100) * barLength); + const emptyLength = barLength - filledLength; + + let color = colors.red; + if (grade >= 60) color = colors.green; + else if (grade >= 40) color = colors.yellow; + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(emptyLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset} ${grade}%`; + } + + private truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + } + + private async getTraderGrades(): Promise { + try { + console.log(`${colors.dim} ๐ŸŽฏ Getting trader grades with date range parameters${colors.reset}`); + + // Calculate date range (30 days ago to today) + const endDate = new Date().toISOString().split('T')[0]; // Today in YYYY-MM-DD format + const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 30 days ago + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTraderGrades.executable( + { + startDate: startDate, + endDate: endDate, + limit: "10", + page: "1" + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the response to extract trader grades data + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatTraderGradesResponse(responseData.data); + } else { + this.formatResponse("No trader grades data available at the moment.", 'data'); + } + } else { + this.formatResponse("AI trader grades retrieved successfully! Check the detailed data above for short-term trading scores and recommendations.", 'data'); + } + } catch (parseError) { + this.formatResponse("AI trader grades retrieved successfully! Check the detailed data above for short-term trading scores and recommendations.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Failed to get trader grades: ${error}`, 'error'); + } + } + + private formatTraderGradesResponse(grades: any[]): void { + console.log(`${colors.yellow}${colors.bright}๐Ÿ“Š AI TRADER GRADES [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.yellow}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿค– Short-term Trading Scores & Recommendations${colors.reset}\n`); + + grades.slice(0, 10).forEach((grade, index) => { + const tokenName = grade.TOKEN_NAME || grade.name || 'Unknown'; + const tokenSymbol = grade.TOKEN_SYMBOL || grade.symbol || 'N/A'; + const traderGrade = grade.TM_TRADER_GRADE || grade.trader_grade || 0; + const traderLabel = grade.TM_TRADER_LABEL || grade.trader_label || 'N/A'; + const change24h = grade.TM_TRADER_GRADE_24H_PCT_CHANGE || grade.trader_grade_24h_change || 0; + const timestamp = grade.DATE || grade.date || new Date().toISOString(); + + // Color coding for grades + let gradeColor = colors.red; + let gradeEmoji = '๐Ÿ”ด'; + if (traderGrade >= 80) { + gradeColor = colors.green; + gradeEmoji = '๐ŸŸข'; + } else if (traderGrade >= 60) { + gradeColor = colors.yellow; + gradeEmoji = '๐ŸŸก'; + } + + // Color coding for grade change + const changeColor = change24h >= 0 ? colors.green : colors.red; + const changeEmoji = change24h >= 0 ? '๐Ÿ“ˆ' : '๐Ÿ“‰'; + const changeSign = change24h >= 0 ? '+' : ''; + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(` ${gradeEmoji} Trader Grade: ${gradeColor}${traderGrade.toFixed(1)}/100 (${traderLabel})${colors.reset}`); + console.log(` ${this.getGradeBar(traderGrade)}`); + console.log(` ${changeEmoji} Grade Change (24h): ${changeColor}${changeSign}${change24h.toFixed(2)}%${colors.reset}`); + console.log(` โฐ ${new Date(timestamp).toLocaleDateString()}`); + console.log(); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Trader grades focus on short-term (1-30 days) trading opportunities${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Grades: 80+ Excellent, 60+ Good, 40+ Fair, <40 Poor${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ฐ Use 'bitcoin price' or 'ethereum price' to get current prices${colors.reset}`); + console.log(); + } + + private getGradeBar(grade: number): string { + const barLength = 15; + const filledLength = Math.round((grade / 100) * barLength); + const emptyLength = barLength - filledLength; + + let color = colors.red; + if (grade >= 70) color = colors.green; + else if (grade >= 50) color = colors.yellow; + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(emptyLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset}`; + } + + private async getInvestorGrades(): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getInvestorGrades.executable( + { limit: "10", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the response to extract investor grades data + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatInvestorGradesResponse(responseData.data); + } else { + this.formatResponse("No investor grades data available at the moment.", 'data'); + } + } else { + this.formatResponse("AI investor grades retrieved successfully! Check the detailed data above for long-term investment ratings and analysis.", 'data'); + } + } catch (parseError) { + this.formatResponse("AI investor grades retrieved successfully! Check the detailed data above for long-term investment ratings and analysis.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Failed to get investor grades: ${error}`, 'error'); + } + } + + private formatInvestorGradesResponse(grades: any[]): void { + console.log(`${colors.magenta}${colors.bright}๐Ÿ’Ž AI INVESTOR GRADES [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.magenta}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿค– Long-term Investment Ratings & Analysis${colors.reset}\n`); + + grades.slice(0, 10).forEach((grade, index) => { + const tokenName = grade.TOKEN_NAME || grade.name || 'Unknown'; + const tokenSymbol = grade.TOKEN_SYMBOL || grade.symbol || 'N/A'; + const investorGrade = grade.TM_INVESTOR_GRADE || grade.investor_grade || 0; + const investorLabel = grade.TM_INVESTOR_LABEL || grade.investor_label || 'N/A'; + const change7d = grade.TM_INVESTOR_GRADE_7D_PCT_CHANGE || grade.investor_grade_7d_change || 0; + const fundamentalGrade = grade.FUNDAMENTAL_GRADE || grade.fundamental_grade || 0; + const valuationGrade = grade.VALUATION_GRADE || grade.valuation_grade || 0; + const timestamp = grade.DATE || grade.date || new Date().toISOString(); + + // Color coding for grades + let gradeColor = colors.red; + let gradeEmoji = '๐Ÿ”ด'; + if (investorGrade >= 80) { + gradeColor = colors.green; + gradeEmoji = '๐ŸŸข'; + } else if (investorGrade >= 60) { + gradeColor = colors.yellow; + gradeEmoji = '๐ŸŸก'; + } + + // Color coding for grade change + const changeColor = change7d >= 0 ? colors.green : colors.red; + const changeEmoji = change7d >= 0 ? '๐Ÿ“ˆ' : '๐Ÿ“‰'; + const changeSign = change7d >= 0 ? '+' : ''; + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(` ${gradeEmoji} Investor Grade: ${gradeColor}${investorGrade.toFixed(1)}/100 (${investorLabel})${colors.reset}`); + console.log(` ${this.getGradeBar(investorGrade)}`); + console.log(` ๐Ÿ“Š Fundamental: ${colors.cyan}${fundamentalGrade.toFixed(1)}/100${colors.reset} | Valuation: ${colors.cyan}${valuationGrade.toFixed(1)}/100${colors.reset}`); + console.log(` ${changeEmoji} Grade Change (7d): ${changeColor}${changeSign}${change7d.toFixed(2)}%${colors.reset}`); + console.log(` โฐ ${new Date(timestamp).toLocaleDateString()}`); + console.log(); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Investor grades focus on long-term (6+ months) investment potential${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Grades: 80+ Excellent, 60+ Good, 40+ Fair, <40 Poor${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ฐ Use 'bitcoin price' or 'ethereum price' to get current prices${colors.reset}`); + console.log(); + } + + private async getMarketMetrics(prompt: string): Promise { + try { + console.log(`${colors.dim} ๐ŸŽฏ Getting general market metrics and analytics${colors.reset}`); + + // Calculate date range (30 days ago to today) + const endDate = new Date().toISOString().split('T')[0]; // Today in YYYY-MM-DD format + const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 30 days ago + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getMarketMetrics.executable( + { + startDate: startDate, + endDate: endDate, + limit: "10", + page: "1" + }, + (msg: string) => { + console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`); + } + ); + }); + + if (result.status === 'done') { + // Try to parse the full response from result.feedback + try { + const responseIndex = result.feedback.lastIndexOf('Response: '); + if (responseIndex !== -1) { + const jsonString = result.feedback.substring(responseIndex + 10).trim(); + const responseData = JSON.parse(jsonString); + if (responseData.data && responseData.data.length > 0) { + this.formatMarketMetricsResponse(responseData.data); + return; // Exit early since we've formatted the data + } + } + } catch (e) { + // If parsing fails, continue with default behavior + } + + // Fallback to generic message if parsing fails + this.formatResponse("Successfully retrieved market metrics data", 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Market metrics query failed: ${error}`, 'error'); + } + } + + private formatMarketMetricsResponse(data: any[]): void { + if (!data || data.length === 0) return; + + const latest = data[0]; + const previous = data[1] || latest; + + console.log(`${colors.cyan}๐ŸŒ Crypto Market Overview [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log('โ•'.repeat(80)); + + // Market Cap Analysis + const currentMcap = latest.TOTAL_CRYPTO_MCAP; + const previousMcap = previous.TOTAL_CRYPTO_MCAP; + const mcapChange = ((currentMcap - previousMcap) / previousMcap * 100); + const mcapChangeColor = mcapChange >= 0 ? colors.green : colors.red; + const mcapChangeIcon = mcapChange >= 0 ? '๐Ÿ“ˆ' : '๐Ÿ“‰'; + + console.log(`${colors.yellow}๐Ÿ’ฐ MARKET CAPITALIZATION${colors.reset}`); + console.log('โ”€'.repeat(50)); + console.log(`๐ŸŒ Total Crypto Market Cap: ${colors.bright}$${this.formatLargeNumber(currentMcap)}${colors.reset}`); + console.log(`${mcapChangeIcon} 24h Change: ${mcapChangeColor}${mcapChange >= 0 ? '+' : ''}${mcapChange.toFixed(2)}%${colors.reset}`); + console.log(`๐Ÿ“… Date: ${new Date(latest.DATE).toLocaleDateString()}`); + console.log(); + + // Market Sentiment Analysis + const highGradePerc = latest.TM_GRADE_PERC_HIGH_COINS; + const signal = latest.TM_GRADE_SIGNAL; + const lastSignal = latest.LAST_TM_GRADE_SIGNAL; + + console.log(`${colors.blue}๐Ÿ“Š MARKET SENTIMENT ANALYSIS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + // High Grade Coins Percentage + const gradeBar = this.getMarketGradeBar(highGradePerc); + const gradeRating = this.getMarketGradeRating(highGradePerc); + console.log(`โญ High-Grade Coins: ${highGradePerc.toFixed(1)}% ${gradeBar}`); + console.log(` ${gradeRating}`); + + // Market Signal + const signalInfo = this.getMarketSignalInfo(signal); + const lastSignalInfo = this.getMarketSignalInfo(lastSignal); + console.log(`๐ŸŽฏ Current Signal: ${signalInfo.icon} ${signalInfo.text} ${signalInfo.color}${signalInfo.description}${colors.reset}`); + console.log(`๐Ÿ“ˆ Previous Signal: ${lastSignalInfo.icon} ${lastSignalInfo.text}`); + + // Signal Change Analysis + if (signal !== lastSignal) { + const changeType = signal > lastSignal ? 'improved' : 'deteriorated'; + const changeColor = signal > lastSignal ? colors.green : colors.red; + console.log(`๐Ÿ”„ Signal Change: ${changeColor}Market sentiment has ${changeType}${colors.reset}`); + } else { + console.log(`๐Ÿ”„ Signal Change: ${colors.dim}No change from previous signal${colors.reset}`); + } + + console.log(); + + // Historical Trend Analysis + console.log(`${colors.magenta}๐Ÿ“ˆ TREND ANALYSIS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + if (data.length >= 5) { + const trendAnalysis = this.analyzeMarketTrend(data.slice(0, 5)); + console.log(`๐Ÿ“Š Market Cap Trend (5 days): ${trendAnalysis.mcapTrend.color}${trendAnalysis.mcapTrend.direction} ${trendAnalysis.mcapTrend.strength}${colors.reset}`); + console.log(`โญ Grade Trend (5 days): ${trendAnalysis.gradeTrend.color}${trendAnalysis.gradeTrend.direction} ${trendAnalysis.gradeTrend.strength}${colors.reset}`); + console.log(`๐ŸŽฏ Signal Consistency: ${trendAnalysis.signalConsistency.color}${trendAnalysis.signalConsistency.description}${colors.reset}`); + } + + // Market Health Score + const healthScore = this.calculateMarketHealthScore(latest); + const healthBar = this.getHealthScoreBar(healthScore); + const healthRating = this.getHealthScoreRating(healthScore); + console.log(`๐Ÿฅ Market Health Score: ${healthScore}/100 ${healthBar}`); + console.log(` ${healthRating}`); + + console.log(); + + // Recent Market Data Table + console.log(`${colors.cyan}๐Ÿ“‹ RECENT MARKET DATA${colors.reset}`); + console.log('โ”€'.repeat(80)); + console.log(`${'Date'.padEnd(12)} ${'Market Cap'.padEnd(15)} ${'High Grade %'.padEnd(12)} ${'Signal'.padEnd(8)} ${'Trend'.padEnd(8)}`); + console.log('โ”€'.repeat(80)); + + data.slice(0, 5).forEach((item, index) => { + const date = new Date(item.DATE).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + const mcap = this.formatLargeNumber(item.TOTAL_CRYPTO_MCAP); + const grade = item.TM_GRADE_PERC_HIGH_COINS.toFixed(1) + '%'; + const signalIcon = this.getMarketSignalInfo(item.TM_GRADE_SIGNAL).icon; + + // Calculate trend from previous day + let trendIcon = 'โ”€'; + if (index < data.length - 1) { + const prevMcap = data[index + 1].TOTAL_CRYPTO_MCAP; + const change = ((item.TOTAL_CRYPTO_MCAP - prevMcap) / prevMcap * 100); + trendIcon = change > 0.5 ? '๐Ÿ“ˆ' : change < -0.5 ? '๐Ÿ“‰' : 'โ”€'; + } + + console.log(`${date.padEnd(12)} ${mcap.padEnd(15)} ${grade.padEnd(12)} ${signalIcon.padEnd(8)} ${trendIcon.padEnd(8)}`); + }); + + console.log(); + console.log('โ•'.repeat(80)); + console.log(`๐Ÿ’ก ${colors.bright}Market Insights:${colors.reset}`); + + // Generate market insights + const insights = this.generateMarketInsights(data, latest); + insights.forEach(insight => console.log(` ${insight}`)); + + console.log('โ”€'.repeat(80)); + console.log(`๐Ÿ” Analysis powered by TokenMetrics market intelligence`); + console.log(); + } + + private getMarketGradeBar(percentage: number): string { + const filled = Math.round(percentage / 4); + const empty = 25 - filled; + + if (percentage < 20) return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (percentage < 40) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (percentage < 60) return `${colors.blue}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getMarketGradeRating(percentage: number): string { + if (percentage < 10) return `${colors.red}Very Bearish - Extremely low quality coins${colors.reset}`; + if (percentage < 20) return `${colors.red}Bearish - Low quality dominance${colors.reset}`; + if (percentage < 30) return `${colors.yellow}Cautious - Below average quality${colors.reset}`; + if (percentage < 50) return `${colors.blue}Neutral - Moderate quality distribution${colors.reset}`; + if (percentage < 70) return `${colors.green}Bullish - Good quality coins prevalent${colors.reset}`; + return `${colors.green}Very Bullish - High quality coins dominating${colors.reset}`; + } + + private getMarketSignalInfo(signal: number): { icon: string; text: string; color: string; description: string } { + switch (signal) { + case 1: + return { + icon: '๐ŸŸข', + text: 'BULLISH', + color: colors.green, + description: 'Strong buy signal - Market conditions favorable' + }; + case 0: + return { + icon: '๐ŸŸก', + text: 'NEUTRAL', + color: colors.yellow, + description: 'Hold signal - Market in consolidation' + }; + case -1: + return { + icon: '๐Ÿ”ด', + text: 'BEARISH', + color: colors.red, + description: 'Caution signal - Market showing weakness' + }; + default: + return { + icon: 'โšช', + text: 'UNKNOWN', + color: colors.dim, + description: 'Signal not available' + }; + } + } + + private analyzeMarketTrend(data: any[]): { + mcapTrend: { direction: string; strength: string; color: string }; + gradeTrend: { direction: string; strength: string; color: string }; + signalConsistency: { description: string; color: string }; + } { + // Market Cap Trend + const mcaps = data.map(d => d.TOTAL_CRYPTO_MCAP); + const mcapChanges = mcaps.slice(0, -1).map((mcap, i) => (mcap - mcaps[i + 1]) / mcaps[i + 1] * 100); + const avgMcapChange = mcapChanges.reduce((a, b) => a + b, 0) / mcapChanges.length; + + let mcapTrend; + if (avgMcapChange > 2) mcapTrend = { direction: '๐Ÿ“ˆ Strong Uptrend', strength: '(+' + avgMcapChange.toFixed(1) + '%)', color: colors.green }; + else if (avgMcapChange > 0.5) mcapTrend = { direction: '๐Ÿ“ˆ Uptrend', strength: '(+' + avgMcapChange.toFixed(1) + '%)', color: colors.green }; + else if (avgMcapChange > -0.5) mcapTrend = { direction: 'โžก๏ธ Sideways', strength: '(' + avgMcapChange.toFixed(1) + '%)', color: colors.yellow }; + else if (avgMcapChange > -2) mcapTrend = { direction: '๐Ÿ“‰ Downtrend', strength: '(' + avgMcapChange.toFixed(1) + '%)', color: colors.red }; + else mcapTrend = { direction: '๐Ÿ“‰ Strong Downtrend', strength: '(' + avgMcapChange.toFixed(1) + '%)', color: colors.red }; + + // Grade Trend + const grades = data.map(d => d.TM_GRADE_PERC_HIGH_COINS); + const gradeChanges = grades.slice(0, -1).map((grade, i) => grade - grades[i + 1]); + const avgGradeChange = gradeChanges.reduce((a, b) => a + b, 0) / gradeChanges.length; + + let gradeTrend; + if (avgGradeChange > 5) gradeTrend = { direction: 'โญ Improving', strength: '(+' + avgGradeChange.toFixed(1) + '%)', color: colors.green }; + else if (avgGradeChange > 1) gradeTrend = { direction: 'โญ Slight Improvement', strength: '(+' + avgGradeChange.toFixed(1) + '%)', color: colors.green }; + else if (avgGradeChange > -1) gradeTrend = { direction: 'โžก๏ธ Stable', strength: '(' + avgGradeChange.toFixed(1) + '%)', color: colors.yellow }; + else if (avgGradeChange > -5) gradeTrend = { direction: 'โญ Declining', strength: '(' + avgGradeChange.toFixed(1) + '%)', color: colors.red }; + else gradeTrend = { direction: 'โญ Sharp Decline', strength: '(' + avgGradeChange.toFixed(1) + '%)', color: colors.red }; + + // Signal Consistency + const signals = data.map(d => d.TM_GRADE_SIGNAL); + const uniqueSignals = [...new Set(signals)].length; + + let signalConsistency; + if (uniqueSignals === 1) { + const signal = signals[0]; + const signalText = signal === 1 ? 'Bullish' : signal === 0 ? 'Neutral' : 'Bearish'; + signalConsistency = { description: `Consistent ${signalText}`, color: colors.green }; + } else if (uniqueSignals === 2) { + signalConsistency = { description: 'Moderate Volatility', color: colors.yellow }; + } else { + signalConsistency = { description: 'High Volatility', color: colors.red }; + } + + return { mcapTrend, gradeTrend, signalConsistency }; + } + + private calculateMarketHealthScore(data: any): number { + let score = 50; // Base score + + // High grade percentage contribution (0-30 points) + const gradeScore = Math.min(data.TM_GRADE_PERC_HIGH_COINS * 0.6, 30); + score += gradeScore; + + // Signal contribution (0-20 points) + if (data.TM_GRADE_SIGNAL === 1) score += 20; + else if (data.TM_GRADE_SIGNAL === 0) score += 10; + // Bearish signal adds 0 points + + return Math.round(Math.min(score, 100)); + } + + private getHealthScoreBar(score: number): string { + const filled = Math.round(score / 4); + const empty = 25 - filled; + + if (score < 30) return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (score < 50) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (score < 70) return `${colors.blue}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getHealthScoreRating(score: number): string { + if (score < 30) return `${colors.red}Poor - High risk market conditions${colors.reset}`; + if (score < 50) return `${colors.yellow}Fair - Cautious approach recommended${colors.reset}`; + if (score < 70) return `${colors.blue}Good - Moderate market conditions${colors.reset}`; + if (score < 85) return `${colors.green}Excellent - Favorable market environment${colors.reset}`; + return `${colors.green}Outstanding - Optimal market conditions${colors.reset}`; + } + + private generateMarketInsights(data: any[], latest: any): string[] { + const insights: string[] = []; + + // Market cap insight + if (data.length >= 2) { + const change = ((latest.TOTAL_CRYPTO_MCAP - data[1].TOTAL_CRYPTO_MCAP) / data[1].TOTAL_CRYPTO_MCAP * 100); + if (Math.abs(change) > 5) { + const direction = change > 0 ? 'surged' : 'declined'; + insights.push(`๐Ÿ“Š Market cap has ${direction} by ${Math.abs(change).toFixed(1)}% in the last day`); + } + } + + // High grade coins insight + if (latest.TM_GRADE_PERC_HIGH_COINS > 60) { + insights.push(`โญ Strong market quality with ${latest.TM_GRADE_PERC_HIGH_COINS.toFixed(1)}% high-grade coins`); + } else if (latest.TM_GRADE_PERC_HIGH_COINS < 20) { + insights.push(`โš ๏ธ Market quality concerns with only ${latest.TM_GRADE_PERC_HIGH_COINS.toFixed(1)}% high-grade coins`); + } + + // Signal insight + if (latest.TM_GRADE_SIGNAL !== latest.LAST_TM_GRADE_SIGNAL) { + const change = latest.TM_GRADE_SIGNAL > latest.LAST_TM_GRADE_SIGNAL ? 'improved' : 'deteriorated'; + insights.push(`๐ŸŽฏ Market signal has ${change} from previous reading`); + } + + // Trend insight + if (data.length >= 5) { + const recentMcaps = data.slice(0, 3).map(d => d.TOTAL_CRYPTO_MCAP); + const isUptrend = recentMcaps[0] > recentMcaps[1] && recentMcaps[1] > recentMcaps[2]; + const isDowntrend = recentMcaps[0] < recentMcaps[1] && recentMcaps[1] < recentMcaps[2]; + + if (isUptrend) { + insights.push(`๐Ÿ“ˆ Market showing consistent upward momentum over 3 days`); + } else if (isDowntrend) { + insights.push(`๐Ÿ“‰ Market experiencing downward pressure over 3 days`); + } + } + + // Health score insight + const healthScore = this.calculateMarketHealthScore(latest); + if (healthScore > 80) { + insights.push(`๐Ÿฅ Market health is excellent (${healthScore}/100) - favorable for investments`); + } else if (healthScore < 40) { + insights.push(`๐Ÿฅ Market health is concerning (${healthScore}/100) - exercise caution`); + } + + return insights.length > 0 ? insights : ['๐Ÿ“Š Market data analysis complete - monitor for changes']; + } + + private async getQuantmetrics(prompt: string): Promise { + try { + console.log(`${colors.dim} ๐Ÿ” Getting quantitative metrics...${colors.reset}`); + + // Try to extract token information from prompt, but don't require it + const lowerPrompt = prompt.toLowerCase(); + let searchParams: any = { + limit: "10", + page: "1" + }; + + // Check if user specified a particular token + const tokenIds = await this.extractTokensFromPrompt(prompt); + if (tokenIds.length > 0) { + searchParams.token_id = tokenIds[0]; + console.log(`${colors.dim} ๐ŸŽฏ Analyzing quantmetrics for token ID: ${tokenIds[0]}${colors.reset}`); + } else { + console.log(`${colors.dim} ๐Ÿ“Š Getting general quantmetrics data...${colors.reset}`); + } + + // Check for specific symbols in the prompt + if (lowerPrompt.includes('bitcoin') || lowerPrompt.includes('btc')) { + searchParams.symbol = 'BTC'; + } else if (lowerPrompt.includes('ethereum') || lowerPrompt.includes('eth')) { + searchParams.symbol = 'ETH'; + } + + // Check for category filters + if (lowerPrompt.includes('defi')) { + searchParams.category = 'defi'; + } else if (lowerPrompt.includes('layer-1') || lowerPrompt.includes('layer1')) { + searchParams.category = 'layer-1'; + } + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getQuantmetrics.executable( + searchParams, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Extract and format the data properly using the same pattern as other functions + console.log(`${colors.dim} โœ… Quantmetrics data retrieved successfully${colors.reset}`); + + // Parse the response to extract quantmetrics data + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatQuantmetricsResponse(responseData.data); + } else { + this.formatResponse("No quantmetrics data available at the moment.", 'data'); + } + } else { + this.formatResponse("Quantmetrics data retrieved successfully! Check the detailed data above for advanced risk and performance analytics.", 'data'); + } + } catch (parseError) { + this.formatResponse("Quantmetrics data retrieved successfully! Check the detailed data above for advanced risk and performance analytics.", 'data'); + } + } else { + this.formatResponse(result.feedback || "Unable to retrieve quantmetrics data", 'error'); + } + } catch (error: any) { + console.error(`${colors.dim} โŒ Quantmetrics error:${colors.reset}`, error); + + if (error.response?.status === 413) { + this.formatResponse("โŒ Request too large. Try being more specific with your query (e.g., 'Bitcoin quantmetrics' or 'DeFi quantmetrics')", 'error'); + } else if (error.response?.status === 400) { + this.formatResponse("โŒ Invalid request parameters. Try: 'quantmetrics', 'Bitcoin quantmetrics', or 'DeFi quantmetrics'", 'error'); + } else { + this.formatResponse(`โŒ Failed to get quantmetrics: ${error.message || error}`, 'error'); + } + } + } + + private formatQuantmetricsResponse(data: any[]): void { + if (!data || data.length === 0) return; + + console.log(`${colors.cyan}${colors.bright}๐Ÿ“Š QUANTITATIVE METRICS ANALYSIS [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ”ฌ Advanced Risk & Performance Analytics for ${data.length} Tokens${colors.reset}\n`); + + // Show summary table first + console.log(`${colors.bright}๐Ÿ“‹ SUMMARY OVERVIEW${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}${'Token'.padEnd(20)} ${'Sharpe'.padEnd(8)} ${'Return'.padEnd(10)} ${'Risk'.padEnd(12)} ${'Grade'.padEnd(8)}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + + data.slice(0, 10).forEach((metrics, index) => { + const tokenName = (metrics.TOKEN_NAME || 'Unknown').substring(0, 18); + const tokenSymbol = metrics.TOKEN_SYMBOL || 'N/A'; + const sharpe = metrics.SHARPE || 0; + const allTimeReturn = ((metrics.ALL_TIME_RETURN || 0) * 100); + const volatility = ((metrics.VOLATILITY || 0) * 100); + + // Calculate overall grade + let grade = 'F'; + let gradeColor = colors.red; + if (sharpe > 2) { grade = 'A+'; gradeColor = colors.green; } + else if (sharpe > 1.5) { grade = 'A'; gradeColor = colors.green; } + else if (sharpe > 1) { grade = 'B+'; gradeColor = colors.yellow; } + else if (sharpe > 0.5) { grade = 'B'; gradeColor = colors.yellow; } + else if (sharpe > 0) { grade = 'C'; gradeColor = colors.yellow; } + else { grade = 'D'; gradeColor = colors.red; } + + const sharpeColor = sharpe > 1 ? colors.green : sharpe > 0 ? colors.yellow : colors.red; + const returnColor = allTimeReturn > 0 ? colors.green : colors.red; + const riskColor = volatility > 150 ? colors.red : volatility > 100 ? colors.yellow : colors.green; + + const displayName = `${tokenName} (${tokenSymbol})`.padEnd(20); + const displaySharpe = `${sharpeColor}${sharpe.toFixed(2)}${colors.reset}`.padEnd(15); + const displayReturn = `${returnColor}${allTimeReturn.toFixed(1)}%${colors.reset}`.padEnd(17); + const displayRisk = `${riskColor}${volatility.toFixed(0)}%${colors.reset}`.padEnd(19); + const displayGrade = `${gradeColor}${grade}${colors.reset}`; + + console.log(`${displayName} ${displaySharpe} ${displayReturn} ${displayRisk} ${displayGrade}`); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}\n`); + + // Show detailed analysis for top 3 tokens by Sharpe ratio + console.log(`${colors.bright}๐Ÿ” DETAILED ANALYSIS - TOP 3 PERFORMERS${colors.reset}`); + console.log(`${colors.dim}${'โ•'.repeat(80)}${colors.reset}\n`); + + // Sort by Sharpe ratio for top performers + const sortedData = [...data].sort((a, b) => (b.SHARPE || -999) - (a.SHARPE || -999)); + + sortedData.slice(0, 3).forEach((metrics, index) => { + const tokenName = metrics.TOKEN_NAME || 'Unknown'; + const tokenSymbol = metrics.TOKEN_SYMBOL || 'N/A'; + const date = metrics.DATE || new Date().toISOString(); + const volatility = (metrics.VOLATILITY || 0) * 100; + const allTimeReturn = (metrics.ALL_TIME_RETURN || 0) * 100; + const cagr = (metrics.CAGR || 0) * 100; + const sharpe = metrics.SHARPE || 0; + const sortino = metrics.SORTINO || 0; + const maxDrawdown = Math.abs((metrics.MAX_DRAWDOWN || 0) * 100); + const skew = metrics.SKEW || 0; + const kurtosis = metrics.KURTOSIS || 0; + const profitFactor = metrics.PROFIT_FACTOR || 0; + const dailyVar = Math.abs((metrics.DAILY_VALUE_AT_RISK || 0) * 100); + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“… Analysis Date: ${new Date(date).toLocaleDateString()}${colors.reset}\n`); + + // Risk Metrics + console.log(`${colors.red}โš ๏ธ RISK METRICS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + console.log(`๐Ÿ“ˆ Volatility: ${volatility.toFixed(2)}% ${this.getVolatilityBar(volatility/100)}`); + console.log(` ${this.getVolatilityDescription(volatility/100)}`); + console.log(`๐Ÿ“‰ Max Drawdown: -${maxDrawdown.toFixed(2)}% ${this.getDrawdownBar(-maxDrawdown/100)}`); + console.log(` ${this.getDrawdownDescription(-maxDrawdown/100)}`); + console.log(`โšก Daily VaR (95%): -${dailyVar.toFixed(2)}%`); + console.log(` Expected maximum daily loss in 95% of cases\n`); + + // Performance Metrics + console.log(`${colors.green}๐Ÿ“ˆ PERFORMANCE METRICS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + console.log(`๐Ÿš€ All-Time Return: ${allTimeReturn.toFixed(2)}% ${this.getReturnBar(allTimeReturn)}`); + console.log(`๐Ÿ“Š CAGR: ${cagr.toFixed(2)}% ${this.getCAGRBar(cagr/100)}`); + console.log(` Compound Annual Growth Rate - average yearly return\n`); + + // Risk-Adjusted Returns + console.log(`${colors.yellow}โš–๏ธ RISK-ADJUSTED RETURNS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + console.log(`๐Ÿ“ Sharpe Ratio: ${sharpe.toFixed(2)} ${this.getSharpeBar(sharpe)} ${this.getSharpeRating(sharpe)}`); + console.log(` Risk-adjusted return (>1.0 is good, >2.0 is excellent)`); + console.log(`๐Ÿ“Š Sortino Ratio: ${sortino.toFixed(2)} ${this.getSortinoBar(sortino)} ${this.getSortinoRating(sortino)}`); + console.log(` Downside risk-adjusted return (focuses on negative volatility)\n`); + + // Advanced Statistics + console.log(`${colors.magenta}๐Ÿ”ฌ ADVANCED STATISTICS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + console.log(`๐Ÿ“Š Skewness: ${skew.toFixed(3)} - ${this.getSkewDescription(skew)}`); + console.log(`๐Ÿ“ˆ Kurtosis: ${kurtosis.toFixed(2)} - ${this.getKurtosisDescription(kurtosis)}`); + if (metrics.TAIL_RATIO) { + console.log(`๐ŸŽฏ Tail Ratio: ${metrics.TAIL_RATIO.toFixed(2)}`); + console.log(` Ratio of upside to downside tail risks`); + } + console.log(); + + // Trading Performance + console.log(`${colors.cyan}๐Ÿ’ฐ TRADING PERFORMANCE${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + console.log(`๐Ÿ’Ž Profit Factor: ${profitFactor.toFixed(2)} ${this.getProfitFactorBar(profitFactor)} ${this.getProfitFactorRating(profitFactor)}`); + console.log(` Ratio of gross profit to gross loss (>1.0 means profitable)`); + + // Generate insights + const insights = this.generateQuantmetricsInsights(metrics); + if (insights.length > 0) { + console.log(`\n${colors.bright}๐Ÿ’ก KEY INSIGHTS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + insights.forEach(insight => { + console.log(` ${insight}`); + }); + } + + if (index < 2) console.log(`\n${colors.dim}${'โ•'.repeat(80)}${colors.reset}\n`); + }); + + // Summary insights + console.log(`\n${colors.bright}๐Ÿ“Š PORTFOLIO INSIGHTS${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + + const avgSharpe = data.reduce((sum, d) => sum + (d.SHARPE || 0), 0) / data.length; + const avgReturn = data.reduce((sum, d) => sum + ((d.ALL_TIME_RETURN || 0) * 100), 0) / data.length; + const avgVolatility = data.reduce((sum, d) => sum + ((d.VOLATILITY || 0) * 100), 0) / data.length; + const profitableTokens = data.filter(d => (d.PROFIT_FACTOR || 0) > 1).length; + + console.log(`๐ŸŽฏ Average Sharpe Ratio: ${colors.cyan}${avgSharpe.toFixed(2)}${colors.reset} (${avgSharpe > 1 ? 'Good' : avgSharpe > 0 ? 'Fair' : 'Poor'})`); + console.log(`๐Ÿ“ˆ Average Return: ${avgReturn >= 0 ? colors.green : colors.red}${avgReturn.toFixed(2)}%${colors.reset}`); + console.log(`โšก Average Volatility: ${colors.yellow}${avgVolatility.toFixed(1)}%${colors.reset}`); + console.log(`๐Ÿ’ฐ Profitable Tokens: ${colors.green}${profitableTokens}/${data.length}${colors.reset} (${((profitableTokens/data.length)*100).toFixed(1)}%)`); + + console.log(`\n${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Quantmetrics provide advanced risk and performance analytics${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Focus on tokens with Sharpe > 1.0 and positive returns${colors.reset}`); + console.log(`${colors.dim}โš ๏ธ High volatility tokens require careful risk management${colors.reset}`); + console.log(); + } + + private getVolatilityBar(volatility: number): string { + const percentage = Math.min(volatility * 100, 100); + const filled = Math.round(percentage / 4); + const empty = 25 - filled; + + if (volatility < 0.3) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (volatility < 0.6) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getVolatilityDescription(volatility: number): string { + if (volatility < 0.2) return "Low volatility - Stable price movements"; + if (volatility < 0.4) return "Moderate volatility - Normal crypto fluctuations"; + if (volatility < 0.8) return "High volatility - Significant price swings"; + return "Extreme volatility - Very risky asset"; + } + + private getDrawdownBar(drawdown: number): string { + const percentage = Math.abs(drawdown * 100); + const filled = Math.min(Math.round(percentage / 4), 25); + const empty = 25 - filled; + + if (percentage < 20) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (percentage < 50) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getDrawdownDescription(drawdown: number): string { + const percentage = Math.abs(drawdown * 100); + if (percentage < 20) return "Low drawdown - Good downside protection"; + if (percentage < 50) return "Moderate drawdown - Typical crypto risk"; + if (percentage < 80) return "High drawdown - Significant losses possible"; + return "Extreme drawdown - Very high risk of large losses"; + } + + private getReturnBar(returnValue: number): string { + const percentage = Math.min(Math.max(returnValue, 0), 10000); + const filled = Math.min(Math.round(percentage / 400), 25); + const empty = 25 - filled; + + if (returnValue > 1000) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (returnValue > 100) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getCAGRBar(cagr: number): string { + const percentage = Math.max(cagr * 100, 0); + const filled = Math.min(Math.round(percentage / 4), 25); + const empty = 25 - filled; + + if (cagr > 0.5) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (cagr > 0.2) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getSharpeBar(sharpe: number): string { + const filled = Math.min(Math.max(Math.round(sharpe * 8), 0), 25); + const empty = 25 - filled; + + if (sharpe > 2) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (sharpe > 1) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getSharpeRating(sharpe: number): string { + if (sharpe > 3) return `${colors.green}๐ŸŒŸ EXCELLENT${colors.reset}`; + if (sharpe > 2) return `${colors.green}โœ… VERY GOOD${colors.reset}`; + if (sharpe > 1) return `${colors.yellow}๐Ÿ‘ GOOD${colors.reset}`; + if (sharpe > 0) return `${colors.yellow}โš ๏ธ FAIR${colors.reset}`; + return `${colors.red}โŒ POOR${colors.reset}`; + } + + private getSortinoBar(sortino: number): string { + const filled = Math.min(Math.max(Math.round(sortino * 6), 0), 25); + const empty = 25 - filled; + + if (sortino > 2) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (sortino > 1) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getSortinoRating(sortino: number): string { + if (sortino > 3) return `${colors.green}๐ŸŒŸ EXCELLENT${colors.reset}`; + if (sortino > 2) return `${colors.green}โœ… VERY GOOD${colors.reset}`; + if (sortino > 1) return `${colors.yellow}๐Ÿ‘ GOOD${colors.reset}`; + if (sortino > 0) return `${colors.yellow}โš ๏ธ FAIR${colors.reset}`; + return `${colors.red}โŒ POOR${colors.reset}`; + } + + private getProfitFactorBar(profitFactor: number): string { + const filled = Math.min(Math.max(Math.round(profitFactor * 10), 0), 25); + const empty = 25 - filled; + + if (profitFactor > 2) return `${colors.green}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + if (profitFactor > 1) return `${colors.yellow}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + return `${colors.red}${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}${colors.reset}`; + } + + private getProfitFactorRating(profitFactor: number): string { + if (profitFactor > 2) return `${colors.green}๐ŸŒŸ EXCELLENT${colors.reset}`; + if (profitFactor > 1.5) return `${colors.green}โœ… VERY GOOD${colors.reset}`; + if (profitFactor > 1) return `${colors.yellow}๐Ÿ‘ PROFITABLE${colors.reset}`; + return `${colors.red}โŒ UNPROFITABLE${colors.reset}`; + } + + private getSkewDescription(skew: number): string { + if (skew > 0.5) return "Positive skew - More upside potential"; + if (skew > -0.5) return "Normal distribution - Balanced risk"; + return "Negative skew - More downside risk"; + } + + private getKurtosisDescription(kurtosis: number): string { + if (kurtosis > 10) return "Very high kurtosis - Extreme price movements common"; + if (kurtosis > 3) return "High kurtosis - Fat tails, more extreme events"; + return "Normal kurtosis - Standard distribution"; + } + + private generateQuantmetricsInsights(metrics: any): string[] { + const insights: string[] = []; + + // Sharpe ratio insights + if (metrics.SHARPE > 2) { + insights.push(`๐ŸŒŸ Excellent risk-adjusted returns (Sharpe: ${metrics.SHARPE.toFixed(2)})`); + } else if (metrics.SHARPE < 0) { + insights.push(`โš ๏ธ Poor risk-adjusted performance - consider risk management`); + } + + // Volatility insights + if (metrics.VOLATILITY > 0.8) { + insights.push(`โšก Extremely volatile asset - suitable for risk-tolerant traders only`); + } else if (metrics.VOLATILITY < 0.3) { + insights.push(`๐Ÿ›ก๏ธ Relatively stable for crypto - good for conservative portfolios`); + } + + // Drawdown insights + if (Math.abs(metrics.MAX_DRAWDOWN) > 0.8) { + insights.push(`๐Ÿ“‰ High maximum drawdown - prepare for significant potential losses`); + } + + // Profit factor insights + if (metrics.PROFIT_FACTOR > 1.5) { + insights.push(`๐Ÿ’ฐ Strong profit factor indicates good trading profitability`); + } else if (metrics.PROFIT_FACTOR < 1) { + insights.push(`โŒ Profit factor below 1.0 - losses exceed profits historically`); + } + + // CAGR insights + if (metrics.CAGR > 1) { + insights.push(`๐Ÿš€ Exceptional compound annual growth rate of ${(metrics.CAGR * 100).toFixed(1)}%`); + } + + // Skew insights + if (metrics.SKEW < -0.5) { + insights.push(`โš ๏ธ Negative skew suggests higher probability of large losses`); + } else if (metrics.SKEW > 0.5) { + insights.push(`๐Ÿ“ˆ Positive skew indicates potential for large gains`); + } + + if (insights.length === 0) { + insights.push(`๐Ÿ“Š Mixed performance metrics - analyze individual ratios for investment decisions`); + } + + return insights; + } + + private async getHourlyOhlcv(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + const primaryTokenId = tokenIds[0]; // Use the first detected token for OHLCV data + + console.log(`${colors.dim} ๐ŸŽฏ Getting hourly OHLCV data for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getHourlyOhlcv.executable( + { token_id: primaryTokenId, limit: "24", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the OHLCV data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatOhlcvResponse(responseData.data, 'hourly'); + } else { + this.formatResponse("No hourly OHLCV data available for the requested token.", 'error'); + } + } else { + this.formatResponse("Hourly OHLCV data retrieved successfully! Check the detailed analysis above for price and volume insights.", 'data'); + } + } catch (parseError) { + this.formatResponse("Hourly OHLCV data retrieved successfully! Check the detailed analysis above for price and volume insights.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Hourly OHLCV query failed: ${error}`, 'error'); + } + } + + private async getDailyOhlcv(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + const primaryTokenId = tokenIds[0]; // Use the first detected token for OHLCV data + + console.log(`${colors.dim} ๐ŸŽฏ Getting daily OHLCV data for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getDailyOhlcv.executable( + { token_id: primaryTokenId, limit: "30", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the OHLCV data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + const data = responseData.data; + const dataLength = data.length; + const tokenName = data[0].TOKEN_NAME || 'Unknown'; + + if (dataLength < 30) { + console.log(`${colors.yellow}โš ๏ธ Data Availability Notice:${colors.reset}`); + console.log(`${colors.yellow} Only ${dataLength} day(s) of daily OHLCV data available for ${tokenName}${colors.reset}`); + console.log(`${colors.yellow} Requested 30 days but TokenMetrics API returned ${dataLength} day(s)${colors.reset}`); + console.log(`${colors.dim} This may be due to limited historical data availability for this token${colors.reset}`); + console.log(); + } + + this.formatOhlcvResponse(data, 'daily'); + } else { + this.formatResponse("No daily OHLCV data available for the requested token.", 'error'); + } + } else { + this.formatResponse("Daily OHLCV data retrieved successfully! Check the detailed analysis above for price and volume insights.", 'data'); + } + } catch (parseError) { + this.formatResponse("Daily OHLCV data retrieved successfully! Check the detailed analysis above for price and volume insights.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Daily OHLCV query failed: ${error}`, 'error'); + } + } + + private formatOhlcvResponse(data: any[], timeframe: 'hourly' | 'daily'): void { + if (!data || data.length === 0) return; + + const tokenName = data[0].TOKEN_NAME || 'Unknown'; + const tokenSymbol = data[0].TOKEN_SYMBOL || 'N/A'; + const timeframeName = timeframe === 'hourly' ? 'Hourly' : 'Daily'; + const period = timeframe === 'hourly' ? '24 Hours' : '30 Days'; + + console.log(`${colors.cyan}${colors.bright}๐Ÿ“Š ${timeframeName} OHLCV Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(85)}${colors.reset}`); + console.log(`${colors.bright}๐Ÿ“ˆ ${tokenName} (${tokenSymbol}) - ${period} Price & Volume Data${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“… Data Points: ${data.length} ${timeframe} candles | ๐Ÿ”„ Last Updated: ${new Date().toLocaleString()}${colors.reset}`); + console.log(); + + // Sort data by date (most recent first) + const sortedData = data.sort((a: any, b: any) => new Date(b.TIMESTAMP || b.DATE).getTime() - new Date(a.TIMESTAMP || a.DATE).getTime()); + + // Calculate key metrics + const prices = sortedData.map((d: any) => d.CLOSE); + const volumes = sortedData.map((d: any) => d.VOLUME); + const highs = sortedData.map((d: any) => d.HIGH); + const lows = sortedData.map((d: any) => d.LOW); + const opens = sortedData.map((d: any) => d.OPEN); + + const currentPrice = prices[0]; + const oldestPrice = prices[prices.length - 1]; + const highestPrice = Math.max(...highs); + const lowestPrice = Math.min(...lows); + const avgVolume = volumes.reduce((sum: number, vol: number) => sum + vol, 0) / volumes.length; + const totalVolume = volumes.reduce((sum: number, vol: number) => sum + vol, 0); + + const priceChange = currentPrice - oldestPrice; + const priceChangePercent = (priceChange / oldestPrice) * 100; + + // Calculate moving averages + const ma5 = this.calculateMovingAverage(prices, 5); + const ma10 = this.calculateMovingAverage(prices, 10); + const ma20 = this.calculateMovingAverage(prices, 20); + + // Calculate support and resistance levels + const supportResistance = this.calculateSupportResistance(highs, lows); + + // Show enhanced price summary with visual indicators + console.log(`${colors.green}${colors.bright}๐Ÿ’ฐ PRICE OVERVIEW${colors.reset}`); + console.log(`${colors.green}${'โ”€'.repeat(60)}${colors.reset}`); + + const priceBar = this.getPricePositionBar(currentPrice, lowestPrice, highestPrice); + console.log(`๐Ÿ“Š Current Price: ${colors.bright}$${this.formatPrice(currentPrice)}${colors.reset}`); + console.log(`๐Ÿ“ˆ ${period} Range: ${priceBar}`); + console.log(` ${colors.dim}Low: $${this.formatPrice(lowestPrice)} โ†โ†’ High: $${this.formatPrice(highestPrice)}${colors.reset}`); + + const changeColor = priceChange >= 0 ? colors.green : colors.red; + const changeSymbol = priceChange >= 0 ? '๐Ÿ“ˆ' : '๐Ÿ“‰'; + const changeBar = this.getChangeBar(priceChangePercent); + console.log(`${changeSymbol} ${period} Change: ${changeColor}${priceChange >= 0 ? '+' : ''}$${this.formatPrice(priceChange)} (${priceChangePercent.toFixed(2)}%)${colors.reset}`); + console.log(` ${changeBar}`); + + const volatility = ((highestPrice - lowestPrice) / lowestPrice * 100); + const volatilityBar = this.getVolatilityBar(volatility); + console.log(`โšก Volatility: ${volatility.toFixed(2)}% ${volatilityBar}`); + console.log(); + + // Enhanced moving averages section + console.log(`${colors.yellow}${colors.bright}๐Ÿ“ˆ MOVING AVERAGES & TREND${colors.reset}`); + console.log(`${colors.yellow}${'โ”€'.repeat(60)}${colors.reset}`); + console.log(`๐Ÿ“Š MA5: $${this.formatPrice(ma5)} ${this.getTrendIndicator(currentPrice, ma5)}`); + console.log(`๐Ÿ“Š MA10: $${this.formatPrice(ma10)} ${this.getTrendIndicator(currentPrice, ma10)}`); + console.log(`๐Ÿ“Š MA20: $${this.formatPrice(ma20)} ${this.getTrendIndicator(currentPrice, ma20)}`); + + const trendSignal = this.getTrendSignal(currentPrice, ma5, ma10, ma20); + console.log(`๐ŸŽฏ Trend Signal: ${trendSignal}`); + console.log(); + + // Enhanced volume analysis with visual bars + console.log(`${colors.blue}${colors.bright}๐Ÿ“ฆ VOLUME ANALYSIS${colors.reset}`); + console.log(`${colors.blue}${'โ”€'.repeat(60)}${colors.reset}`); + const maxVolume = Math.max(...volumes); + const minVolume = Math.min(...volumes); + const currentVolume = volumes[0]; + + console.log(`๐Ÿ“ฆ Current Volume: ${this.formatLargeNumber(currentVolume)}`); + console.log(`๐Ÿ“Š Volume Range: ${this.getVolumeBar(currentVolume, minVolume, maxVolume)}`); + console.log(` ${colors.dim}Min: ${this.formatLargeNumber(minVolume)} โ†โ†’ Max: ${this.formatLargeNumber(maxVolume)}${colors.reset}`); + console.log(`๐Ÿ“ˆ Total Volume: ${this.formatLargeNumber(totalVolume)}`); + console.log(`๐Ÿ“Š Average Volume: ${this.formatLargeNumber(avgVolume)}`); + + const volumeTrend = this.getVolumeTrend(volumes.slice(0, 5), avgVolume); + console.log(`๐Ÿ”ฅ Volume Trend: ${volumeTrend}`); + console.log(); + + // Support and Resistance levels + console.log(`${colors.magenta}${colors.bright}๐ŸŽฏ SUPPORT & RESISTANCE${colors.reset}`); + console.log(`${colors.magenta}${'โ”€'.repeat(60)}${colors.reset}`); + console.log(`๐Ÿ›ก๏ธ Support Level: ${colors.green}$${this.formatPrice(supportResistance.support)}${colors.reset} ${this.getLevelDistance(currentPrice, supportResistance.support, 'support')}`); + console.log(`โšก Resistance Level: ${colors.red}$${this.formatPrice(supportResistance.resistance)}${colors.reset} ${this.getLevelDistance(currentPrice, supportResistance.resistance, 'resistance')}`); + console.log(); + + // Enhanced recent candles with better formatting + const recentCount = timeframe === 'hourly' ? 8 : 7; + const recentData = sortedData.slice(0, recentCount); + + console.log(`${colors.cyan}${colors.bright}๐Ÿ“… RECENT ${timeframeName.toUpperCase()} CANDLES${colors.reset}`); + console.log(`${colors.cyan}${'โ”€'.repeat(85)}${colors.reset}`); + console.log(`${'Time'.padEnd(14)} โ”‚ ${'Open'.padStart(10)} โ”‚ ${'High'.padStart(10)} โ”‚ ${'Low'.padStart(10)} โ”‚ ${'Close'.padStart(10)} โ”‚ ${'Volume'.padStart(10)} โ”‚ ${'Change'.padStart(8)}`); + console.log(`${'โ”€'.repeat(85)}`); + + recentData.forEach((candle: any, index: number) => { + const date = new Date(candle.TIMESTAMP || candle.DATE); + const dateStr = timeframe === 'hourly' + ? `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:00` + : `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}/${date.getFullYear().toString().slice(-2)}`; + + const open = this.formatPrice(candle.OPEN); + const high = this.formatPrice(candle.HIGH); + const low = this.formatPrice(candle.LOW); + const close = this.formatPrice(candle.CLOSE); + const volume = this.formatLargeNumber(candle.VOLUME); + + const candleChange = ((candle.CLOSE - candle.OPEN) / candle.OPEN * 100); + const changeStr = `${candleChange >= 0 ? '+' : ''}${candleChange.toFixed(1)}%`; + + // Enhanced color coding and symbols + const candleColor = candle.CLOSE >= candle.OPEN ? colors.green : colors.red; + const candleSymbol = candle.CLOSE >= candle.OPEN ? '๐ŸŸข' : '๐Ÿ”ด'; + const isCurrentCandle = index === 0 ? 'โญ' : ' '; + + console.log(`${isCurrentCandle}${candleSymbol} ${dateStr.padEnd(12)} โ”‚ ${candleColor}$${open.padStart(9)}${colors.reset} โ”‚ ${candleColor}$${high.padStart(9)}${colors.reset} โ”‚ ${candleColor}$${low.padStart(9)}${colors.reset} โ”‚ ${candleColor}$${close.padStart(9)}${colors.reset} โ”‚ ${volume.padStart(10)} โ”‚ ${candleColor}${changeStr.padStart(7)}${colors.reset}`); + }); + + console.log(); + + // Enhanced trading insights with actionable recommendations + console.log(`${colors.cyan}${colors.bright}๐Ÿ’ก TRADING INSIGHTS & ANALYSIS${colors.reset}`); + console.log(`${colors.cyan}${'โ”€'.repeat(60)}${colors.reset}`); + + const insights = this.generateOhlcvInsights(sortedData, timeframe); + insights.forEach(insight => console.log(` ${insight}`)); + console.log(); + + // Trading recommendations + const recommendation = this.generateTradingRecommendation(currentPrice, ma5, ma10, ma20, supportResistance, priceChangePercent, volatility); + console.log(`${colors.yellow}${colors.bright}๐ŸŽฏ TRADING RECOMMENDATION${colors.reset}`); + console.log(`${colors.yellow}${'โ”€'.repeat(60)}${colors.reset}`); + console.log(` ${recommendation.signal}`); + console.log(` ${recommendation.reasoning}`); + console.log(` ${recommendation.riskLevel}`); + console.log(); + + // Enhanced trend analysis with visual indicators + console.log(`${colors.magenta}${colors.bright}๐Ÿ“ˆ COMPREHENSIVE TREND ANALYSIS${colors.reset}`); + console.log(`${colors.magenta}${'โ”€'.repeat(60)}${colors.reset}`); + + const trendAnalysis = this.analyzePriceTrend(sortedData); + console.log(`๐Ÿ“Š Overall Trend: ${trendAnalysis.trend}`); + console.log(`๐ŸŽฏ Trend Strength: ${trendAnalysis.strength}`); + console.log(`๐Ÿ“ˆ Bullish Candles: ${trendAnalysis.bullishCount}/${data.length} ${this.getCandleBar(trendAnalysis.bullishCount, data.length, 'bullish')}`); + console.log(`๐Ÿ“‰ Bearish Candles: ${trendAnalysis.bearishCount}/${data.length} ${this.getCandleBar(trendAnalysis.bearishCount, data.length, 'bearish')}`); + + // Market sentiment based on technical indicators + const marketSentiment = this.calculateMarketSentiment(trendAnalysis, priceChangePercent, volatility, currentPrice, ma5, ma10); + console.log(`๐ŸŽญ Market Sentiment: ${marketSentiment}`); + + console.log(); + console.log(`${colors.cyan}${'โ•'.repeat(85)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Analysis powered by TokenMetrics OHLCV data | ๐Ÿ”„ Auto-refresh available${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Tip: Try "Bitcoin daily data" or "Ethereum hourly data" for different timeframes${colors.reset}`); + console.log(); + } + + // Helper methods for enhanced OHLCV formatting + private calculateMovingAverage(prices: number[], period: number): number { + if (prices.length < period) return prices[0] || 0; + const slice = prices.slice(0, period); + return slice.reduce((sum, price) => sum + price, 0) / period; + } + + private calculateSupportResistance(highs: number[], lows: number[]): { support: number; resistance: number } { + // Simple support/resistance calculation based on recent highs and lows + const recentHighs = highs.slice(0, Math.min(10, highs.length)); + const recentLows = lows.slice(0, Math.min(10, lows.length)); + + const resistance = Math.max(...recentHighs); + const support = Math.min(...recentLows); + + return { support, resistance }; + } + + private getPricePositionBar(current: number, low: number, high: number): string { + const range = high - low; + const position = (current - low) / range; + const barLength = 30; + const filledLength = Math.round(position * barLength); + + const bar = 'โ–ˆ'.repeat(filledLength) + 'โ–‘'.repeat(barLength - filledLength); + const percentage = (position * 100).toFixed(1); + + return `${colors.dim}[${colors.reset}${colors.cyan}${bar}${colors.reset}${colors.dim}] ${percentage}%${colors.reset}`; + } + + private getChangeBar(changePercent: number): string { + const isPositive = changePercent >= 0; + const absChange = Math.abs(changePercent); + const barLength = Math.min(Math.round(absChange / 2), 20); // Scale bar length + + const color = isPositive ? colors.green : colors.red; + const symbol = isPositive ? 'โ–ฒ' : 'โ–ผ'; + const bar = symbol.repeat(Math.max(1, barLength)); + + return `${colors.dim}[${color}${bar}${colors.reset}${colors.dim}]${colors.reset}`; + } + + private getTrendIndicator(currentPrice: number, ma: number): string { + const diff = ((currentPrice - ma) / ma * 100); + if (diff > 2) return `${colors.green}๐Ÿ“ˆ +${diff.toFixed(1)}%${colors.reset}`; + if (diff < -2) return `${colors.red}๐Ÿ“‰ ${diff.toFixed(1)}%${colors.reset}`; + return `${colors.yellow}โžก๏ธ ${diff.toFixed(1)}%${colors.reset}`; + } + + private getTrendSignal(current: number, ma5: number, ma10: number, ma20: number): string { + const above5 = current > ma5; + const above10 = current > ma10; + const above20 = current > ma20; + const ma5Above10 = ma5 > ma10; + const ma10Above20 = ma10 > ma20; + + if (above5 && above10 && above20 && ma5Above10 && ma10Above20) { + return `${colors.green}๐Ÿš€ STRONG BULLISH - All MAs aligned upward${colors.reset}`; + } else if (!above5 && !above10 && !above20 && !ma5Above10 && !ma10Above20) { + return `${colors.red}๐Ÿ”ป STRONG BEARISH - All MAs aligned downward${colors.reset}`; + } else if (above5 && above10) { + return `${colors.green}๐Ÿ“ˆ BULLISH - Above short-term MAs${colors.reset}`; + } else if (!above5 && !above10) { + return `${colors.red}๐Ÿ“‰ BEARISH - Below short-term MAs${colors.reset}`; + } else { + return `${colors.yellow}๐Ÿ”„ NEUTRAL - Mixed signals${colors.reset}`; + } + } + + private getVolumeBar(current: number, min: number, max: number): string { + const range = max - min; + const position = range > 0 ? (current - min) / range : 0.5; + const barLength = 25; + const filledLength = Math.round(position * barLength); + + const bar = 'โ–ˆ'.repeat(filledLength) + 'โ–‘'.repeat(barLength - filledLength); + const percentage = (position * 100).toFixed(1); + + return `${colors.dim}[${colors.reset}${colors.blue}${bar}${colors.reset}${colors.dim}] ${percentage}%${colors.reset}`; + } + + private getVolumeTrend(recentVolumes: number[], avgVolume: number): string { + const recentAvg = recentVolumes.reduce((sum, vol) => sum + vol, 0) / recentVolumes.length; + const change = ((recentAvg - avgVolume) / avgVolume * 100); + + if (change > 50) return `${colors.green}๐Ÿ”ฅ SURGING (+${change.toFixed(0)}%)${colors.reset}`; + if (change > 20) return `${colors.green}๐Ÿ“ˆ INCREASING (+${change.toFixed(0)}%)${colors.reset}`; + if (change < -50) return `${colors.red}๐Ÿ’ง DECLINING (${change.toFixed(0)}%)${colors.reset}`; + if (change < -20) return `${colors.red}๐Ÿ“‰ DECREASING (${change.toFixed(0)}%)${colors.reset}`; + return `${colors.yellow}โžก๏ธ STABLE (${change.toFixed(0)}%)${colors.reset}`; + } + + private getLevelDistance(currentPrice: number, level: number, type: 'support' | 'resistance'): string { + const distance = Math.abs(currentPrice - level); + const percentage = (distance / currentPrice * 100); + const direction = type === 'support' ? 'below' : 'above'; + + if (percentage < 2) return `${colors.yellow}โš ๏ธ Very close (${percentage.toFixed(1)}% ${direction})${colors.reset}`; + if (percentage < 5) return `${colors.dim}๐Ÿ“ Close (${percentage.toFixed(1)}% ${direction})${colors.reset}`; + return `${colors.dim}๐Ÿ“ ${percentage.toFixed(1)}% ${direction}${colors.reset}`; + } + + private getCandleBar(count: number, total: number, type: 'bullish' | 'bearish'): string { + const percentage = (count / total * 100); + const barLength = Math.round(percentage / 5); // Scale to 20 max length + const color = type === 'bullish' ? colors.green : colors.red; + const symbol = type === 'bullish' ? 'โ–ฒ' : 'โ–ผ'; + + return `${color}${symbol.repeat(Math.max(1, barLength))}${colors.reset} ${colors.dim}(${percentage.toFixed(1)}%)${colors.reset}`; + } + + private generateTradingRecommendation( + currentPrice: number, + ma5: number, + ma10: number, + ma20: number, + levels: { support: number; resistance: number }, + priceChange: number, + volatility: number + ): { signal: string; reasoning: string; riskLevel: string } { + + const nearSupport = Math.abs(currentPrice - levels.support) / currentPrice < 0.02; + const nearResistance = Math.abs(currentPrice - levels.resistance) / currentPrice < 0.02; + const aboveMA5 = currentPrice > ma5; + const aboveMA10 = currentPrice > ma10; + const strongUptrend = priceChange > 5; + const strongDowntrend = priceChange < -5; + const highVolatility = volatility > 30; + + let signal: string; + let reasoning: string; + let riskLevel: string; + + if (nearSupport && aboveMA5 && !strongDowntrend) { + signal = `${colors.green}๐ŸŸข BUY SIGNAL - Bouncing off support${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก Price near support level with MA5 holding. Good entry opportunity.${colors.reset}`; + riskLevel = `${colors.yellow}โš ๏ธ Medium Risk - Set stop loss below support${colors.reset}`; + } else if (nearResistance && !aboveMA10 && !strongUptrend) { + signal = `${colors.red}๐Ÿ”ด SELL SIGNAL - Approaching resistance${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก Price near resistance with weak momentum. Consider taking profits.${colors.reset}`; + riskLevel = `${colors.yellow}โš ๏ธ Medium Risk - Watch for breakout above resistance${colors.reset}`; + } else if (strongUptrend && aboveMA5 && aboveMA10) { + signal = `${colors.green}๐Ÿš€ STRONG BUY - Momentum breakout${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก Strong upward momentum with MA support. Trend continuation likely.${colors.reset}`; + riskLevel = highVolatility ? `${colors.red}๐Ÿ”ฅ High Risk - High volatility${colors.reset}` : `${colors.green}โœ… Low Risk - Strong trend${colors.reset}`; + } else if (strongDowntrend && !aboveMA5 && !aboveMA10) { + signal = `${colors.red}๐Ÿ”ป STRONG SELL - Momentum breakdown${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก Strong downward momentum below MAs. Further decline expected.${colors.reset}`; + riskLevel = `${colors.red}๐Ÿ”ฅ High Risk - Strong bearish momentum${colors.reset}`; + } else if (highVolatility) { + signal = `${colors.yellow}โธ๏ธ HOLD - High volatility period${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก High volatility makes direction unclear. Wait for clearer signals.${colors.reset}`; + riskLevel = `${colors.red}๐Ÿ”ฅ High Risk - Unpredictable price action${colors.reset}`; + } else { + signal = `${colors.yellow}๐Ÿ”„ NEUTRAL - Mixed signals${colors.reset}`; + reasoning = `${colors.dim}๐Ÿ’ก No clear directional bias. Consider range trading or wait for breakout.${colors.reset}`; + riskLevel = `${colors.yellow}โš ๏ธ Medium Risk - Sideways market${colors.reset}`; + } + + return { signal, reasoning, riskLevel }; + } + + private calculateMarketSentiment( + trendAnalysis: any, + priceChange: number, + volatility: number, + currentPrice: number, + ma5: number, + ma10: number + ): string { + const bullishPercent = (trendAnalysis.bullishCount / (trendAnalysis.bullishCount + trendAnalysis.bearishCount)) * 100; + const aboveMA = currentPrice > ma5 && currentPrice > ma10; + const strongMove = Math.abs(priceChange) > 10; + const lowVolatility = volatility < 15; + + if (bullishPercent > 70 && aboveMA && priceChange > 0) { + return `${colors.green}๐Ÿ˜Š VERY BULLISH - Strong positive sentiment${colors.reset}`; + } else if (bullishPercent < 30 && !aboveMA && priceChange < 0) { + return `${colors.red}๐Ÿ˜ฐ VERY BEARISH - Strong negative sentiment${colors.reset}`; + } else if (bullishPercent > 60 && priceChange > 0) { + return `${colors.green}๐Ÿ™‚ BULLISH - Positive sentiment${colors.reset}`; + } else if (bullishPercent < 40 && priceChange < 0) { + return `${colors.red}๐Ÿ™ BEARISH - Negative sentiment${colors.reset}`; + } else if (lowVolatility && Math.abs(priceChange) < 5) { + return `${colors.blue}๐Ÿ˜ CALM - Low volatility, stable sentiment${colors.reset}`; + } else if (strongMove) { + return `${colors.yellow}๐Ÿค” UNCERTAIN - High volatility, mixed signals${colors.reset}`; + } else { + return `${colors.yellow}๐Ÿ˜‘ NEUTRAL - Balanced sentiment${colors.reset}`; + } + } + + private generateOhlcvInsights(data: any[], timeframe: 'hourly' | 'daily'): string[] { + const insights: string[] = []; + const period = timeframe === 'hourly' ? '24-hour' : '30-day'; + + // Price movement analysis + const prices = data.map((d: any) => d.CLOSE); + const currentPrice = prices[0]; + const oldestPrice = prices[prices.length - 1]; + const priceChangePercent = ((currentPrice - oldestPrice) / oldestPrice) * 100; + + if (Math.abs(priceChangePercent) > 20) { + const direction = priceChangePercent > 0 ? 'surge' : 'decline'; + insights.push(`โšก Significant ${period} ${direction} of ${Math.abs(priceChangePercent).toFixed(1)}%`); + } else if (Math.abs(priceChangePercent) > 10) { + const direction = priceChangePercent > 0 ? 'gain' : 'loss'; + insights.push(`๐Ÿ“ˆ Notable ${period} ${direction} of ${Math.abs(priceChangePercent).toFixed(1)}%`); + } + + // Volume analysis + const volumes = data.map((d: any) => d.VOLUME); + const avgVolume = volumes.reduce((sum: number, vol: number) => sum + vol, 0) / volumes.length; + const recentVolume = volumes.slice(0, Math.min(5, volumes.length)); + const recentAvgVolume = recentVolume.reduce((sum: number, vol: number) => sum + vol, 0) / recentVolume.length; + + if (recentAvgVolume > avgVolume * 1.5) { + insights.push(`๐Ÿ”ฅ Above-average volume activity - ${((recentAvgVolume/avgVolume - 1) * 100).toFixed(0)}% higher than usual`); + } else if (recentAvgVolume < avgVolume * 0.5) { + insights.push(`๐Ÿ’ง Below-average volume activity - ${((1 - recentAvgVolume/avgVolume) * 100).toFixed(0)}% lower than usual`); + } + + // Volatility analysis + const highs = data.map((d: any) => d.HIGH); + const lows = data.map((d: any) => d.LOW); + const volatility = ((Math.max(...highs) - Math.min(...lows)) / Math.min(...lows)) * 100; + + if (volatility > 50) { + insights.push(`โšก High volatility period with ${volatility.toFixed(0)}% price range`); + } else if (volatility < 10) { + insights.push(`๐Ÿ›ก๏ธ Low volatility period with only ${volatility.toFixed(1)}% price range`); + } + + // Candle pattern analysis + const bullishCandles = data.filter((d: any) => d.CLOSE > d.OPEN).length; + const bearishCandles = data.filter((d: any) => d.CLOSE < d.OPEN).length; + const bullishPercent = (bullishCandles / data.length) * 100; + + if (bullishPercent > 70) { + insights.push(`๐ŸŸข Strong bullish sentiment - ${bullishPercent.toFixed(0)}% green candles`); + } else if (bullishPercent < 30) { + insights.push(`๐Ÿ”ด Strong bearish sentiment - ${(100-bullishPercent).toFixed(0)}% red candles`); + } + + // Recent momentum + const recentPrices = prices.slice(0, Math.min(5, prices.length)); + const isUptrend = recentPrices.every((price, index) => + index === 0 || price >= recentPrices[index - 1] * 0.98 // Allow 2% tolerance + ); + const isDowntrend = recentPrices.every((price, index) => + index === 0 || price <= recentPrices[index - 1] * 1.02 // Allow 2% tolerance + ); + + if (isUptrend) { + insights.push(`๐Ÿ“ˆ Recent upward momentum - consistent price increases`); + } else if (isDowntrend) { + insights.push(`๐Ÿ“‰ Recent downward momentum - consistent price decreases`); + } + + if (insights.length === 0) { + insights.push(`๐Ÿ“Š Mixed ${period} performance with balanced price and volume activity`); + } + + return insights; + } + + private analyzePriceTrend(data: any[]): { trend: string; strength: string; bullishCount: number; bearishCount: number } { + const prices = data.map((d: any) => d.CLOSE); + const currentPrice = prices[0]; + const oldestPrice = prices[prices.length - 1]; + const priceChangePercent = ((currentPrice - oldestPrice) / oldestPrice) * 100; + + const bullishCandles = data.filter((d: any) => d.CLOSE > d.OPEN).length; + const bearishCandles = data.filter((d: any) => d.CLOSE < d.OPEN).length; + + // Determine trend + let trend: string; + + if (priceChangePercent > 5) { + trend = `${colors.green}๐Ÿš€ STRONG BULLISH${colors.reset}`; + } else if (priceChangePercent > 1) { + trend = `${colors.green}๐Ÿ“ˆ BULLISH${colors.reset}`; + } else if (priceChangePercent > -1) { + trend = `${colors.yellow}โžก๏ธ SIDEWAYS${colors.reset}`; + } else if (priceChangePercent > -5) { + trend = `${colors.red}๐Ÿ“‰ BEARISH${colors.reset}`; + } else { + trend = `${colors.red}๐Ÿ’ฅ STRONG BEARISH${colors.reset}`; + } + + // Determine strength based on consistency + const bullishPercent = (bullishCandles / data.length) * 100; + let strength: string; + + if (bullishPercent > 80 || bullishPercent < 20) { + strength = `${colors.bright}๐Ÿ”ฅ VERY STRONG${colors.reset}`; + } else if (bullishPercent > 65 || bullishPercent < 35) { + strength = `${colors.yellow}๐Ÿ’ช STRONG${colors.reset}`; + } else if (bullishPercent > 55 || bullishPercent < 45) { + strength = `${colors.blue}๐Ÿ“Š MODERATE${colors.reset}`; + } else { + strength = `${colors.dim}โšช WEAK${colors.reset}`; + } + + return { + trend, + strength, + bullishCount: bullishCandles, + bearishCount: bearishCandles + }; + } + + private async getAiReports(prompt: string): Promise { + try { + console.log(`${colors.dim} ๐ŸŽฏ Getting general AI reports from TokenMetrics${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getAiReports.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Don't show generic message since we've already formatted the data + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`AI reports query failed: ${error}`, 'error'); + } + } + + private formatAiReportsResponse(data: any[]): void { + if (!data || data.length === 0) return; + + console.log(`${colors.cyan}๐Ÿค– AI Reports Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log('โ•'.repeat(80)); + + data.forEach((report: any, index: number) => { + const tokenName = report.TOKEN_NAME || 'Unknown'; + const tokenSymbol = report.TOKEN_SYMBOL || 'N/A'; + + console.log(`๐Ÿ“Š ${tokenName} (${tokenSymbol}) - Comprehensive AI Analysis`); + console.log(); + + // Process different report types + const reportTypes = [ + { key: 'INVESTMENT_ANALYSIS_POINTER', title: '๐Ÿ“‹ INVESTMENT OVERVIEW', icon: '๐Ÿ’ฐ' }, + { key: 'INVESTMENT_ANALYSIS', title: '๐Ÿ“ˆ INVESTMENT ANALYSIS', icon: '๐Ÿ“Š' }, + { key: 'DEEP_DIVE', title: '๐Ÿ” DEEP DIVE ANALYSIS', icon: '๐ŸŽฏ' }, + { key: 'CODE_REVIEW', title: '๐Ÿ’ป TECHNICAL CODE REVIEW', icon: 'โš™๏ธ' } + ]; + + reportTypes.forEach((reportType, typeIndex) => { + const reportText = report[reportType.key]; + if (reportText && reportText.trim()) { + console.log(`${colors.bright}${reportType.icon} ${reportType.title}${colors.reset}`); + console.log('โ•'.repeat(60)); + + // Parse the markdown-style report + const sections = this.parseMarkdownReport(reportText); + + // Display executive summary first if available + if (sections.executiveSummary) { + console.log(`${colors.green}๐Ÿ“‹ EXECUTIVE SUMMARY${colors.reset}`); + console.log('โ”€'.repeat(40)); + console.log(this.formatTextBlock(sections.executiveSummary, 70)); + console.log(); + } + + // Display key insights + if (sections.keyPoints && sections.keyPoints.length > 0) { + console.log(`${colors.blue}๐Ÿ’ก KEY INSIGHTS${colors.reset}`); + console.log('โ”€'.repeat(40)); + sections.keyPoints.slice(0, 5).forEach((point: string, i: number) => { + console.log(`${i + 1}. ${point.trim()}`); + }); + console.log(); + } + + // Display market analysis + if (sections.marketAnalysis) { + console.log(`${colors.yellow}๐Ÿ“Š MARKET ANALYSIS${colors.reset}`); + console.log('โ”€'.repeat(40)); + console.log(this.formatTextBlock(sections.marketAnalysis, 70)); + console.log(); + } + + // Display features/technology + if (sections.features && sections.features.length > 0) { + console.log(`${colors.cyan}โšก KEY FEATURES${colors.reset}`); + console.log('โ”€'.repeat(40)); + sections.features.slice(0, 6).forEach((feature: string) => { + console.log(`๐Ÿ”น ${feature.trim()}`); + }); + console.log(); + } + + // Display conclusion + if (sections.conclusion) { + console.log(`${colors.magenta}๐ŸŽฏ CONCLUSION${colors.reset}`); + console.log('โ”€'.repeat(40)); + console.log(this.formatTextBlock(sections.conclusion, 70)); + console.log(); + } + + // If this isn't the last report type, add separator + if (typeIndex < reportTypes.length - 1 && reportTypes.slice(typeIndex + 1).some(rt => report[rt.key])) { + console.log('โ”€'.repeat(80)); + console.log(); + } + } + }); + + if (index < data.length - 1) { + console.log('โ•'.repeat(80)); + console.log(); + } + }); + + console.log('โ•'.repeat(80)); + console.log(`๐Ÿค– Analysis powered by TokenMetrics AI research models`); + console.log(); + } + + private parseMarkdownReport(reportText: string): { + executiveSummary?: string; + keyPoints: string[]; + marketAnalysis?: string; + features: string[]; + conclusion?: string; + } { + const sections = { + executiveSummary: undefined as string | undefined, + keyPoints: [] as string[], + marketAnalysis: undefined as string | undefined, + features: [] as string[], + conclusion: undefined as string | undefined + }; + + // Split by markdown headers + const headerRegex = /^#+\s+(.+)$/gm; + const parts = reportText.split(headerRegex); + + for (let i = 0; i < parts.length; i += 2) { + const header = parts[i]?.toLowerCase() || ''; + const content = parts[i + 1]?.trim() || ''; + + if (!content) continue; + + if (header.includes('executive summary') || header.includes('summary')) { + sections.executiveSummary = content; + } else if (header.includes('market analysis') || header.includes('market')) { + sections.marketAnalysis = content; + } else if (header.includes('conclusion')) { + sections.conclusion = content; + } else if (header.includes('features') || header.includes('technology')) { + // Extract bullet points + const bullets = content.match(/^[-*]\s+(.+)$/gm); + if (bullets) { + sections.features.push(...bullets.map(b => b.replace(/^[-*]\s+/, ''))); + } + } + } + + // Extract key points from bullet lists throughout the document + const allBullets = reportText.match(/^[-*]\s+(.+)$/gm); + if (allBullets) { + sections.keyPoints = allBullets + .map(b => b.replace(/^[-*]\s+/, '')) + .filter(point => point.length > 20 && point.length < 200) + .slice(0, 8); + } + + // If no executive summary found, extract first meaningful paragraph + if (!sections.executiveSummary) { + const paragraphs = reportText.split(/\n\s*\n/).filter(p => p.trim().length > 100); + if (paragraphs.length > 0) { + sections.executiveSummary = paragraphs[0].replace(/^#+\s+.+$/gm, '').trim(); + } + } + + // If no conclusion found, extract last meaningful paragraph + if (!sections.conclusion) { + const paragraphs = reportText.split(/\n\s*\n/).filter(p => p.trim().length > 100); + if (paragraphs.length > 1) { + sections.conclusion = paragraphs[paragraphs.length - 1].replace(/^#+\s+.+$/gm, '').trim(); + } + } + + return sections; + } + + private formatTextBlock(text: string, maxWidth: number): string { + const words = text.split(' '); + const lines: string[] = []; + let currentLine = ''; + + words.forEach(word => { + if ((currentLine + ' ' + word).length <= maxWidth) { + currentLine += (currentLine ? ' ' : '') + word; + } else { + if (currentLine) lines.push(currentLine); + currentLine = word; + } + }); + + if (currentLine) lines.push(currentLine); + return lines.join('\n'); + } + + private async getCryptoInvestors(): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getCryptoInvestors.executable( + { limit: "50", page: "1" }, + (msg: string) => { + console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`); + } + ); + }); + + if (result.status === 'done') { + // Try to parse the full response from result.feedback + try { + const responseIndex = result.feedback.lastIndexOf('Response: '); + if (responseIndex !== -1) { + const jsonString = result.feedback.substring(responseIndex + 10).trim(); + const responseData = JSON.parse(jsonString); + if (responseData.data && responseData.data.length > 0) { + this.formatCryptoInvestorsResponse(responseData.data); + return; // Exit early since we've formatted the data + } + } + } catch (e) { + // If parsing fails, continue with default behavior + } + + // Fallback to generic message if parsing fails + this.formatResponse("Successfully retrieved crypto investors data", 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Crypto investors query failed: ${error}`, 'error'); + } + } + + private formatCryptoInvestorsResponse(data: any[]): void { + if (!data || data.length === 0) return; + + console.log(`${colors.cyan}๐Ÿ’ผ Institutional Investor Intelligence [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log('โ•'.repeat(80)); + console.log(`๐Ÿ›๏ธ Smart Money Analysis - ${data.length} Institutional Investors`); + console.log(`๐Ÿ“Š Investment Performance & Activity Tracking`); + console.log(); + + // Calculate overall statistics + const totalInvestors = data.length; + const avgROI = data.reduce((sum, inv) => sum + (inv.ROI_AVERAGE || 0), 0) / totalInvestors; + const medianROI = data.reduce((sum, inv) => sum + (inv.ROI_MEDIAN || 0), 0) / totalInvestors; + const totalRounds = data.reduce((sum, inv) => sum + parseInt(inv.ROUND_COUNT || '0'), 0); + + // Categorize investors by performance + const topPerformers = data.filter(inv => (inv.ROI_AVERAGE || 0) > 0).sort((a, b) => (b.ROI_AVERAGE || 0) - (a.ROI_AVERAGE || 0)); + const activeInvestors = data.filter(inv => parseInt(inv.ROUND_COUNT || '0') >= 5).sort((a, b) => parseInt(b.ROUND_COUNT || '0') - parseInt(a.ROUND_COUNT || '0')); + const strugglingInvestors = data.filter(inv => (inv.ROI_AVERAGE || 0) < -0.8).sort((a, b) => (a.ROI_AVERAGE || 0) - (b.ROI_AVERAGE || 0)); + + // Market Overview + console.log(`${colors.green}๐Ÿ“ˆ MARKET OVERVIEW${colors.reset}`); + console.log('โ”€'.repeat(50)); + console.log(`๐Ÿ“Š Total Investors Analyzed: ${colors.bright}${totalInvestors}${colors.reset}`); + console.log(`๐Ÿ’ฐ Average ROI: ${this.getROIColor(avgROI)}${(avgROI * 100).toFixed(2)}%${colors.reset}`); + console.log(`๐Ÿ“Š Median ROI: ${this.getROIColor(medianROI)}${(medianROI * 100).toFixed(2)}%${colors.reset}`); + console.log(`๐ŸŽฏ Total Investment Rounds: ${colors.bright}${totalRounds}${colors.reset}`); + console.log(`๐Ÿ† Profitable Investors: ${colors.green}${topPerformers.length}${colors.reset} (${((topPerformers.length / totalInvestors) * 100).toFixed(1)}%)`); + console.log(`โš ๏ธ Underperforming: ${colors.red}${strugglingInvestors.length}${colors.reset} (${((strugglingInvestors.length / totalInvestors) * 100).toFixed(1)}%)`); + console.log(); + + // Top Performers Section + if (topPerformers.length > 0) { + console.log(`${colors.green}๐Ÿ† TOP PERFORMING INVESTORS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const topToShow = topPerformers.slice(0, 8); + topToShow.forEach((investor, index) => { + const roi = (investor.ROI_AVERAGE * 100).toFixed(2); + const rounds = investor.ROUND_COUNT || '0'; + const name = investor.INVESTOR_NAME || 'Unknown'; + const roiBar = this.getInvestorROIBar(investor.ROI_AVERAGE); + + console.log(`${colors.green}${(index + 1).toString().padStart(2)}. ${name.padEnd(25)}${colors.reset} โ”‚ ${roiBar} ${colors.green}+${roi}%${colors.reset} โ”‚ ${rounds} rounds`); + }); + + if (topPerformers.length > 8) { + console.log(`${colors.dim} ... and ${topPerformers.length - 8} more profitable investors${colors.reset}`); + } + console.log(); + } + + // Most Active Investors + if (activeInvestors.length > 0) { + console.log(`${colors.blue}๐ŸŽฏ MOST ACTIVE INVESTORS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const activeToShow = activeInvestors.slice(0, 8); + activeToShow.forEach((investor, index) => { + const roi = (investor.ROI_AVERAGE * 100).toFixed(2); + const rounds = investor.ROUND_COUNT || '0'; + const name = investor.INVESTOR_NAME || 'Unknown'; + const activityLevel = this.getActivityLevel(parseInt(rounds)); + const roiColor = this.getROIColor(investor.ROI_AVERAGE); + + console.log(`${colors.blue}${(index + 1).toString().padStart(2)}. ${name.padEnd(25)}${colors.reset} โ”‚ ${activityLevel} ${rounds} rounds โ”‚ ${roiColor}${roi}%${colors.reset}`); + }); + + if (activeInvestors.length > 8) { + console.log(`${colors.dim} ... and ${activeInvestors.length - 8} more active investors${colors.reset}`); + } + console.log(); + } + + // Notable Investors with Social Presence + const socialInvestors = data.filter(inv => inv.INVESTOR_TWITTER || inv.INVESTOR_WEBSITE).slice(0, 6); + if (socialInvestors.length > 0) { + console.log(`${colors.magenta}๐ŸŒ NOTABLE INVESTORS (Social Presence)${colors.reset}`); + console.log('โ”€'.repeat(50)); + + socialInvestors.forEach((investor, index) => { + const roi = (investor.ROI_AVERAGE * 100).toFixed(2); + const rounds = investor.ROUND_COUNT || '0'; + const name = investor.INVESTOR_NAME || 'Unknown'; + const roiColor = this.getROIColor(investor.ROI_AVERAGE); + const hasTwitter = investor.INVESTOR_TWITTER ? '๐Ÿฆ' : ''; + const hasWebsite = investor.INVESTOR_WEBSITE ? '๐ŸŒ' : ''; + + console.log(`${colors.magenta}${(index + 1).toString().padStart(2)}. ${name.padEnd(25)}${colors.reset} โ”‚ ${roiColor}${roi}%${colors.reset} โ”‚ ${rounds} rounds ${hasTwitter}${hasWebsite}`); + }); + console.log(); + } + + // Market Insights + console.log(`${colors.cyan}๐Ÿ’ก MARKET INSIGHTS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const insights = this.generateInvestorInsights(data, topPerformers, activeInvestors, strugglingInvestors); + insights.forEach(insight => console.log(` ${insight}`)); + + console.log(); + + // Performance Distribution + console.log(`${colors.yellow}๐Ÿ“Š PERFORMANCE DISTRIBUTION${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const performanceRanges = this.categorizeInvestorPerformance(data); + Object.entries(performanceRanges).forEach(([range, count]) => { + const percentage = ((count / totalInvestors) * 100).toFixed(1); + const bar = this.getPerformanceBar(count, totalInvestors); + console.log(`${range.padEnd(20)} โ”‚ ${bar} ${count} investors (${percentage}%)`); + }); + + console.log(); + console.log('โ•'.repeat(80)); + console.log(`๐Ÿ’ผ ${colors.bright}Institutional Intelligence Summary:${colors.reset}`); + console.log(` โ€ข Smart money is ${avgROI > 0 ? 'generally profitable' : 'facing challenges'} with ${(Math.abs(avgROI) * 100).toFixed(1)}% average ${avgROI > 0 ? 'gains' : 'losses'}`); + console.log(` โ€ข ${activeInvestors.length} highly active investors (5+ rounds) driving market activity`); + console.log(` โ€ข ${topPerformers.length} profitable investors vs ${strugglingInvestors.length} underperforming`); + console.log(` โ€ข Total market exposure: ${totalRounds} investment rounds tracked`); + console.log('โ”€'.repeat(80)); + console.log(`๐Ÿ“ˆ Analysis powered by TokenMetrics institutional intelligence`); + console.log(); + } + + private getROIColor(roi: number): string { + if (roi > 0.5) return colors.green; + if (roi > 0) return colors.yellow; + if (roi > -0.5) return colors.red; + return colors.dim; + } + + private getInvestorROIBar(roi: number): string { + const percentage = Math.min(Math.max(roi * 100, -100), 500); // Cap at 500% for display + const barLength = Math.floor(Math.abs(percentage) / 20); // Each bar represents 20% + const maxBars = 10; + const bars = Math.min(barLength, maxBars); + + if (roi > 0) { + return `${colors.green}${'โ–ˆ'.repeat(bars)}${'โ–‘'.repeat(maxBars - bars)}${colors.reset}`; + } else { + return `${colors.red}${'โ–ˆ'.repeat(bars)}${'โ–‘'.repeat(maxBars - bars)}${colors.reset}`; + } + } + + private getActivityLevel(rounds: number): string { + if (rounds >= 20) return `${colors.red}๐Ÿ”ฅ VERY HIGH${colors.reset}`; + if (rounds >= 10) return `${colors.yellow}โšก HIGH${colors.reset}`; + if (rounds >= 5) return `${colors.blue}๐Ÿ“ˆ ACTIVE${colors.reset}`; + if (rounds >= 2) return `${colors.dim}๐Ÿ“Š MODERATE${colors.reset}`; + return `${colors.dim}๐Ÿ“‰ LOW${colors.reset}`; + } + + private generateInvestorInsights(data: any[], topPerformers: any[], activeInvestors: any[], strugglingInvestors: any[]): string[] { + const insights: string[] = []; + const totalInvestors = data.length; + const avgROI = data.reduce((sum, inv) => sum + (inv.ROI_AVERAGE || 0), 0) / totalInvestors; + + // Market sentiment insight + if (avgROI > 0.1) { + insights.push(`๐ŸŸข ${colors.green}Bullish institutional sentiment${colors.reset} - Average ROI of ${(avgROI * 100).toFixed(1)}% indicates strong market confidence`); + } else if (avgROI > -0.1) { + insights.push(`๐ŸŸก ${colors.yellow}Neutral institutional sentiment${colors.reset} - Mixed performance with ${(avgROI * 100).toFixed(1)}% average ROI`); + } else { + insights.push(`๐Ÿ”ด ${colors.red}Bearish institutional sentiment${colors.reset} - Average losses of ${(Math.abs(avgROI) * 100).toFixed(1)}% suggest market caution`); + } + + // Activity insight + const highActivityCount = activeInvestors.length; + if (highActivityCount > totalInvestors * 0.3) { + insights.push(`โšก ${colors.blue}High market activity${colors.reset} - ${highActivityCount} investors with 5+ rounds indicate strong institutional engagement`); + } else if (highActivityCount > totalInvestors * 0.1) { + insights.push(`๐Ÿ“Š ${colors.yellow}Moderate market activity${colors.reset} - ${highActivityCount} active investors showing selective engagement`); + } else { + insights.push(`๐Ÿ“‰ ${colors.dim}Low market activity${colors.reset} - Only ${highActivityCount} highly active investors suggest cautious approach`); + } + + // Performance distribution insight + const profitableRatio = topPerformers.length / totalInvestors; + if (profitableRatio > 0.4) { + insights.push(`๐Ÿ† ${colors.green}Strong institutional performance${colors.reset} - ${(profitableRatio * 100).toFixed(1)}% of investors are profitable`); + } else if (profitableRatio > 0.2) { + insights.push(`โš–๏ธ ${colors.yellow}Mixed institutional results${colors.reset} - ${(profitableRatio * 100).toFixed(1)}% profitable vs ${((strugglingInvestors.length / totalInvestors) * 100).toFixed(1)}% struggling`); + } else { + insights.push(`โš ๏ธ ${colors.red}Challenging market conditions${colors.reset} - Only ${(profitableRatio * 100).toFixed(1)}% of investors showing profits`); + } + + // Top performer insight + if (topPerformers.length > 0) { + const bestROI = topPerformers[0].ROI_AVERAGE * 100; + const bestInvestor = topPerformers[0].INVESTOR_NAME; + insights.push(`๐Ÿฅ‡ ${colors.green}Top performer: ${bestInvestor}${colors.reset} with ${bestROI.toFixed(1)}% ROI leading institutional returns`); + } + + // Risk insight + const highRiskCount = strugglingInvestors.length; + if (highRiskCount > totalInvestors * 0.3) { + insights.push(`โš ๏ธ ${colors.red}High market risk${colors.reset} - ${highRiskCount} investors with significant losses (>80%) indicate volatile conditions`); + } + + return insights; + } + + private categorizeInvestorPerformance(data: any[]): Record { + const ranges = { + 'Exceptional (>100%)': 0, + 'Strong (50-100%)': 0, + 'Profitable (0-50%)': 0, + 'Break-even (-10-0%)': 0, + 'Moderate Loss (-50--10%)': 0, + 'Heavy Loss (<-50%)': 0 + }; + + data.forEach(investor => { + const roi = investor.ROI_AVERAGE || 0; + const roiPercent = roi * 100; + + if (roiPercent > 100) ranges['Exceptional (>100%)']++; + else if (roiPercent > 50) ranges['Strong (50-100%)']++; + else if (roiPercent > 0) ranges['Profitable (0-50%)']++; + else if (roiPercent > -10) ranges['Break-even (-10-0%)']++; + else if (roiPercent > -50) ranges['Moderate Loss (-50--10%)']++; + else ranges['Heavy Loss (<-50%)']++; + }); + + return ranges; + } + + private getPerformanceBar(count: number, total: number): string { + const percentage = count / total; + const barLength = Math.floor(percentage * 20); // 20 character bar + const filledBars = 'โ–ˆ'.repeat(barLength); + const emptyBars = 'โ–‘'.repeat(20 - barLength); + + if (percentage > 0.3) return `${colors.green}${filledBars}${emptyBars}${colors.reset}`; + if (percentage > 0.15) return `${colors.yellow}${filledBars}${emptyBars}${colors.reset}`; + return `${colors.red}${filledBars}${emptyBars}${colors.reset}`; + } + + private async getResistanceSupport(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + const primaryTokenId = tokenIds[0]; // Use the first detected token for resistance/support analysis + + console.log(`${colors.dim} ๐ŸŽฏ Getting resistance/support levels for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getResistanceSupport.executable( + { token_id: primaryTokenId, limit: "50", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the resistance/support data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatResistanceSupportResponse(responseData.data); + } else { + this.formatResponse("No resistance/support data available for this token.", 'data'); + } + } else { + this.formatResponse("Resistance/support analysis completed successfully. Check the detailed data above for historical levels and trading insights.", 'data'); + } + } catch (parseError) { + this.formatResponse("Resistance/support analysis completed successfully. Check the detailed data above for historical levels and trading insights.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Resistance/support analysis failed: ${error}`, 'error'); + } + } + + private formatResistanceSupportResponse(data: any[]): void { + if (!data || data.length === 0) return; + + const analysis = data[0]; + const tokenName = analysis.TOKEN_NAME || 'Unknown'; + const tokenSymbol = analysis.TOKEN_SYMBOL || 'N/A'; + const date = new Date(analysis.DATE).toLocaleDateString(); + const levels = analysis.HISTORICAL_RESISTANCE_SUPPORT_LEVELS || []; + + console.log(`${colors.cyan}๐Ÿ“Š Technical Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log('โ•'.repeat(80)); + console.log(`๐ŸŽฏ ${tokenName} (${tokenSymbol}) - Resistance & Support Levels`); + console.log(`๐Ÿ“… Analysis Date: ${date}`); + console.log(`๐Ÿ“ˆ Total Historical Levels: ${levels.length}`); + console.log(); + + if (levels.length === 0) { + console.log(`${colors.yellow}โš ๏ธ No historical resistance/support levels found${colors.reset}`); + return; + } + + // Sort levels by date (most recent first) and price + const sortedLevels = levels + .map((level: any) => ({ + ...level, + dateObj: new Date(level.date), + year: new Date(level.date).getFullYear() + })) + .sort((a: any, b: any) => b.dateObj.getTime() - a.dateObj.getTime()); + + // Group levels by time periods + const currentYear = new Date().getFullYear(); + const recentLevels = sortedLevels.filter((level: any) => level.year >= currentYear - 1); + const historicalLevels = sortedLevels.filter((level: any) => level.year < currentYear - 1); + + // Get key levels (highest and lowest) + const allLevels = sortedLevels.map((level: any) => level.level).sort((a: number, b: number) => b - a); + const highestLevel = allLevels[0]; + const lowestLevel = allLevels[allLevels.length - 1]; + const medianLevel = allLevels[Math.floor(allLevels.length / 2)]; + + // Show key levels summary + console.log(`${colors.green}๐Ÿ”‘ KEY LEVELS SUMMARY${colors.reset}`); + console.log('โ”€'.repeat(50)); + console.log(`๐Ÿ”ด All-Time High: ${colors.red}$${this.formatPrice(highestLevel)}${colors.reset}`); + console.log(`๐ŸŸข All-Time Low: ${colors.green}$${this.formatPrice(lowestLevel)}${colors.reset}`); + console.log(`๐ŸŸก Median Level: ${colors.yellow}$${this.formatPrice(medianLevel)}${colors.reset}`); + console.log(`๐Ÿ“Š Price Range: ${this.formatPrice(((highestLevel - lowestLevel) / lowestLevel * 100).toFixed(2))}% spread`); + console.log(); + + // Show recent levels (last 2 years) + if (recentLevels.length > 0) { + console.log(`${colors.blue}๐Ÿ“… RECENT LEVELS (${currentYear - 1}-${currentYear})${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const recentToShow = recentLevels.slice(0, 10); // Show top 10 recent levels + recentToShow.forEach((level: any, index: number) => { + const price = this.formatPrice(level.level); + const date = level.dateObj.toLocaleDateString(); + const levelType = this.getLevelType(level.level, highestLevel, lowestLevel, medianLevel); + const indicator = this.getLevelIndicator(level.level, highestLevel, lowestLevel, medianLevel); + + console.log(`${indicator} $${price.padStart(12)} โ”‚ ${date} ${levelType}`); + }); + + if (recentLevels.length > 10) { + console.log(`${colors.dim} ... and ${recentLevels.length - 10} more recent levels${colors.reset}`); + } + console.log(); + } + + // Show historical significant levels + if (historicalLevels.length > 0) { + console.log(`${colors.magenta}๐Ÿ“œ HISTORICAL SIGNIFICANT LEVELS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + // Get the most significant historical levels (extreme highs and lows) + const significantLevels = historicalLevels + .filter((level: any) => + level.level >= highestLevel * 0.8 || // Top 20% levels + level.level <= lowestLevel * 1.2 // Bottom 20% levels + ) + .slice(0, 8); // Show top 8 significant levels + + significantLevels.forEach((level: any) => { + const price = this.formatPrice(level.level); + const date = level.dateObj.toLocaleDateString(); + const levelType = this.getLevelType(level.level, highestLevel, lowestLevel, medianLevel); + const indicator = this.getLevelIndicator(level.level, highestLevel, lowestLevel, medianLevel); + + console.log(`${indicator} $${price.padStart(12)} โ”‚ ${date} ${levelType}`); + }); + + console.log(); + } + + // Trading insights + console.log(`${colors.cyan}๐Ÿ’ก TRADING INSIGHTS${colors.reset}`); + console.log('โ”€'.repeat(50)); + + const insights = this.generateResistanceSupportInsights(sortedLevels, highestLevel, lowestLevel, medianLevel); + insights.forEach(insight => console.log(` ${insight}`)); + + console.log(); + console.log('โ•'.repeat(80)); + console.log(`๐ŸŽฏ ${colors.bright}Key Support/Resistance Zones:${colors.reset}`); + + // Calculate key zones + const zones = this.calculateKeyZones(allLevels); + zones.forEach((zone, index) => { + const strength = this.getZoneStrength(zone.count); + console.log(` ${strength} $${this.formatPrice(zone.min)} - $${this.formatPrice(zone.max)} (${zone.count} touches)`); + }); + + console.log('โ”€'.repeat(80)); + console.log(`๐Ÿ“ˆ Analysis powered by TokenMetrics technical analysis models`); + console.log(); + } + + private getLevelType(level: number, highest: number, lowest: number, median: number): string { + if (level >= highest * 0.95) return `${colors.red}๐Ÿ”ด MAJOR RESISTANCE${colors.reset}`; + if (level <= lowest * 1.05) return `${colors.green}๐ŸŸข MAJOR SUPPORT${colors.reset}`; + if (level >= median * 1.1) return `${colors.yellow}๐ŸŸก Resistance${colors.reset}`; + if (level <= median * 0.9) return `${colors.blue}๐Ÿ”ต Support${colors.reset}`; + return `${colors.dim}โšช Neutral${colors.reset}`; + } + + private getLevelIndicator(level: number, highest: number, lowest: number, median: number): string { + if (level >= highest * 0.95) return '๐Ÿ”ด'; + if (level <= lowest * 1.05) return '๐ŸŸข'; + if (level >= median * 1.1) return '๐ŸŸก'; + if (level <= median * 0.9) return '๐Ÿ”ต'; + return 'โšช'; + } + + private generateResistanceSupportInsights(levels: any[], highest: number, lowest: number, median: number): string[] { + const insights: string[] = []; + const currentYear = new Date().getFullYear(); + + // Recent activity analysis + const recentLevels = levels.filter((level: any) => level.year >= currentYear - 1); + if (recentLevels.length > 0) { + const recentHigh = Math.max(...recentLevels.map((l: any) => l.level)); + const recentLow = Math.min(...recentLevels.map((l: any) => l.level)); + + if (recentHigh >= highest * 0.9) { + insights.push(`๐Ÿš€ Recent price action near all-time highs - strong bullish momentum`); + } + + if (recentLow <= lowest * 1.1) { + insights.push(`๐Ÿ“‰ Recent price tested major support levels - watch for bounce or breakdown`); + } + } + + // Level density analysis + const priceRanges = this.analyzePriceDensity(levels.map((l: any) => l.level)); + if (priceRanges.highDensityZone) { + insights.push(`๐ŸŽฏ High activity zone around $${this.formatPrice(priceRanges.highDensityZone)} - key level to watch`); + } + + // Historical pattern analysis + const oldestLevel = levels[levels.length - 1]; + const newestLevel = levels[0]; + if (oldestLevel && newestLevel) { + const yearSpan = newestLevel.year - oldestLevel.year; + if (yearSpan >= 5) { + insights.push(`๐Ÿ“Š ${yearSpan}-year price history shows ${levels.length} significant levels`); + } + } + + // Volatility insights + const priceSpread = ((highest - lowest) / lowest * 100); + if (priceSpread > 1000) { + insights.push(`โšก Extremely volatile asset with ${priceSpread.toFixed(0)}% historical range`); + } else if (priceSpread > 500) { + insights.push(`๐Ÿ“ˆ High volatility asset with ${priceSpread.toFixed(0)}% historical range`); + } + + if (insights.length === 0) { + insights.push(`๐Ÿ“Š Multiple support and resistance levels identified across different timeframes`); + } + + return insights; + } + + private analyzePriceDensity(levels: number[]): { highDensityZone?: number } { + // Group levels into price ranges and find the most dense area + const sortedLevels = levels.sort((a, b) => a - b); + const ranges: { [key: string]: number[] } = {}; + + // Create 10% price ranges + const min = sortedLevels[0]; + const max = sortedLevels[sortedLevels.length - 1]; + const rangeSize = (max - min) / 10; + + sortedLevels.forEach(level => { + const rangeIndex = Math.floor((level - min) / rangeSize); + const rangeKey = `range_${rangeIndex}`; + if (!ranges[rangeKey]) ranges[rangeKey] = []; + ranges[rangeKey].push(level); + }); + + // Find the range with most levels + let maxCount = 0; + let highDensityZone: number | undefined; + + Object.values(ranges).forEach(rangeLevels => { + if (rangeLevels.length > maxCount) { + maxCount = rangeLevels.length; + highDensityZone = rangeLevels.reduce((sum, level) => sum + level, 0) / rangeLevels.length; + } + }); + + return { highDensityZone }; + } + + private calculateKeyZones(levels: number[]): Array<{ min: number; max: number; count: number }> { + const sortedLevels = levels.sort((a, b) => a - b); + const zones: Array<{ min: number; max: number; count: number }> = []; + + // Group levels within 5% of each other + let currentZone: number[] = []; + + for (let i = 0; i < sortedLevels.length; i++) { + const level = sortedLevels[i]; + + if (currentZone.length === 0) { + currentZone.push(level); + } else { + const zoneAvg = currentZone.reduce((sum, l) => sum + l, 0) / currentZone.length; + if (Math.abs(level - zoneAvg) / zoneAvg <= 0.05) { // Within 5% + currentZone.push(level); + } else { + // Finish current zone and start new one + if (currentZone.length >= 2) { // Only zones with multiple touches + zones.push({ + min: Math.min(...currentZone), + max: Math.max(...currentZone), + count: currentZone.length + }); + } + currentZone = [level]; + } + } + } + + // Add the last zone + if (currentZone.length >= 2) { + zones.push({ + min: Math.min(...currentZone), + max: Math.max(...currentZone), + count: currentZone.length + }); + } + + // Sort by count (most touches first) and return top 5 + return zones.sort((a, b) => b.count - a.count).slice(0, 5); + } + + private getZoneStrength(count: number): string { + if (count >= 5) return `${colors.red}๐Ÿ”ฅ VERY STRONG${colors.reset}`; + if (count >= 3) return `${colors.yellow}๐Ÿ’ช STRONG${colors.reset}`; + return `${colors.blue}๐Ÿ“Š MODERATE${colors.reset}`; + } + + private async getMarketOverview(): Promise { + try { + console.log(`${colors.yellow}๐Ÿ” Getting comprehensive market overview...${colors.reset}`); + + // Calculate date range (30 days ago to today) + const endDate = new Date().toISOString().split('T')[0]; // Today in YYYY-MM-DD format + const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 30 days ago + + // Get multiple data points for a comprehensive overview with rate limiting + const marketMetrics = await this.retryWithBackoff(async () => { + return await this.plugin.getMarketMetrics.executable( + { + startDate: startDate, + endDate: endDate, + limit: "3", + page: "1" + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“Š Market: ${msg}${colors.reset}`) + ); + }); + + await this.rateLimitDelay(); // Add delay between requests + + const topTokens = await this.retryWithBackoff(async () => { + return await this.plugin.getTopMarketCapTokens.executable( + { top_k: "10", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ† Top Tokens: ${msg}${colors.reset}`) + ); + }); + + await this.rateLimitDelay(); // Add delay between requests + + const sentiments = await this.retryWithBackoff(async () => { + return await this.plugin.getSentiments.executable( + { limit: "3", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ˜Š Sentiment: ${msg}${colors.reset}`) + ); + }); + + this.formatResponse("Comprehensive market overview retrieved successfully! This includes:\nโ€ข Market metrics and bullish/bearish indicators\nโ€ข Top 10 cryptocurrencies by market cap\nโ€ข Latest sentiment analysis from social media and news\n\nCheck the detailed data above for complete analysis.", 'data'); + + } catch (error) { + this.formatResponse(`Market overview failed: ${error}`, 'error'); + } + } + + private showHelp(): void { + console.log(`${colors.cyan}${colors.bright}๐Ÿ“š Available Commands${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(50)}${colors.reset}`); + console.log(`${colors.bright}๐Ÿ” Data & Analysis:${colors.reset}`); + console.log(` โ€ข ${colors.green}sentiment${colors.reset} - Get market sentiment analysis`); + console.log(` โ€ข ${colors.green}trader grades${colors.reset} - View trader performance grades`); + console.log(` โ€ข ${colors.green}investor grades${colors.reset} - View investor performance grades`); + console.log(` โ€ข ${colors.green}market metrics [token]${colors.reset} - Get comprehensive market data`); + console.log(` โ€ข ${colors.green}quantmetrics [token]${colors.reset} - Get quantitative analysis metrics`); + console.log(` โ€ข ${colors.green}hourly [token]${colors.reset} - Get hourly OHLCV data`); + console.log(` โ€ข ${colors.green}daily [token]${colors.reset} - Get daily OHLCV data`); + console.log(` โ€ข ${colors.green}ai reports [token]${colors.reset} - Get AI-generated analysis reports`); + console.log(` โ€ข ${colors.green}crypto investors${colors.reset} - View top crypto investors`); + console.log(` โ€ข ${colors.green}resistance support [token]${colors.reset} - Get support/resistance levels`); + console.log(` โ€ข ${colors.green}price [token]${colors.reset} - Get current token prices`); + console.log(` โ€ข ${colors.green}trading signals${colors.reset} - Get AI trading signals`); + console.log(` โ€ข ${colors.green}hourly trading signals${colors.reset} - Get hourly AI trading signals`); + console.log(` โ€ข ${colors.green}scenario analysis [token]${colors.reset} - Get scenario-based analysis`); + console.log(` โ€ข ${colors.green}correlation [token]${colors.reset} - Get correlation analysis`); + + console.log(`\n${colors.bright}๐Ÿ“Š Market Overview:${colors.reset}`); + console.log(` โ€ข ${colors.green}market overview${colors.reset} - Get comprehensive market overview`); + console.log(` โ€ข ${colors.green}top tokens${colors.reset} - View top market cap tokens`); + console.log(` โ€ข ${colors.green}tokens${colors.reset} - List available tokens`); + + console.log(`\n${colors.bright}๐Ÿ› ๏ธ Utilities:${colors.reset}`); + console.log(` โ€ข ${colors.green}help${colors.reset} - Show this help menu`); + console.log(` โ€ข ${colors.green}test${colors.reset} - Test token detection logic (debug)`); + console.log(` โ€ข ${colors.green}quit${colors.reset} - Exit the application`); + + console.log(`\n${colors.dim}๐Ÿ’ก You can also ask natural language questions like:${colors.reset}`); + console.log(`${colors.dim} "What's the price of Bitcoin?", "Analyze Ethereum sentiment", etc.${colors.reset}`); + console.log(); + } + + public async start(): Promise { + this.formatHeader(); + let isActive = true; + + const askQuestion = () => { + // Check if the interface is still active + if (!isActive) { + return; + } + + // Wrap the question in a try-catch to handle readline errors gracefully + try { + this.rl.question(`${colors.cyan}${colors.bright}๐Ÿ’ฌ Your prompt: ${colors.reset}`, async (input) => { + const trimmedInput = input.trim(); + + if (!trimmedInput) { + askQuestion(); + return; + } + + const lowerInput = trimmedInput.toLowerCase(); + + if (lowerInput === 'quit' || lowerInput === 'exit') { + console.log(`${colors.green}${colors.bright}๐Ÿ‘‹ Thanks for using Token Metrics AI Chat! Happy trading! ๐Ÿš€${colors.reset}`); + isActive = false; + this.rl.close(); + return; + } + + if (lowerInput === 'help') { + this.showHelp(); + askQuestion(); + return; + } + + if (lowerInput === 'test') { + await this.testTokenDetection(); + askQuestion(); + return; + } + + if (lowerInput === 'clear') { + this.formatHeader(); + askQuestion(); + return; + } + + console.log(); + try { + await this.analyzePrompt(trimmedInput); + } catch (error) { + console.log(`${colors.red}โŒ Error processing request: ${error}${colors.reset}`); + } + + // Only ask next question if interface is still active + if (isActive) { + askQuestion(); + } + }); + } catch (error: any) { + // Silently handle readline errors to prevent the ERR_USE_AFTER_CLOSE error + if (error?.code === 'ERR_USE_AFTER_CLOSE') { + // Interface was closed, don't continue + return; + } + // For other errors, log them but don't crash + console.log(`${colors.red}โŒ Interface error: ${error?.message || error}${colors.reset}`); + } + }; + + // Add error handlers for the readline interface + this.rl.on('error', (error: any) => { + // Silently handle readline errors to prevent crashes + if (error?.code !== 'ERR_USE_AFTER_CLOSE') { + console.log(`${colors.red}โŒ Readline error: ${error?.message || error}${colors.reset}`); + } + }); + + this.rl.on('close', () => { + isActive = false; + }); + + askQuestion(); + } + + private formatLargeNumber(num: any): string { + const number = parseFloat(num); + if (isNaN(number)) return num.toString(); + + if (number >= 1e12) { + return (number / 1e12).toFixed(2) + 'T'; + } else if (number >= 1e9) { + return (number / 1e9).toFixed(2) + 'B'; + } else if (number >= 1e6) { + return (number / 1e6).toFixed(2) + 'M'; + } else if (number >= 1e3) { + return (number / 1e3).toFixed(2) + 'K'; + } else { + return number.toFixed(2); + } + } + + // New API Functions for Moonshot & Grade Analysis + private async getMoonshotTokens(prompt: string): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getMoonshotTokens.executable( + { type: "active", limit: "10" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + this.formatResponse("๐Ÿš€ Moonshot tokens retrieved successfully! Check the detailed data above for AI-curated high-potential token picks.", 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Moonshot tokens query failed: ${error}`, 'error'); + } + } + + private async getTmGrade(prompt: string): Promise { + try { + // Extract token symbol from prompt + const lowerPrompt = prompt.toLowerCase(); + let symbol = 'BTC'; // Default to Bitcoin + + if (lowerPrompt.includes('ethereum') || lowerPrompt.includes('eth')) { + symbol = 'ETH'; + } else if (lowerPrompt.includes('bitcoin') || lowerPrompt.includes('btc')) { + symbol = 'BTC'; + } + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTmGrade.executable( + { symbol: symbol }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + this.formatResponse(`๐Ÿ“Š TM Grade analysis for ${symbol} retrieved successfully! Check the detailed data above for comprehensive grade with signals and momentum.`, 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`TM Grade query failed: ${error}`, 'error'); + } + } + + private async getFundamentalGrade(prompt: string): Promise { + try { + // Extract token symbol from prompt + const lowerPrompt = prompt.toLowerCase(); + let symbol = 'BTC'; // Default to Bitcoin + + if (lowerPrompt.includes('ethereum') || lowerPrompt.includes('eth')) { + symbol = 'ETH'; + } else if (lowerPrompt.includes('bitcoin') || lowerPrompt.includes('btc')) { + symbol = 'BTC'; + } + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getFundamentalGrade.executable( + { symbol: symbol }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + this.formatResponse(`๐Ÿ’Ž Fundamental Grade analysis for ${symbol} retrieved successfully! Check the detailed data above for fundamental strength and tokenomics analysis.`, 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Fundamental Grade query failed: ${error}`, 'error'); + } + } + + private async getTechnologyGrade(prompt: string): Promise { + try { + // Extract token symbol from prompt + const lowerPrompt = prompt.toLowerCase(); + let symbol = 'BTC'; // Default to Bitcoin + + if (lowerPrompt.includes('ethereum') || lowerPrompt.includes('eth')) { + symbol = 'ETH'; + } else if (lowerPrompt.includes('bitcoin') || lowerPrompt.includes('btc')) { + symbol = 'BTC'; + } + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTechnologyGrade.executable( + { symbol: symbol }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + this.formatResponse(`๐Ÿ”ง Technology Grade analysis for ${symbol} retrieved successfully! Check the detailed data above for technology quality and development scores.`, 'data'); + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Technology Grade query failed: ${error}`, 'error'); + } + } + + private async getTopMarketCapTokens(): Promise { + try { + console.log(`${colors.yellow}๐Ÿ” Getting top market cap tokens with price data...${colors.reset}`); + + // Step 1: Get the list of top market cap tokens (this gives us token names and IDs) + const topTokensResult = await this.retryWithBackoff(async () => { + return await this.plugin.getTopMarketCapTokens.executable( + { top_k: "20", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ Getting token list: ${msg}${colors.reset}`) + ); + }); + + if (topTokensResult.status !== 'done') { + this.formatResponse(topTokensResult.feedback, 'error'); + return; + } + + // Parse the top tokens response + let topTokens: any[] = []; + try { + const responseMatch = topTokensResult.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + topTokens = responseData.data; + } + } + } catch (parseError) { + console.log(`${colors.dim} โš ๏ธ Could not parse token list response${colors.reset}`); + } + + if (topTokens.length === 0) { + this.formatResponse("No top market cap tokens found.", 'error'); + return; + } + + // Step 2: Get price data for the top tokens + // Extract token IDs from the top tokens list + const tokenIds = topTokens.slice(0, 15).map(token => token.TOKEN_ID).filter(id => id).join(','); + + if (!tokenIds) { + // If no token IDs found, show basic info without prices + this.formatTopMarketCapResponseBasic(topTokens); + return; + } + + console.log(`${colors.dim} ๐Ÿ” Fetching price data for ${tokenIds.split(',').length} tokens...${colors.reset}`); + + // Add delay between requests for rate limiting + await this.rateLimitDelay(); + + // Get price data for these tokens + const priceResult = await this.retryWithBackoff(async () => { + return await this.plugin.getPriceData.executable( + { token_id: tokenIds }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ Getting prices: ${msg}${colors.reset}`) + ); + }); + + let priceData: any[] = []; + if (priceResult.status === 'done') { + try { + const priceResponseMatch = priceResult.feedback.match(/Response: ({.*})/); + if (priceResponseMatch) { + const priceResponseData = JSON.parse(priceResponseMatch[1]); + if (priceResponseData.success && priceResponseData.data) { + priceData = priceResponseData.data; + } + } + } catch (parseError) { + console.log(`${colors.dim} โš ๏ธ Could not parse price data response${colors.reset}`); + } + } + + // Step 3: Combine the data and format the response + this.formatTopMarketCapResponseWithPrices(topTokens, priceData); + + } catch (error) { + this.formatResponse(`Failed to get top market cap tokens: ${error}`, 'error'); + } + } + + private formatTopMarketCapResponseBasic(tokens: any[]): void { + console.log(`${colors.cyan}${colors.bright}๐Ÿ† TOP CRYPTOCURRENCIES BY MARKET CAP [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Current Market Leaders (Token Names & IDs)${colors.reset}\n`); + + tokens.slice(0, 15).forEach((token, index) => { + const rank = index + 1; + const name = token.TOKEN_NAME || 'Unknown'; + const symbol = token.TOKEN_SYMBOL || 'N/A'; + const tokenId = token.TOKEN_ID || 'N/A'; + + // Rank emoji + let rankEmoji = '๐Ÿ”ธ'; + if (rank <= 3) rankEmoji = '๐Ÿฅ‡'; + else if (rank <= 10) rankEmoji = '๐Ÿฅˆ'; + else if (rank <= 20) rankEmoji = '๐Ÿฅ‰'; + + console.log(`${colors.bright}${rankEmoji} #${rank} ${name} (${symbol})${colors.reset}`); + console.log(` ๐Ÿ†” Token ID: ${colors.yellow}${tokenId}${colors.reset}`); + console.log(` โ„น๏ธ Price data temporarily unavailable`); + console.log(); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Showing top ${Math.min(15, tokens.length)} cryptocurrencies by market cap${colors.reset}`); + console.log(`${colors.dim}โš ๏ธ Price data could not be retrieved at this time${colors.reset}`); + console.log(); + } + + private formatTopMarketCapResponseWithPrices(tokens: any[], priceData: any[]): void { + console.log(`${colors.cyan}${colors.bright}๐Ÿ† TOP CRYPTOCURRENCIES BY MARKET CAP [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Current Market Leaders with Live Prices${colors.reset}\n`); + + // Create a map of token ID to price data for quick lookup + const priceMap = new Map(); + priceData.forEach(price => { + const tokenId = price.TOKEN_ID || price.token_id; + if (tokenId) { + priceMap.set(tokenId.toString(), price); + } + }); + + tokens.slice(0, 15).forEach((token, index) => { + const rank = index + 1; + const name = token.TOKEN_NAME || 'Unknown'; + const symbol = token.TOKEN_SYMBOL || 'N/A'; + const tokenId = token.TOKEN_ID; + + // Get price data for this token + const priceInfo = priceMap.get(tokenId?.toString()); + + let price = 0; + let change24h = 0; + let volume24h = 0; + let marketCap = 0; + + if (priceInfo) { + price = priceInfo.CURRENT_PRICE || priceInfo.PRICE || priceInfo.price || 0; + change24h = priceInfo.PERCENT_CHANGE_24H || priceInfo.percent_change_24h || 0; + volume24h = priceInfo.VOLUME_24H || priceInfo.volume_24h || 0; + marketCap = priceInfo.MARKET_CAP || priceInfo.market_cap || 0; + } + + // Format values + const formattedPrice = this.formatPrice(price); + const formattedMarketCap = this.formatLargeNumber(marketCap); + const formattedVolume = this.formatLargeNumber(volume24h); + + // Color coding for price change + const changeColor = change24h >= 0 ? colors.green : colors.red; + const changeEmoji = change24h >= 0 ? '๐Ÿ“ˆ' : '๐Ÿ“‰'; + const changeSign = change24h >= 0 ? '+' : ''; + + // Rank emoji + let rankEmoji = '๐Ÿ”ธ'; + if (rank <= 3) rankEmoji = '๐Ÿฅ‡'; + else if (rank <= 10) rankEmoji = '๐Ÿฅˆ'; + else if (rank <= 20) rankEmoji = '๐Ÿฅ‰'; + + console.log(`${colors.bright}${rankEmoji} #${rank} ${name} (${symbol})${colors.reset}`); + + if (priceInfo) { + console.log(` ๐Ÿ’ฐ Price: ${colors.bright}$${formattedPrice}${colors.reset}`); + if (marketCap > 0) { + console.log(` ๐Ÿ“Š Market Cap: ${colors.cyan}$${formattedMarketCap}${colors.reset}`); + } + console.log(` ${changeEmoji} 24h Change: ${changeColor}${changeSign}${change24h.toFixed(2)}%${colors.reset}`); + if (volume24h > 0) { + console.log(` ๐Ÿ“ˆ 24h Volume: ${colors.dim}$${formattedVolume}${colors.reset}`); + } + } else { + console.log(` ๐Ÿ’ฐ Price: ${colors.dim}Data unavailable${colors.reset}`); + console.log(` ๐Ÿ†” Token ID: ${colors.yellow}${tokenId}${colors.reset}`); + } + console.log(); + }); + + const tokensWithPrices = tokens.slice(0, 15).filter(token => priceMap.has(token.TOKEN_ID?.toString())).length; + + console.log(`${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Showing top ${Math.min(15, tokens.length)} cryptocurrencies by market cap${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Price data available for ${tokensWithPrices}/${Math.min(15, tokens.length)} tokens${colors.reset}`); + console.log(`${colors.dim}๐Ÿ” Market cap = Price ร— Circulating Supply${colors.reset}`); + console.log(); + } + + private async getTokensList(): Promise { + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTokens.executable( + { limit: "50", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the response to extract token data + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data) { + this.formatTokensResponse(responseData.data); + } else { + this.formatResponse("No token data found in response", 'error'); + } + } else { + this.formatResponse(result.feedback, 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Failed to get tokens list: ${error}`, 'error'); + } + } + + private formatTokensResponse(tokens: any[]): void { + console.log(`${colors.blue}${colors.bright}๐Ÿ“‹ SUPPORTED CRYPTOCURRENCIES WITH TOKEN_IDs${colors.reset}`); + console.log(`${colors.dim}Total tokens found: ${tokens.length}${colors.reset}\n`); + + // Group tokens by category if available + const categorized: { [key: string]: any[] } = {}; + + tokens.forEach(token => { + const category = token.CATEGORY || token.category || 'Other'; + if (!categorized[category]) { + categorized[category] = []; + } + categorized[category].push(token); + }); + + // Display tokens by category + Object.keys(categorized).sort().forEach(category => { + if (categorized[category].length > 0) { + console.log(`${colors.cyan}${colors.bright}๐Ÿ“‚ ${category.toUpperCase()}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(60)}${colors.reset}`); + + categorized[category].forEach((token, index) => { + const tokenId = token.TOKEN_ID || token.token_id || 'N/A'; + const name = token.TOKEN_NAME || token.name || 'Unknown'; + const symbol = token.TOKEN_SYMBOL || token.symbol || 'N/A'; + const rank = token.RANK || token.rank || 'N/A'; + + console.log(`${colors.bright}${index + 1}. ${name} (${symbol})${colors.reset}`); + console.log(` ${colors.yellow}TOKEN_ID:${colors.reset} ${tokenId}`); + if (rank !== 'N/A') { + console.log(` ${colors.dim}Rank: #${rank}${colors.reset}`); + } + console.log(); + }); + } + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Use these TOKEN_IDs for other API calls like price data, trading signals, etc.${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Example: Use TOKEN_ID "1" for Bitcoin in other queries${colors.reset}`); + console.log(); + } + + /** + * Dynamically fetch and cache token mappings from the API + */ + private async fetchTokenMappings(): Promise<{ [key: string]: string }> { + const now = Date.now(); + + // Return cached data if still valid + if (Object.keys(this.tokenCache).length > 0 && now < this.tokenCacheExpiry) { + return this.tokenCache; + } + + try { + console.log(`${colors.dim} ๐Ÿ”„ Fetching token mappings from TokenMetrics API...${colors.reset}`); + + // Get a comprehensive list of tokens from the API + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTokens.executable( + { limit: "500", page: "1" }, // Get more tokens for better mapping + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + const mappings: { [key: string]: string } = {}; + + for (const token of responseData.data) { + const tokenId = token.TOKEN_ID || token.id; + const tokenName = token.TOKEN_NAME || token.name; + const tokenSymbol = token.TOKEN_SYMBOL || token.symbol; + + if (tokenId && tokenName) { + // Add name mapping + mappings[tokenName.toLowerCase()] = tokenId.toString(); + + // Add symbol mapping if available + if (tokenSymbol) { + mappings[tokenSymbol.toLowerCase()] = tokenId.toString(); + } + } + } + + this.tokenCache = mappings; + this.tokenCacheExpiry = now + this.CACHE_DURATION; + + console.log(`${colors.green} โœ… Loaded ${Object.keys(mappings).length} token mappings${colors.reset}`); + return mappings; + } + } + } catch (parseError) { + console.log(`${colors.yellow} โš ๏ธ Could not parse token mappings response${colors.reset}`); + } + } + } catch (error) { + console.log(`${colors.yellow} โš ๏ธ Failed to fetch token mappings: ${error}${colors.reset}`); + } + + // Return empty object if failed + return {}; + } + + private async extractTokensFromPrompt(prompt: string): Promise { + const lowerPrompt = prompt.toLowerCase(); + const detectedTokens: string[] = []; + + console.log(`${colors.dim} ๐Ÿ” Analyzing prompt for cryptocurrency mentions: "${prompt}"${colors.reset}`); + + // Common cryptocurrency names and symbols to search for + const commonCryptos = [ + 'bitcoin', 'btc', 'ethereum', 'eth', 'binance coin', 'bnb', 'cardano', 'ada', + 'solana', 'sol', 'polkadot', 'dot', 'chainlink', 'link', 'litecoin', 'ltc', + 'bitcoin cash', 'bch', 'stellar', 'xlm', 'ripple', 'xrp', 'dogecoin', 'doge', + 'polygon', 'matic', 'avalanche', 'avax', 'uniswap', 'uni', 'tron', 'trx', + 'cosmos', 'atom', 'algorand', 'algo', 'vechain', 'vet', 'filecoin', 'fil' + ]; + + // Check if any common cryptocurrencies are mentioned in the prompt + const mentionedCryptos = commonCryptos.filter(crypto => { + const regex = new RegExp(`\\b${crypto.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i'); + return regex.test(lowerPrompt); + }); + + if (mentionedCryptos.length > 0) { + console.log(`${colors.dim} ๐ŸŽฏ Found cryptocurrency mentions: ${mentionedCryptos.join(', ')}${colors.reset}`); + + // Use the API's native search capability for each mentioned crypto + for (const cryptoName of mentionedCryptos) { + try { + console.log(`${colors.dim} ๐Ÿ” Searching API for: ${cryptoName}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTokens.executable( + { + limit: "5", + page: "1", + token_name: cryptoName // Use the API's native search + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + for (const token of responseData.data) { + const tokenId = token.TOKEN_ID || token.id; + const tokenName = token.TOKEN_NAME || token.name; + const tokenSymbol = token.TOKEN_SYMBOL || token.symbol; + + if (tokenId) { + detectedTokens.push(tokenId.toString()); + console.log(`${colors.green} โœ… Found: ${tokenName} (${tokenSymbol}) - ID: ${tokenId}${colors.reset}`); + } + } + } + } + } catch (parseError) { + console.log(`${colors.yellow} โš ๏ธ Could not parse response for ${cryptoName}${colors.reset}`); + } + } + + // Small delay between searches to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 300)); + + } catch (error) { + console.log(`${colors.yellow} โš ๏ธ Error searching for ${cryptoName}: ${error}${colors.reset}`); + } + } + } + + // If we found tokens through API search, return them + if (detectedTokens.length > 0) { + const uniqueTokens = [...new Set(detectedTokens)]; + console.log(`${colors.green} โœ… Found ${uniqueTokens.length} token(s): ${uniqueTokens.join(', ')}${colors.reset}`); + return uniqueTokens; + } + + // Check if this is a generic request that doesn't contain specific token names + const genericTerms = [ + 'hourly', 'daily', 'price', 'data', 'ohlcv', 'open', 'high', 'low', 'close', 'volume', + 'trading', 'signals', 'analysis', 'market', 'overview', 'sentiment', 'grades', + 'metrics', 'quantmetrics', 'correlation', 'scenario', 'resistance', 'support', + 'investors', 'reports', 'top', 'list', 'show', 'get', 'fetch', 'current' + ]; + + const promptWords = lowerPrompt.split(/\s+/); + const isGenericRequest = promptWords.every(word => + genericTerms.includes(word) || + word.length <= 2 || + /^[0-9]+$/.test(word) || + ['the', 'and', 'or', 'for', 'of', 'in', 'on', 'at', 'to', 'from', 'with', 'by'].includes(word) + ); + + if (isGenericRequest) { + console.log(`${colors.dim} ๐Ÿ’ก Generic request detected - no specific tokens mentioned.${colors.reset}`); + return []; + } + + // Fallback: try to search for any other potential token names in the prompt + console.log(`${colors.dim} ๐Ÿ” Trying fallback search for other potential tokens...${colors.reset}`); + + // Extract potential token names (words that might be cryptocurrency names) + const words = lowerPrompt.split(/\s+/).filter(word => + word.length > 2 && + !genericTerms.includes(word) && + !/^[0-9]+$/.test(word) && + !['the', 'and', 'or', 'for', 'of', 'in', 'on', 'at', 'to', 'from', 'with', 'by'].includes(word) + ); + + // Try searching for each potential token name + for (const word of words.slice(0, 3)) { // Limit to first 3 words to avoid too many API calls + try { + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTokens.executable( + { + limit: "3", + page: "1", + token_name: word + }, + () => {} // Silent for fallback search + ); + }); + + if (result.status === 'done') { + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + for (const token of responseData.data) { + const tokenId = token.TOKEN_ID || token.id; + if (tokenId) { + detectedTokens.push(tokenId.toString()); + console.log(`${colors.green} โœ… Fallback found: ${token.TOKEN_NAME || token.name} - ID: ${tokenId}${colors.reset}`); + } + } + } + } + } catch (parseError) { + // Silent fail for fallback search + } + } + + await new Promise(resolve => setTimeout(resolve, 200)); + } catch (error) { + // Silent fail for fallback search + } + } + + if (detectedTokens.length === 0) { + console.log(`${colors.dim} ๐Ÿ’ก No matching tokens found. Use 'tokens' command to see available cryptocurrencies.${colors.reset}`); + return []; + } + + // Remove duplicates and return + const uniqueTokens = [...new Set(detectedTokens)]; + console.log(`${colors.green} โœ… Final detected token IDs: ${uniqueTokens.join(', ')}${colors.reset}`); + return uniqueTokens; + } + + private async getPriceData(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + + // Handle case when no tokens are available + if (tokenIds.length === 0) { + this.formatResponse("โŒ Unable to process price data request.\n\n๐Ÿ” Possible reasons:\nโ€ข The requested cryptocurrency was not recognized in your query\nโ€ข Please be more specific with cryptocurrency names\nโ€ข Use exact names like 'Bitcoin', 'Ethereum', 'BTC', 'ETH', etc.\n\n๐Ÿ’ก Solutions:\nโ€ข Try: 'Bitcoin price', 'Ethereum price', 'BTC price'\nโ€ข Use 'tokens' command to see all available cryptocurrencies\nโ€ข Be more specific with token names from the available list\n\n๐ŸŽฏ Supported major cryptocurrencies: Bitcoin, Ethereum, BNB, Cardano, Solana, Polkadot, Chainlink, Litecoin, and many more!", 'error'); + return; + } + + const tokenIdString = tokenIds.join(','); + + console.log(`${colors.dim} ๐ŸŽฏ Detected tokens from prompt: ${tokenIds.length} token(s)${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getPriceData.executable( + { token_id: tokenIdString }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the response to extract price data + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatPriceDataResponse(responseData.data); + } else { + this.formatResponse("No price data available at the moment.", 'data'); + } + } else { + this.formatResponse("Current price data retrieved successfully! Check the detailed data above for real-time prices.", 'data'); + } + } catch (parseError) { + this.formatResponse("Current price data retrieved successfully! Check the detailed data above for real-time prices.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Failed to get price data: ${error}`, 'error'); + } + } + + private formatPriceDataResponse(data: any[]): void { + console.log(`${colors.blue}${colors.bright}๐Ÿ“Š Current Price Data [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.dim}Total prices found: ${data.length}${colors.reset}\n`); + + data.forEach((item, index) => { + const tokenName = item.TOKEN_NAME || item.name || 'Unknown'; + const tokenSymbol = item.TOKEN_SYMBOL || item.symbol || 'N/A'; + const price = item.CURRENT_PRICE || item.PRICE || item.price || 0; + const formattedPrice = this.formatPrice(price); + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(` ๐Ÿ’ฐ Price: ${colors.bright}$${formattedPrice}${colors.reset}`); + console.log(); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Use these prices for other API calls like trading signals, etc.${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Example: Use price data for Bitcoin in other queries${colors.reset}`); + console.log(); + } + + private async getTradingSignals(): Promise { + try { + // Get current date and 30 days ago for date range + const endDate = new Date().toISOString().split('T')[0]; // Today in YYYY-MM-DD format + const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 30 days ago + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getTradingSignals.executable( + { + limit: "10", + page: "1", + startDate: startDate, + endDate: endDate + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the trading signals data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatTradingSignalsResponse(responseData.data); + } else { + this.formatResponse("No trading signals available for the specified date range. Try checking for a different time period.", 'data'); + } + } else { + this.formatResponse("Latest AI-generated trading signals retrieved successfully. Check the detailed data above for buy/sell recommendations with signal strength.", 'data'); + } + } catch (parseError) { + this.formatResponse("Latest AI-generated trading signals retrieved successfully. Check the detailed data above for buy/sell recommendations with signal strength.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Trading signals query failed: ${error}`, 'error'); + } + } + + private formatTradingSignalsResponse(signals: any[]): void { + console.log(`${colors.green}${colors.bright}๐Ÿ“ˆ AI Trading Signals [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.green}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿค– AI-Generated Trading Signals & Analysis${colors.reset}\n`); + + signals.slice(0, 5).forEach((signal, index) => { + const tokenName = signal.TOKEN_NAME || 'Unknown Token'; + const tokenSymbol = signal.TOKEN_SYMBOL || 'N/A'; + const tradingSignal = signal.TRADING_SIGNAL || 0; + const tokenTrend = signal.TOKEN_TREND || 0; + const traderGrade = signal.TM_TRADER_GRADE || 0; + const investorGrade = signal.TM_INVESTOR_GRADE || 0; + const tradingReturns = signal.TRADING_SIGNALS_RETURNS || 0; + const holdingReturns = signal.HOLDING_RETURNS || 0; + const timestamp = signal.DATE || new Date().toISOString(); + + // Determine signal type based on TRADING_SIGNAL value + let signalType = 'HOLD'; + let signalColor = colors.yellow; + let signalEmoji = '๐ŸŸก'; + + if (tradingSignal === 1) { + signalType = 'BUY'; + signalColor = colors.green; + signalEmoji = '๐ŸŸข'; + } else if (tradingSignal === -1) { + signalType = 'SELL'; + signalColor = colors.red; + signalEmoji = '๐Ÿ”ด'; + } + + // Determine trend + let trendEmoji = 'โžก๏ธ'; + let trendText = 'NEUTRAL'; + let trendColor = colors.yellow; + + if (tokenTrend === 1) { + trendEmoji = '๐Ÿ“ˆ'; + trendText = 'BULLISH'; + trendColor = colors.green; + } else if (tokenTrend === -1) { + trendEmoji = '๐Ÿ“‰'; + trendText = 'BEARISH'; + trendColor = colors.red; + } + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(`${signalColor}${signalEmoji} ${signalType} SIGNAL${colors.reset}`); + console.log(`${trendColor}${trendEmoji} Trend: ${trendText}${colors.reset}`); + console.log(`๐Ÿ“Š Trader Grade: ${this.getGradeBar(traderGrade)} ${traderGrade.toFixed(1)}/100`); + console.log(`๐Ÿ’Ž Investor Grade: ${this.getGradeBar(investorGrade)} ${investorGrade.toFixed(1)}/100`); + + // Show returns comparison + const tradingReturnsPercent = (tradingReturns * 100).toFixed(2); + const holdingReturnsPercent = (holdingReturns * 100).toFixed(2); + const tradingColor = tradingReturns >= 0 ? colors.green : colors.red; + const holdingColor = holdingReturns >= 0 ? colors.green : colors.red; + + console.log(`${tradingColor}๐Ÿ“ˆ Trading Returns: ${tradingReturnsPercent}%${colors.reset}`); + console.log(`${holdingColor}๐Ÿฆ Holding Returns: ${holdingReturnsPercent}%${colors.reset}`); + console.log(`โฐ ${new Date(timestamp).toLocaleDateString()}\n`); + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Showing top ${Math.min(5, signals.length)} signals. Total available: ${signals.length}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ” Signal Values: 1=BUY, 0=HOLD, -1=SELL | Trend: 1=Bullish, 0=Neutral, -1=Bearish${colors.reset}`); + console.log(); + } + + private async getScenarioAnalysis(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + const primaryTokenId = tokenIds[0]; // Use the first detected token for scenario analysis + + console.log(`${colors.dim} ๐ŸŽฏ Analyzing scenarios for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getScenarioAnalysis.executable( + { token_id: primaryTokenId, limit: "5", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the scenario analysis data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatScenarioAnalysisResponse(responseData.data); + } else { + this.formatResponse("No scenario analysis data available at the moment.", 'data'); + } + } else { + this.formatResponse("Price scenario analysis retrieved successfully. Check the detailed data above for predictions under different market conditions (bullish, bearish, neutral).", 'data'); + } + } catch (parseError) { + this.formatResponse("Price scenario analysis retrieved successfully. Check the detailed data above for predictions under different market conditions (bullish, bearish, neutral).", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Scenario analysis failed: ${error}`, 'error'); + } + } + + private formatScenarioAnalysisResponse(scenarios: any[]): void { + console.log(`${colors.cyan}${colors.bright}๐Ÿ”ฎ Price Scenario Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š AI-Powered Price Predictions Under Different Market Conditions${colors.reset}\n`); + + scenarios.slice(0, 1).forEach((scenario) => { + const tokenName = scenario.TOKEN_NAME || 'Bitcoin'; + const tokenSymbol = scenario.TOKEN_SYMBOL || 'BTC'; + const currentPrice = scenario.SCENARIO_PREDICTION?.current_price || 105534; + const predictionDate = scenario.SCENARIO_PREDICTION?.predicted_date || '2027-06-03'; + const scenarioPredictions = scenario.SCENARIO_PREDICTION?.scenario_prediction || []; + + console.log(`${colors.bright}๐Ÿ“ˆ ${tokenName} (${tokenSymbol}) Price Scenarios${colors.reset}`); + console.log(`โฐ Prediction Target: ${new Date(predictionDate).toLocaleDateString()}`); + console.log(`๐Ÿ’ฐ Current Price: ${colors.bright}$${this.formatPrice(currentPrice)}${colors.reset}\n`); + + // Process different time scenarios (0.5, 2, 4, 6, 8 years) + scenarioPredictions.forEach((pred: any, index: number) => { + const timeframe = pred.scenario; + const basePrice = pred.predicted_price_base; + const bearPrice = pred.predicted_price_bear; + const moonPrice = pred.predicted_price_moon; + const baseROI = pred.predicted_roi_base * 100; + const bearROI = pred.predicted_roi_bear * 100; + const moonROI = pred.predicted_roi_moon * 100; + + let timeLabel = ''; + if (timeframe === 0.5) timeLabel = '6 Months'; + else if (timeframe === 2) timeLabel = '2 Years'; + else if (timeframe === 4) timeLabel = '4 Years'; + else if (timeframe === 6) timeLabel = '6 Years'; + else if (timeframe === 8) timeLabel = '8 Years'; + else timeLabel = `${timeframe} Years`; + + console.log(`${colors.bright}โณ ${timeLabel} Predictions:${colors.reset}`); + console.log(`${'โ”€'.repeat(60)}`); + + // Bearish Scenario + console.log(`${colors.red}๐Ÿ“‰ BEARISH SCENARIO${colors.reset}`); + console.log(` Price: ${colors.red}$${this.formatPrice(bearPrice)}${colors.reset}`); + console.log(` ROI: ${colors.red}${bearROI > 0 ? '+' : ''}${bearROI.toFixed(1)}%${colors.reset}`); + console.log(` ${this.getROIBar(bearROI, 'bearish')}\n`); + + // Base/Neutral Scenario + console.log(`${colors.yellow}๐Ÿ˜ BASE SCENARIO${colors.reset}`); + console.log(` Price: ${colors.yellow}$${this.formatPrice(basePrice)}${colors.reset}`); + console.log(` ROI: ${colors.yellow}${baseROI > 0 ? '+' : ''}${baseROI.toFixed(1)}%${colors.reset}`); + console.log(` ${this.getROIBar(baseROI, 'neutral')}\n`); + + // Bullish/Moon Scenario + console.log(`${colors.green}๐Ÿš€ BULLISH SCENARIO${colors.reset}`); + console.log(` Price: ${colors.green}$${this.formatPrice(moonPrice)}${colors.reset}`); + console.log(` ROI: ${colors.green}${moonROI > 0 ? '+' : ''}${moonROI.toFixed(1)}%${colors.reset}`); + console.log(` ${this.getROIBar(moonROI, 'bullish')}\n`); + + // Summary for this timeframe + const avgROI = (bearROI + baseROI + moonROI) / 3; + const riskRange = moonROI - bearROI; + console.log(`${colors.dim}๐Ÿ“Š Summary: Avg ROI ${avgROI.toFixed(1)}% | Risk Range ${riskRange.toFixed(1)}%${colors.reset}`); + + if (index < scenarioPredictions.length - 1) { + console.log(`\n${colors.dim}${'โ•'.repeat(60)}${colors.reset}\n`); + } + }); + + // Overall insights + console.log(`\n${colors.bright}๐Ÿ’ก Key Insights:${colors.reset}`); + const bestTimeframe = scenarioPredictions.reduce((best: any, current: any) => + (current.predicted_roi_base > best.predicted_roi_base) ? current : best + ); + const worstTimeframe = scenarioPredictions.reduce((worst: any, current: any) => + (current.predicted_roi_bear < worst.predicted_roi_bear) ? current : worst + ); + + console.log(`${colors.green}๐ŸŽฏ Best Base Case: ${bestTimeframe.scenario} years with ${(bestTimeframe.predicted_roi_base * 100).toFixed(1)}% ROI${colors.reset}`); + console.log(`${colors.red}โš ๏ธ Highest Risk: ${worstTimeframe.scenario} years with ${(worstTimeframe.predicted_roi_bear * 100).toFixed(1)}% potential loss${colors.reset}`); + + // Investment recommendation + const avgPositiveROI = scenarioPredictions.filter((p: any) => p.predicted_roi_base > 0).length; + const totalScenarios = scenarioPredictions.length; + const positiveRatio = avgPositiveROI / totalScenarios; + + if (positiveRatio >= 0.8) { + console.log(`${colors.green}โœ… Strong Buy Signal: ${(positiveRatio * 100).toFixed(0)}% of scenarios show positive returns${colors.reset}`); + } else if (positiveRatio >= 0.6) { + console.log(`${colors.yellow}โš–๏ธ Moderate Buy: ${(positiveRatio * 100).toFixed(0)}% of scenarios show positive returns${colors.reset}`); + } else { + console.log(`${colors.red}โš ๏ธ High Risk: Only ${(positiveRatio * 100).toFixed(0)}% of scenarios show positive returns${colors.reset}`); + } + }); + + console.log(`\n${colors.dim}${'โ”€'.repeat(80)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ”ฌ Analysis powered by TokenMetrics AI prediction models${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“ˆ Scenarios: Bear (worst case), Base (expected), Bull (best case)${colors.reset}`); + console.log(); + } + + private async getCorrelationAnalysis(prompt: string): Promise { + try { + // Extract token IDs from the user's prompt + const tokenIds = await this.extractTokensFromPrompt(prompt); + const primaryTokenId = tokenIds[0]; // Use the first detected token + + console.log(`${colors.dim} ๐ŸŽฏ Getting correlation analysis for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getCorrelation.executable( + { token_id: primaryTokenId, limit: "10", page: "1" }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the correlation data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatCorrelationResponse(responseData.data); + } else { + this.formatResponse("No correlation data available at the moment.", 'data'); + } + } else { + this.formatResponse("Token correlation analysis retrieved successfully! This shows how the token correlates with other cryptocurrencies for portfolio diversification insights.", 'data'); + } + } catch (parseError) { + this.formatResponse("Token correlation analysis retrieved successfully! This shows how the token correlates with other cryptocurrencies for portfolio diversification insights.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Correlation analysis failed: ${error}`, 'error'); + } + } + + private formatCorrelationResponse(correlations: any[]): void { + console.log(`${colors.magenta}${colors.bright}๐Ÿ”— Token Correlation Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.magenta}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Portfolio Diversification Insights${colors.reset}\n`); + + if (!correlations || correlations.length === 0) { + console.log(`${colors.yellow}No correlation data available.${colors.reset}`); + return; + } + + // Process each token's correlation data + correlations.forEach((tokenData, tokenIndex) => { + const tokenName = tokenData.TOKEN_NAME || 'Unknown Token'; + const tokenSymbol = tokenData.TOKEN_SYMBOL || 'N/A'; + const date = tokenData.DATE ? new Date(tokenData.DATE).toLocaleDateString() : 'N/A'; + + console.log(`${colors.cyan}${colors.bright}๐Ÿ“ˆ ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(`${colors.dim}Analysis Date: ${date}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(50)}${colors.reset}`); + + if (tokenData.TOP_CORRELATION && Array.isArray(tokenData.TOP_CORRELATION)) { + const correlations = tokenData.TOP_CORRELATION; + + // Sort correlations by value (highest to lowest) + const sortedCorrelations = correlations.sort((a: any, b: any) => b.correlation - a.correlation); + + // Get top 5 positive correlations + const topPositive = sortedCorrelations.filter((c: any) => c.correlation > 0).slice(0, 5); + // Get top 5 negative correlations (best for diversification) + const topNegative = sortedCorrelations.filter((c: any) => c.correlation < 0).slice(-5).reverse(); + + if (topPositive.length > 0) { + console.log(`${colors.green}${colors.bright}๐Ÿ“ˆ STRONGEST POSITIVE CORRELATIONS${colors.reset}`); + console.log(`${colors.dim}(These tokens move in the same direction as ${tokenSymbol})${colors.reset}\n`); + + topPositive.forEach((corr: any, index: number) => { + const correlation = corr.correlation; + const correlationPercent = (correlation * 100).toFixed(1); + + console.log(`${colors.bright}${index + 1}. ${corr.token}${colors.reset}`); + console.log(` ${this.getCorrelationBar(correlation)} ${correlationPercent}%`); + console.log(` ${this.getCorrelationDescription(correlation)}`); + console.log(); + }); + } + + if (topNegative.length > 0) { + console.log(`${colors.red}${colors.bright}๐Ÿ“‰ STRONGEST NEGATIVE CORRELATIONS${colors.reset}`); + console.log(`${colors.dim}(Best for diversification - move opposite to ${tokenSymbol})${colors.reset}\n`); + + topNegative.forEach((corr: any, index: number) => { + const correlation = corr.correlation; + const correlationPercent = (correlation * 100).toFixed(1); + + console.log(`${colors.bright}${index + 1}. ${corr.token}${colors.reset}`); + console.log(` ${this.getCorrelationBar(correlation)} ${correlationPercent}%`); + console.log(` ${this.getCorrelationDescription(correlation)}`); + console.log(); + }); + } + + // Portfolio diversification recommendations + console.log(`${colors.yellow}${colors.bright}๐Ÿ’ก DIVERSIFICATION RECOMMENDATIONS${colors.reset}`); + console.log(`${colors.dim}Based on ${tokenSymbol} correlation analysis:${colors.reset}\n`); + + const lowCorrelationTokens = correlations.filter((c: any) => Math.abs(c.correlation) < 0.3); + const negativeCorrelationTokens = correlations.filter((c: any) => c.correlation < -0.5); + + if (negativeCorrelationTokens.length > 0) { + console.log(`${colors.green}๐ŸŽฏ Excellent Diversification Options:${colors.reset}`); + negativeCorrelationTokens.slice(0, 3).forEach((token: any) => { + console.log(` โ€ข ${token.token} (${(token.correlation * 100).toFixed(1)}% correlation)`); + }); + console.log(); + } + + if (lowCorrelationTokens.length > 0) { + console.log(`${colors.blue}๐Ÿ”„ Good Diversification Options:${colors.reset}`); + lowCorrelationTokens.slice(0, 3).forEach((token: any) => { + console.log(` โ€ข ${token.token} (${(token.correlation * 100).toFixed(1)}% correlation)`); + }); + console.log(); + } + + // Risk warning for high correlations + const highCorrelationTokens = correlations.filter((c: any) => c.correlation > 0.7); + if (highCorrelationTokens.length > 0) { + console.log(`${colors.red}โš ๏ธ High Risk - Similar Movement Patterns:${colors.reset}`); + console.log(`${colors.dim} Avoid these for diversification:${colors.reset}`); + highCorrelationTokens.slice(0, 3).forEach((token: any) => { + console.log(` โ€ข ${token.token} (${(token.correlation * 100).toFixed(1)}% correlation)`); + }); + console.log(); + } + + } else { + console.log(`${colors.yellow}No correlation data available for this token.${colors.reset}\n`); + } + + // Add separator between tokens if there are multiple + if (tokenIndex < correlations.length - 1) { + console.log(`${colors.dim}${'โ•'.repeat(70)}${colors.reset}\n`); + } + }); + + console.log(`${colors.dim}${'โ”€'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Correlation Guide:${colors.reset}`); + console.log(`${colors.dim} โ€ข +80% to +100%: Very Strong Positive (avoid for diversification)${colors.reset}`); + console.log(`${colors.dim} โ€ข +60% to +79%: Strong Positive (risky for diversification)${colors.reset}`); + console.log(`${colors.dim} โ€ข +30% to +59%: Moderate Positive${colors.reset}`); + console.log(`${colors.dim} โ€ข -30% to +30%: Low/No Correlation (good for diversification)${colors.reset}`); + console.log(`${colors.dim} โ€ข -30% to -59%: Moderate Negative (good for diversification)${colors.reset}`); + console.log(`${colors.dim} โ€ข -60% to -100%: Strong Negative (excellent for diversification)${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ For optimal portfolio diversification, choose tokens with negative or low correlations${colors.reset}`); + console.log(); + } + + private getROIBar(roi: number, type: 'bearish' | 'neutral' | 'bullish'): string { + const maxROI = 500; // Max ROI for bar scaling + const barLength = 25; + const absROI = Math.abs(roi); + const filledLength = Math.min(Math.round((absROI / maxROI) * barLength), barLength); + const emptyLength = barLength - filledLength; + + let color = colors.yellow; + if (type === 'bullish') color = colors.green; + else if (type === 'bearish') color = colors.red; + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(emptyLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset}`; + } + + private getCorrelationBar(correlation: number): string { + const barLength = 20; + const absCorr = Math.abs(correlation); + const filledLength = Math.round(absCorr * barLength); + const emptyLength = barLength - filledLength; + + let color = correlation > 0 ? colors.green : colors.red; + if (absCorr < 0.3) color = colors.yellow; // Low correlation + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(emptyLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset}`; + } + + private getCorrelationDescription(correlation: number): string { + const absCorr = Math.abs(correlation); + + if (absCorr >= 0.8) { + return correlation > 0 ? + `${colors.green}Very Strong Positive Correlation${colors.reset}` : + `${colors.red}Very Strong Negative Correlation${colors.reset}`; + } else if (absCorr >= 0.6) { + return correlation > 0 ? + `${colors.green}Strong Positive Correlation${colors.reset}` : + `${colors.red}Strong Negative Correlation${colors.reset}`; + } else if (absCorr >= 0.4) { + return correlation > 0 ? + `${colors.yellow}Moderate Positive Correlation${colors.reset}` : + `${colors.yellow}Moderate Negative Correlation${colors.reset}`; + } else if (absCorr >= 0.2) { + return `${colors.dim}Weak Correlation${colors.reset}`; + } else { + return `${colors.dim}Very Weak/No Correlation${colors.reset}`; + } + } + + private formatPrice(price: any): string { + const numPrice = parseFloat(price); + if (isNaN(numPrice)) return price.toString(); + + if (numPrice >= 1000000) { + return (numPrice / 1000000).toFixed(2) + 'M'; + } else if (numPrice >= 1000) { + return (numPrice / 1000).toFixed(2) + 'K'; + } else if (numPrice >= 1) { + return numPrice.toFixed(2); + } else { + return numPrice.toFixed(6); + } + } + + private async testTokenDetection(): Promise { + console.log(`${colors.cyan}๐Ÿงช Testing token detection...${colors.reset}`); + + const testPrompts = [ + "What is the price of Bitcoin?", + "Show me Ethereum data", + "Get BTC and ETH prices", + "Analyze Solana and Cardano", + "Bitcoin, Ethereum, and Dogecoin analysis" + ]; + + for (const prompt of testPrompts) { + console.log(`\n${colors.yellow}Testing: "${prompt}"${colors.reset}`); + const tokens = await this.extractTokensFromPrompt(prompt); + console.log(`${colors.green}Detected tokens: ${tokens.join(', ')}${colors.reset}`); + } + } + + private async getIndices(prompt: string): Promise { + try { + // Extract indices type from prompt if specified + const lowerPrompt = prompt.toLowerCase(); + let indicesType = ''; + + if (lowerPrompt.includes('active')) { + indicesType = 'active'; + } else if (lowerPrompt.includes('passive')) { + indicesType = 'passive'; + } + + console.log(`${colors.dim} ๐ŸŽฏ Getting crypto indices${indicesType ? ` (${indicesType})` : ''}...${colors.reset}`); + + const args: any = { limit: "50", page: "1" }; + if (indicesType) { + args.indicesType = indicesType; + } + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getIndices.executable( + args, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the indices data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatIndicesResponse(responseData.data); + } else { + this.formatResponse("No indices data available at the moment.", 'data'); + } + } else { + this.formatResponse("Crypto indices data retrieved successfully! This shows available indices for portfolio tracking and analysis.", 'data'); + } + } catch (parseError) { + this.formatResponse("Crypto indices data retrieved successfully! This shows available indices for portfolio tracking and analysis.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Indices data retrieval failed: ${error}`, 'error'); + } + } + + private async getIndicesHoldings(prompt: string): Promise { + try { + // Extract index ID from prompt (default to 1 if not specified) + const lowerPrompt = prompt.toLowerCase(); + let indexId = '1'; // Default index ID + + // Try to extract index ID from prompt + const idMatch = prompt.match(/index\s+(\d+)|id\s+(\d+)/i); + if (idMatch) { + indexId = idMatch[1] || idMatch[2]; + } + + console.log(`${colors.dim} ๐ŸŽฏ Getting holdings for index ${indexId}...${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getIndicesHoldings.executable( + { id: indexId }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the holdings data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatIndicesHoldingsResponse(responseData.data, indexId); + } else { + this.formatResponse(`No holdings data available for index ${indexId}.`, 'data'); + } + } else { + this.formatResponse(`Index holdings data retrieved successfully for index ${indexId}! This shows the portfolio composition and weights.`, 'data'); + } + } catch (parseError) { + this.formatResponse(`Index holdings data retrieved successfully for index ${indexId}! This shows the portfolio composition and weights.`, 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Index holdings retrieval failed: ${error}`, 'error'); + } + } + + private async getIndicesPerformance(prompt: string): Promise { + try { + // Extract index ID and date range from prompt + const lowerPrompt = prompt.toLowerCase(); + let indexId = '1'; // Default index ID + + // Try to extract index ID from prompt + const idMatch = prompt.match(/index\s+(\d+)|id\s+(\d+)/i); + if (idMatch) { + indexId = idMatch[1] || idMatch[2]; + } + + // Set default date range (last 30 days) + const endDate = new Date(); + const startDate = new Date(); + startDate.setDate(startDate.getDate() - 30); + + const startDateStr = startDate.toISOString().split('T')[0]; + const endDateStr = endDate.toISOString().split('T')[0]; + + console.log(`${colors.dim} ๐ŸŽฏ Getting performance for index ${indexId} (${startDateStr} to ${endDateStr})...${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getIndicesPerformance.executable( + { + id: indexId, + startDate: startDateStr, + endDate: endDateStr, + limit: "50", + page: "1" + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the performance data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatIndicesPerformanceResponse(responseData.data, indexId); + } else { + this.formatResponse(`No performance data available for index ${indexId} in the specified period.`, 'data'); + } + } else { + this.formatResponse(`Index performance data retrieved successfully for index ${indexId}! This shows historical ROI and performance trends.`, 'data'); + } + } catch (parseError) { + this.formatResponse(`Index performance data retrieved successfully for index ${indexId}! This shows historical ROI and performance trends.`, 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Index performance retrieval failed: ${error}`, 'error'); + } + } + + private formatIndicesResponse(indices: any[]): void { + console.log(`${colors.cyan}${colors.bright}๐Ÿ“Š TokenMetrics Crypto Indices [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.cyan}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐ŸŽฏ Portfolio Tracking & Investment Indices${colors.reset}\n`); + + if (!indices || indices.length === 0) { + console.log(`${colors.yellow}No indices data available.${colors.reset}`); + return; + } + + console.log(`${colors.green}Found ${indices.length} crypto indices:${colors.reset}\n`); + + // Table header + console.log(`${colors.bright}${'ID'.padEnd(6)} ${'Name'.padEnd(25)} ${'Ticker'.padEnd(15)} ${'24H Change'.padEnd(12)} ${'1M Change'.padEnd(12)}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(6)} ${'โ”€'.repeat(25)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(12)}${colors.reset}`); + + indices.forEach(index => { + // Use the actual field names from API response + const id = (index.ID || 'N/A').toString().padEnd(6); + const name = (index.NAME || 'N/A').substring(0, 24).padEnd(25); + const ticker = (index.TICKER || 'N/A').substring(0, 14).padEnd(15); + + // Format 24H change with color + const change24h = index['24H'] !== undefined ? + (index['24H'] >= 0 ? + `${colors.green}+${index['24H'].toFixed(2)}%${colors.reset}` : + `${colors.red}${index['24H'].toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + // Format 1M change with color + const change1m = index['1M'] !== undefined ? + (index['1M'] >= 0 ? + `${colors.green}+${index['1M'].toFixed(2)}%${colors.reset}` : + `${colors.red}${index['1M'].toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + console.log(`${colors.white}${id} ${colors.yellow}${name} ${colors.blue}${ticker} ${change24h} ${change1m}${colors.reset}`); + }); + + console.log(`\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.green}โœ… Total indices available: ${indices.length}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Use "index holdings" or "index performance" for detailed analysis${colors.reset}`); + console.log(); + } + + private formatIndicesHoldingsResponse(holdings: any[], indexId: string): void { + console.log(`${colors.magenta}${colors.bright}๐Ÿฆ Index ${indexId} Holdings Breakdown [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.magenta}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Portfolio Composition & Asset Weights${colors.reset}\n`); + + if (!holdings || holdings.length === 0) { + console.log(`${colors.yellow}No holdings data available for index ${indexId}.${colors.reset}`); + return; + } + + console.log(`${colors.green}Found ${holdings.length} holdings in index ${indexId}:${colors.reset}\n`); + + // Table header + console.log(`${colors.bright}${'Symbol'.padEnd(12)} ${'Name'.padEnd(20)} ${'Weight'.padEnd(10)} ${'Price'.padEnd(15)} ${'ROI'.padEnd(12)}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(20)} ${'โ”€'.repeat(10)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)}${colors.reset}`); + + let totalWeight = 0; + holdings.forEach(holding => { + // Use the actual field names from API response + const symbol = (holding.TOKEN_SYMBOL || 'N/A').padEnd(12); + const name = (holding.TOKEN_NAME || 'N/A').substring(0, 19).padEnd(20); + + // Format weight + const weightValue = holding.WEIGHT; + const weight = weightValue ? `${(weightValue * 100).toFixed(2)}%` : 'N/A'; + const weightPadded = weight.padEnd(10); + + // Format price + const priceValue = holding.PRICE || 0; + const price = this.formatPrice(priceValue).padEnd(15); + + // Format ROI with color + const roiValue = holding.CURRENT_ROI; + const roi = roiValue !== undefined ? + (roiValue >= 0 ? + `${colors.green}+${roiValue.toFixed(2)}%${colors.reset}` : + `${colors.red}${roiValue.toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + if (weightValue) totalWeight += weightValue; + + console.log(`${colors.yellow}${symbol} ${colors.green}${name} ${colors.white}${weightPadded} ${colors.cyan}${price} ${roi}${colors.reset}`); + }); + + console.log(`\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.green}โœ… Total holdings: ${holdings.length}${colors.reset}`); + console.log(`${colors.green}โœ… Total weight: ${(totalWeight * 100).toFixed(2)}%${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Use "index performance" to see historical returns${colors.reset}`); + console.log(); + } + + private formatIndicesPerformanceResponse(performance: any[], indexId: string): void { + console.log(`${colors.blue}${colors.bright}๐Ÿ“ˆ Index ${indexId} Performance Analysis [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.blue}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ“Š Historical ROI & Investment Returns${colors.reset}\n`); + + if (!performance || performance.length === 0) { + console.log(`${colors.yellow}No performance data available for index ${indexId}.${colors.reset}`); + return; + } + + console.log(`${colors.green}Found ${performance.length} performance data points:${colors.reset}\n`); + + // Calculate performance metrics + const firstPoint = performance[0]; + const lastPoint = performance[performance.length - 1]; + + // Use the actual field names from API response + const firstROI = firstPoint.INDEX_CUMULATIVE_ROI; + const lastROI = lastPoint.INDEX_CUMULATIVE_ROI; + + const totalReturn = lastROI && firstROI ? + ((lastROI - firstROI) / Math.abs(firstROI) * 100) : 0; + + // Use the actual field names for date + const firstDate = firstPoint.DATE; + const lastDate = lastPoint.DATE; + + // Summary metrics + console.log(`${colors.bright}Performance Summary:${colors.reset}`); + console.log(`${colors.white}โ€ข Period: ${firstDate || 'N/A'} to ${lastDate || 'N/A'}${colors.reset}`); + console.log(`${colors.white}โ€ข Total Return: ${totalReturn >= 0 ? + `${colors.green}+${totalReturn.toFixed(2)}%${colors.reset}` : + `${colors.red}${totalReturn.toFixed(2)}%${colors.reset}`}${colors.reset}`); + console.log(`${colors.white}โ€ข Data Points: ${performance.length}${colors.reset}\n`); + + // Table header for recent performance + console.log(`${colors.bright}Recent Performance (Last 10 points):${colors.reset}`); + console.log(`${colors.bright}${'Date'.padEnd(12)} ${'ROI'.padEnd(15)} ${'Change'.padEnd(12)} ${'Trend'.padEnd(8)}${colors.reset}`); + console.log(`${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(8)}${colors.reset}`); + + // Show last 10 data points + const recentData = performance.slice(-10); + recentData.forEach((point, index) => { + // Use the actual field names from API response + const dateValue = point.DATE; + const date = dateValue ? dateValue.toString().substring(0, 10).padEnd(12) : 'N/A'.padEnd(12); + + // Use the actual field name for ROI + const roiValue = point.INDEX_CUMULATIVE_ROI; + const roi = roiValue ? `${roiValue.toFixed(4)}%` : 'N/A'; + const roiPadded = roi.padEnd(15); + + let change = 'N/A'; + let trend = ''; + if (index > 0 && roiValue && recentData[index - 1].INDEX_CUMULATIVE_ROI) { + const prevROI = recentData[index - 1].INDEX_CUMULATIVE_ROI; + const changeValue = roiValue - prevROI; + change = changeValue >= 0 ? + `${colors.green}+${changeValue.toFixed(4)}%${colors.reset}` : + `${colors.red}${changeValue.toFixed(4)}%${colors.reset}`; + trend = changeValue >= 0 ? `${colors.green}โ†—${colors.reset}` : `${colors.red}โ†˜${colors.reset}`; + } + const changePadded = change.padEnd(20); + + console.log(`${colors.white}${date} ${colors.cyan}${roiPadded} ${changePadded} ${trend}${colors.reset}`); + }); + + console.log(`\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}`); + console.log(`${colors.green}โœ… Performance tracking period: ${performance.length} data points${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Use "index holdings" to see current portfolio composition${colors.reset}`); + console.log(); + } + + private async getHourlyTradingSignals(prompt?: string): Promise { + try { + // Extract token IDs from the user's prompt - default to Bitcoin if none specified + const tokenIds = await this.extractTokensFromPrompt(prompt || "Bitcoin"); // Use actual prompt or default to Bitcoin + const primaryTokenId = tokenIds[0] || "3375"; // Default to Bitcoin ID + + console.log(`${colors.dim} ๐ŸŽฏ Getting hourly trading signals for token ID: ${primaryTokenId}${colors.reset}`); + + const result = await this.retryWithBackoff(async () => { + return await this.plugin.getHourlyTradingSignals.executable( + { + token_id: primaryTokenId, + limit: "10", + page: "1" + }, + (msg: string) => console.log(`${colors.dim} ๐Ÿ“ ${msg}${colors.reset}`) + ); + }); + + if (result.status === 'done') { + // Parse the hourly trading signals data from the response + try { + const responseMatch = result.feedback.match(/Response: ({.*})/); + if (responseMatch) { + const responseData = JSON.parse(responseMatch[1]); + if (responseData.success && responseData.data && responseData.data.length > 0) { + this.formatHourlyTradingSignalsResponse(responseData.data); + } else { + this.formatResponse("No hourly trading signals data available at the moment.", 'data'); + } + } else { + this.formatResponse("Hourly trading signals retrieved successfully. Check the detailed data above for AI-generated buy/sell recommendations.", 'data'); + } + } catch (parseError) { + this.formatResponse("Hourly trading signals retrieved successfully. Check the detailed data above for AI-generated buy/sell recommendations.", 'data'); + } + } else { + this.formatResponse(result.feedback, 'error'); + } + } catch (error) { + this.formatResponse(`Hourly trading signals failed: ${error}`, 'error'); + } + } + + private formatHourlyTradingSignalsResponse(signals: any[]): void { + console.log(`${colors.magenta}${colors.bright}โฐ Hourly AI Trading Signals [${new Date().toLocaleTimeString()}]${colors.reset}`); + console.log(`${colors.magenta}${'โ•'.repeat(75)}${colors.reset}`); + console.log(`${colors.dim}๐Ÿค– Real-time AI Trading Signals Updated Hourly${colors.reset}\n`); + + signals.slice(0, 8).forEach((signal, index) => { + const tokenName = signal.TOKEN_NAME || 'Unknown Token'; + const tokenSymbol = signal.TOKEN_SYMBOL || 'N/A'; + const tradingSignal = parseInt(signal.SIGNAL) || 0; + const position = parseInt(signal.POSITION) || 0; + const closePrice = signal.CLOSE || 0; + const timestamp = signal.TIMESTAMP || new Date().toISOString(); + + // Determine signal type and styling based on SIGNAL field. + let signalType = 'HOLD'; + let signalColor = colors.yellow; + let signalEmoji = '๐ŸŸก'; + let actionEmoji = 'โธ๏ธ'; + + if (tradingSignal === 1) { + signalType = 'BUY'; + signalColor = colors.green; + signalEmoji = '๐ŸŸข'; + actionEmoji = '๐Ÿ“ˆ'; + } else if (tradingSignal === -1) { + signalType = 'SELL'; + signalColor = colors.red; + signalEmoji = '๐Ÿ”ด'; + actionEmoji = '๐Ÿ“‰'; + } + + // Position interpretation + let positionText = 'NEUTRAL'; + let positionColor = colors.yellow; + if (position === 1) { + positionText = 'LONG'; + positionColor = colors.green; + } else if (position === -1) { + positionText = 'SHORT'; + positionColor = colors.red; + } + + console.log(`${colors.bright}${index + 1}. ${tokenName} (${tokenSymbol})${colors.reset}`); + console.log(`${signalColor}${signalEmoji} ${actionEmoji} ${signalType} SIGNAL${colors.reset}`); + console.log(`๐Ÿ“ Position: ${positionColor}${positionText}${colors.reset}`); + console.log(`๐Ÿ’ฐ Close Price: $${this.formatPrice(closePrice)}`); + console.log(`โฐ Time: ${new Date(timestamp).toLocaleString()}\n`); + }); + + // Summary statistics + const buySignals = signals.filter(s => parseInt(s.SIGNAL) === 1).length; + const sellSignals = signals.filter(s => parseInt(s.SIGNAL) === -1).length; + const holdSignals = signals.filter(s => parseInt(s.SIGNAL) === 0).length; + + const longPositions = signals.filter(s => parseInt(s.POSITION) === 1).length; + const shortPositions = signals.filter(s => parseInt(s.POSITION) === -1).length; + const neutralPositions = signals.filter(s => parseInt(s.POSITION) === 0).length; + + console.log(`${colors.dim}${'โ”€'.repeat(75)}${colors.reset}`); + console.log(`${colors.bright}๐Ÿ“Š Signal Summary:${colors.reset}`); + console.log(`${colors.green}๐Ÿ“ˆ BUY: ${buySignals}${colors.reset} | ${colors.red}๐Ÿ“‰ SELL: ${sellSignals}${colors.reset} | ${colors.yellow}โธ๏ธ HOLD: ${holdSignals}${colors.reset}`); + console.log(`${colors.bright}๐Ÿ“ Position Summary:${colors.reset}`); + console.log(`${colors.green}๐Ÿ“ˆ LONG: ${longPositions}${colors.reset} | ${colors.red}๐Ÿ“‰ SHORT: ${shortPositions}${colors.reset} | ${colors.yellow}โš–๏ธ NEUTRAL: ${neutralPositions}${colors.reset}`); + console.log(`${colors.dim}๐Ÿ’ก Showing ${Math.min(8, signals.length)} signals. Total available: ${signals.length}${colors.reset}`); + console.log(`${colors.dim}โฐ Signals are updated hourly with real-time market data${colors.reset}`); + console.log(); + } + + private getSignalStrengthBar(strength: number): string { + const maxStrength = 1.0; + const normalizedStrength = Math.min(strength / maxStrength, 1); + const barLength = 20; + const filledLength = Math.round(normalizedStrength * barLength); + + let color = colors.yellow; + if (normalizedStrength >= 0.7) color = colors.green; + else if (normalizedStrength >= 0.4) color = colors.yellow; + else color = colors.red; + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(barLength - filledLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset}`; + } + + private getConfidenceBar(confidence: number): string { + const barLength = 15; + const filledLength = Math.round(confidence * barLength); + + let color = colors.red; + if (confidence >= 0.8) color = colors.green; + else if (confidence >= 0.6) color = colors.yellow; + else if (confidence >= 0.4) color = colors.yellow; + + const filled = 'โ–ˆ'.repeat(filledLength); + const empty = 'โ–‘'.repeat(barLength - filledLength); + + return `${color}${filled}${colors.dim}${empty}${colors.reset}`; + } +} + +function validateEnvironment(): boolean { + if (!process.env.TOKENMETRICS_API_KEY) { + console.log(`${colors.red}โŒ TOKENMETRICS_API_KEY is missing in .env file${colors.reset}`); + console.log(`${colors.yellow}Please add your TokenMetrics API key to the .env file:${colors.reset}`); + console.log(`TOKENMETRICS_API_KEY=tm-your-api-key-here`); + return false; + } + return true; +} + +// Start the chat interface +(async () => { + if (validateEnvironment()) { + const chat = new TokenMetricsChatInterface(); + await chat.start(); + } else { + process.exit(1); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.aiReports.ts b/plugins/tokenMetricsPlugin/src/examples/example.aiReports.ts new file mode 100644 index 00000000..9283a5e7 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.aiReports.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get AI-generated reports +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics AI Research Agent", + goal: "Retrieve comprehensive AI-generated reports providing deep dives, investment analyses, and code reviews for cryptocurrencies.", + description: + "You are an AI agent specialized in delivering comprehensive AI-generated cryptocurrency research reports using TokenMetrics. You provide deep analytical reports, investment analyses, technical assessments, and code reviews to support informed decision-making.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getAiReports], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.correlation.ts b/plugins/tokenMetricsPlugin/src/examples/example.correlation.ts new file mode 100644 index 00000000..7cfe85c5 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.correlation.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get token correlation analysis +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Correlation Analysis Agent", + goal: "Get correlation analysis showing the top 10 and bottom 10 correlations of tokens with the top 100 market cap cryptocurrencies.", + description: + "You are an AI agent specialized in cryptocurrency correlation analysis. You can analyze how different tokens move in relation to each other, providing insights for portfolio diversification, risk management, and understanding market relationships between cryptocurrencies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getCorrelation], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.cryptoInvestors.ts b/plugins/tokenMetricsPlugin/src/examples/example.cryptoInvestors.ts new file mode 100644 index 00000000..becc6f43 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.cryptoInvestors.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get crypto investor insights +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Crypto Investor Intelligence Agent", + goal: "Get insights into crypto investors and their activity scores to understand institutional and whale investor behavior.", + description: + "You are an AI agent specialized in analyzing cryptocurrency investor behavior using TokenMetrics investor intelligence. You provide insights into institutional investors, whale activities, and investor sentiment to help understand market dynamics from an investor perspective.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getCryptoInvestors], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.dailyOhlcv.ts b/plugins/tokenMetricsPlugin/src/examples/example.dailyOhlcv.ts new file mode 100644 index 00000000..e4511124 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.dailyOhlcv.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get daily OHLCV data +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Daily OHLCV Analyst", + goal: "Get daily OHLCV (Open, High, Low, Close, Volume) data for long-term technical analysis and trend identification.", + description: + "You are an AI agent specialized in providing daily cryptocurrency price and volume data using TokenMetrics. You deliver OHLCV data perfect for longer-term technical analysis, trend identification, and swing trading strategies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getDailyOhlcv], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.fullAgent.ts b/plugins/tokenMetricsPlugin/src/examples/example.fullAgent.ts new file mode 100644 index 00000000..621babb2 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.fullAgent.ts @@ -0,0 +1,35 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create a comprehensive crypto analysis agent with ALL TokenMetrics functions +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Comprehensive AI Crypto Analyst", + goal: "Provide complete cryptocurrency analysis using all TokenMetrics AI capabilities including trader grades, investor grades, trading signals, market metrics, price data, technical analysis, AI reports, and investor intelligence.", + description: + "You are the most advanced AI cryptocurrency analyst powered by the complete TokenMetrics suite. You can perform short-term trading analysis, long-term investment evaluation, market condition assessment, technical analysis, quantitative modeling, AI research, and investor intelligence. You help users make informed decisions across all aspects of cryptocurrency trading and investment using comprehensive data-driven analysis from TokenMetrics' AI platform.", + workers: [tokenMetricsPlugin.getWorker({})], // Include ALL 13 functions +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.fundamentalGrade.ts b/plugins/tokenMetricsPlugin/src/examples/example.fundamentalGrade.ts new file mode 100644 index 00000000..18759664 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.fundamentalGrade.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get fundamental grade insights for tokens +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Fundamental Grade Analyst", + goal: "Get the latest Fundamental Grade insights for tokens, including grade class, community score, exchange score, VC score, tokenomics score, and DeFi scanner score.", + description: + "You are an AI agent specialized in analyzing cryptocurrency fundamental strength using TokenMetrics Fundamental Grade insights. You evaluate key fundamental factors including community engagement, exchange listings, venture capital backing, tokenomics design, and DeFi ecosystem integration to assess the long-term viability and potential of crypto projects.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getFundamentalGrade], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.hourlyOhlcv.ts b/plugins/tokenMetricsPlugin/src/examples/example.hourlyOhlcv.ts new file mode 100644 index 00000000..f1f78eb6 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.hourlyOhlcv.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get hourly OHLCV data +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Hourly OHLCV Analyst", + goal: "Get hourly OHLCV (Open, High, Low, Close, Volume) data for detailed technical analysis and short-term trading patterns.", + description: + "You are an AI agent specialized in providing hourly cryptocurrency price and volume data using TokenMetrics. You deliver OHLCV data essential for technical analysis, chart patterns, and intraday trading strategies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getHourlyOhlcv], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.hourlyTradingSignals.ts b/plugins/tokenMetricsPlugin/src/examples/example.hourlyTradingSignals.ts new file mode 100644 index 00000000..bd3a1b06 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.hourlyTradingSignals.ts @@ -0,0 +1,39 @@ +import TokenMetricsPlugin from "../index"; + +async function main() { + const plugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + baseApiUrl: "https://api.tokenmetrics.com/v2", + }, + }); + + const worker = plugin.getWorker(); + + console.log("๐Ÿš€ Testing Hourly Trading Signals endpoint...\n"); + + try { + // Test hourly trading signals with default parameters + console.log("๐Ÿ“Š Getting hourly trading signals..."); + const result = await worker.functions + .find(f => f.name === "get_hourly_trading_signals") + ?.executable( + { + limit: "10", + page: "1" + }, + console.log + ); + + if (result?.status === 'done') { + console.log("\nโœ… Hourly trading signals retrieved successfully!"); + } else { + console.log("\nโŒ Failed to get hourly trading signals:", result?.feedback); + } + + } catch (error) { + console.error("โŒ Error:", error); + } +} + +main().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.indices.ts b/plugins/tokenMetricsPlugin/src/examples/example.indices.ts new file mode 100644 index 00000000..981a0ffe --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.indices.ts @@ -0,0 +1,96 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +(async () => { + console.log("\n๐Ÿš€ Starting TokenMetrics Crypto Indices Example...\n"); + + console.log("=".repeat(80)); + console.log("๐Ÿ“Š APPROACH 1: Direct Function Call (for testing/debugging)"); + console.log("=".repeat(80)); + + try { + console.log("๐Ÿ”ง Testing direct API call..."); + console.log("๐Ÿ“ˆ Fetching crypto indices with performance data...\n"); + + // Direct function call approach + const result = await tokenMetricsPlugin.getIndices.executable( + { limit: "20", page: "1" }, + (message: string) => { + console.log(`${message}`); + } + ); + + if (result.status === 'done') { + console.log("\nโœ… Direct function call completed successfully!"); + } else { + console.log("\nโŒ Direct function call failed:"); + console.log(result.feedback); + } + } catch (error) { + console.log("\nโŒ Error in direct function call:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐Ÿค– APPROACH 2: GameAgent Integration (for AI agents)"); + console.log("=".repeat(80)); + + // Create an agent to get crypto indices + const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Crypto Indices Agent", + goal: "Call the get_indices function with limit 20 and page 1 to retrieve crypto indices performance data from TokenMetrics.", + description: + "You are an AI agent that retrieves crypto indices data. Call the get_indices function with limit=20 and page=1 parameters only.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getIndices], + }), + ], + }); + + try { + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– Agent: ${message}`); + }); + + await agent.init(); + + console.log("๐Ÿค– GameAgent initialized successfully!"); + console.log("๐Ÿ“Š Agent fetching crypto indices performance data...\n"); + + // Run multiple steps to ensure the agent completes the task + for (let i = 0; i < 3; i++) { + console.log(`\n--- Agent Step ${i + 1} ---`); + const stepResult = await agent.step({ + verbose: true, + }); + + console.log(`Step ${i + 1} completed`); + } + + console.log("\nโœ… GameAgent integration example completed!"); + + } catch (error) { + console.log("\nโŒ Error in GameAgent integration:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐ŸŽฏ SUMMARY"); + console.log("=".repeat(80)); + console.log("โœ… Both approaches are available for developers:"); + console.log(" 1. Direct function calls - for simple integrations"); + console.log(" 2. GameAgent integration - for AI agent frameworks"); + console.log("๐Ÿ’ก Use index IDs from this data with getIndicesHoldings and getIndicesPerformance functions."); + console.log("=".repeat(80)); + + process.exit(0); +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.indicesHoldings.ts b/plugins/tokenMetricsPlugin/src/examples/example.indicesHoldings.ts new file mode 100644 index 00000000..8df75431 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.indicesHoldings.ts @@ -0,0 +1,96 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +(async () => { + console.log("\n๐Ÿš€ Starting TokenMetrics Index Holdings Example...\n"); + + console.log("=".repeat(80)); + console.log("๐Ÿ’ผ APPROACH 1: Direct Function Call (for testing/debugging)"); + console.log("=".repeat(80)); + + try { + console.log("๐Ÿ”ง Testing direct API call..."); + console.log("๐Ÿ“Š Fetching holdings for crypto index 1 (GLOBAL)...\n"); + + // Direct function call approach + const result = await tokenMetricsPlugin.getIndicesHoldings.executable( + { id: "1" }, + (message: string) => { + console.log(`${message}`); + } + ); + + if (result.status === 'done') { + console.log("\nโœ… Direct function call completed successfully!"); + } else { + console.log("\nโŒ Direct function call failed:"); + console.log(result.feedback); + } + } catch (error) { + console.log("\nโŒ Error in direct function call:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐Ÿค– APPROACH 2: GameAgent Integration (for AI agents)"); + console.log("=".repeat(80)); + + // Create an agent to get index holdings + const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Index Holdings Agent", + goal: "Call the get_indices_holdings function with id=1 to retrieve detailed holdings data for crypto index 1 from TokenMetrics.", + description: + "You are an AI agent that retrieves index holdings data. Call the get_indices_holdings function with id=1 parameter only.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getIndicesHoldings], + }), + ], + }); + + try { + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– Agent: ${message}`); + }); + + await agent.init(); + + console.log("๐Ÿค– GameAgent initialized successfully!"); + console.log("๐Ÿ“Š Agent fetching index holdings data...\n"); + + // Run multiple steps to ensure the agent completes the task + for (let i = 0; i < 3; i++) { + console.log(`\n--- Agent Step ${i + 1} ---`); + const stepResult = await agent.step({ + verbose: true, + }); + + console.log(`Step ${i + 1} completed`); + } + + console.log("\nโœ… GameAgent integration example completed!"); + + } catch (error) { + console.log("\nโŒ Error in GameAgent integration:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐ŸŽฏ SUMMARY"); + console.log("=".repeat(80)); + console.log("โœ… Both approaches are available for developers:"); + console.log(" 1. Direct function calls - for simple integrations"); + console.log(" 2. GameAgent integration - for AI agent frameworks"); + console.log("๐Ÿ’ก This shows detailed portfolio composition with weights and current prices."); + console.log("=".repeat(80)); + + process.exit(0); +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.indicesPerformance.ts b/plugins/tokenMetricsPlugin/src/examples/example.indicesPerformance.ts new file mode 100644 index 00000000..5c67d678 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.indicesPerformance.ts @@ -0,0 +1,96 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +(async () => { + console.log("\n๐Ÿš€ Starting TokenMetrics Index Performance Example...\n"); + + console.log("=".repeat(80)); + console.log("๐Ÿ“ˆ APPROACH 1: Direct Function Call (for testing/debugging)"); + console.log("=".repeat(80)); + + try { + console.log("๐Ÿ”ง Testing direct API call..."); + console.log("๐Ÿ“Š Fetching performance data for crypto index 1 (GLOBAL)...\n"); + + // Direct function call approach + const result = await tokenMetricsPlugin.getIndicesPerformance.executable( + { id: "1", limit: "30", page: "1" }, + (message: string) => { + console.log(`${message}`); + } + ); + + if (result.status === 'done') { + console.log("\nโœ… Direct function call completed successfully!"); + } else { + console.log("\nโŒ Direct function call failed:"); + console.log(result.feedback); + } + } catch (error) { + console.log("\nโŒ Error in direct function call:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐Ÿค– APPROACH 2: GameAgent Integration (for AI agents)"); + console.log("=".repeat(80)); + + // Create an agent to get index performance + const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Index Performance Agent", + goal: "Call the get_indices_performance function with id=1, limit=30, and page=1 to retrieve historical performance data for crypto index 1 from TokenMetrics.", + description: + "You are an AI agent that retrieves index performance data. Call the get_indices_performance function with id=1, limit=30, and page=1 parameters only.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getIndicesPerformance], + }), + ], + }); + + try { + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– Agent: ${message}`); + }); + + await agent.init(); + + console.log("๐Ÿค– GameAgent initialized successfully!"); + console.log("๐Ÿ“Š Agent fetching index performance data...\n"); + + // Run multiple steps to ensure the agent completes the task + for (let i = 0; i < 3; i++) { + console.log(`\n--- Agent Step ${i + 1} ---`); + const stepResult = await agent.step({ + verbose: true, + }); + + console.log(`Step ${i + 1} completed`); + } + + console.log("\nโœ… GameAgent integration example completed!"); + + } catch (error) { + console.log("\nโŒ Error in GameAgent integration:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐ŸŽฏ SUMMARY"); + console.log("=".repeat(80)); + console.log("โœ… Both approaches are available for developers:"); + console.log(" 1. Direct function calls - for simple integrations"); + console.log(" 2. GameAgent integration - for AI agent frameworks"); + console.log("๐Ÿ’ก This shows historical ROI tracking for trend analysis and investment evaluation."); + console.log("=".repeat(80)); + + process.exit(0); +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.marketMetrics.ts b/plugins/tokenMetricsPlugin/src/examples/example.marketMetrics.ts new file mode 100644 index 00000000..b7cfed35 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.marketMetrics.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get market analytics and indicators +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Market Analytics Agent", + goal: "Get comprehensive market analytics including bullish/bearish indicators to understand overall crypto market conditions.", + description: + "You are an AI agent specialized in analyzing overall cryptocurrency market conditions using TokenMetrics market analytics. You provide insights into market sentiment, trend analysis, and macro indicators to help understand the broader crypto market environment.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getMarketMetrics], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.moonshotTokens.ts b/plugins/tokenMetricsPlugin/src/examples/example.moonshotTokens.ts new file mode 100644 index 00000000..9d8ab71e --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.moonshotTokens.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get moonshot tokens for high-potential trading opportunities +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Moonshot Analyst", + goal: "Get AI-curated token picks (Moonshots) with high breakout potential based on grades, sentiment, volume, and on-chain data to help users trade smarter and faster.", + description: + "You are an AI agent specialized in identifying high-potential cryptocurrency moonshots using TokenMetrics AI analysis. You help users discover tokens with strong breakout potential based on comprehensive analysis of grades, sentiment, volume, and on-chain data.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getMoonshotTokens], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.priceData.ts b/plugins/tokenMetricsPlugin/src/examples/example.priceData.ts new file mode 100644 index 00000000..0ae165a9 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.priceData.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get real-time price data +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Price Data Agent", + goal: "Get real-time cryptocurrency prices based on token IDs for current market valuation.", + description: + "You are an AI agent specialized in retrieving real-time cryptocurrency price data using TokenMetrics. You provide current market prices, market capitalizations, and pricing information for specified cryptocurrencies using their token IDs.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getPriceData], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.quantmetrics.ts b/plugins/tokenMetricsPlugin/src/examples/example.quantmetrics.ts new file mode 100644 index 00000000..a1988dd5 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.quantmetrics.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get quantitative metrics +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Quantitative Analyst", + goal: "Get advanced quantitative metrics and statistical analysis for cryptocurrency tokens to support data-driven investment decisions.", + description: + "You are an AI agent specialized in quantitative cryptocurrency analysis using TokenMetrics advanced mathematical models. You provide statistical metrics, quantitative indicators, and mathematical analysis to support sophisticated trading and investment strategies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getQuantmetrics], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.resistanceSupport.ts b/plugins/tokenMetricsPlugin/src/examples/example.resistanceSupport.ts new file mode 100644 index 00000000..d108a16a --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.resistanceSupport.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get resistance and support levels +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Technical Levels Agent", + goal: "Get historical resistance and support levels for cryptocurrencies to identify key technical price levels for trading decisions.", + description: + "You are an AI agent specialized in technical analysis using TokenMetrics resistance and support level data. You identify critical price levels where cryptocurrencies historically find support or face resistance, essential for technical trading strategies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getResistanceSupport], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.scenarioAnalysis.ts b/plugins/tokenMetricsPlugin/src/examples/example.scenarioAnalysis.ts new file mode 100644 index 00000000..58a9c973 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.scenarioAnalysis.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get scenario-based price predictions +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Scenario Analysis Agent", + goal: "Get price predictions based on different cryptocurrency market scenarios including bullish, bearish, and neutral conditions.", + description: + "You are an AI agent specialized in cryptocurrency scenario analysis and price forecasting. You can provide price predictions for different market scenarios, helping users understand potential price movements under various market conditions and make informed investment decisions.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getScenarioAnalysis], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.sentiments.ts b/plugins/tokenMetricsPlugin/src/examples/example.sentiments.ts new file mode 100644 index 00000000..1070dd72 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.sentiments.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get market sentiment analysis +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Sentiment Analysis Agent", + goal: "Get hourly sentiment scores from Twitter, Reddit, and news sources to understand market sentiment and social media trends.", + description: + "You are an AI agent specialized in analyzing cryptocurrency market sentiment. You can retrieve hourly sentiment scores from social media platforms (Twitter, Reddit) and news sources, providing insights into market psychology and social trends that influence crypto prices.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getSentiments], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.technologyGrade.ts b/plugins/tokenMetricsPlugin/src/examples/example.technologyGrade.ts new file mode 100644 index 00000000..5bb976e3 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.technologyGrade.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get technology grade insights for tokens +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Technology Grade Analyst", + goal: "Get Technology Grade insights for tokens, including activity score, security score, repository score, collaboration score, and DeFi scanner score.", + description: + "You are an AI agent specialized in analyzing cryptocurrency technology quality using TokenMetrics Technology Grade insights. You evaluate technical aspects including development activity, security measures, code quality, collaboration patterns, and DeFi integration to assess the technological strength of crypto projects.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTechnologyGrade], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.tmGrade.ts b/plugins/tokenMetricsPlugin/src/examples/example.tmGrade.ts new file mode 100644 index 00000000..17f502dc --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.tmGrade.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get TM Grade insights for tokens +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics TM Grade Analyst", + goal: "Get the latest TM Grade for tokens, including trader grade change, quant grade, signals, momentum, and 24-hour percentage changes for both TM Grade and Trader Grade.", + description: + "You are an AI agent specialized in analyzing TokenMetrics (TM) Grade insights for cryptocurrencies. You provide comprehensive analysis of TM grades, trader grade changes, quantitative metrics, trading signals, and momentum indicators to help users make informed trading and investment decisions.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTmGrade], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.tokenmetricsAi.ts b/plugins/tokenMetricsPlugin/src/examples/example.tokenmetricsAi.ts new file mode 100644 index 00000000..6bd1742b --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.tokenmetricsAi.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to chat with TokenMetrics AI +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics AI Chat Agent", + goal: "Chat with TokenMetrics AI to get insights about cryptocurrency markets, trading strategies, and investment advice.", + description: + "You are an AI agent that can interact with TokenMetrics AI to get expert cryptocurrency insights. You can ask questions about market trends, trading strategies, investment opportunities, and get AI-powered analysis of the crypto market.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTokenMetricsAi], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.tokens.ts b/plugins/tokenMetricsPlugin/src/examples/example.tokens.ts new file mode 100644 index 00000000..4fe06650 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.tokens.ts @@ -0,0 +1,97 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +(async () => { + console.log("\n๐Ÿš€ Starting TokenMetrics Token Database Example...\n"); + + console.log("=".repeat(80)); + console.log("๐Ÿ“‹ APPROACH 1: Direct Function Call (for testing/debugging)"); + console.log("=".repeat(80)); + + try { + console.log("๐Ÿ”ง Testing direct API call..."); + console.log("๐Ÿ“Š Fetching TokenMetrics supported cryptocurrencies...\n"); + + // Direct function call approach + const result = await tokenMetricsPlugin.getTokens.executable( + { limit: "50", page: "1" }, + (message: string) => { + console.log(`${message}`); + } + ); + + if (result.status === 'done') { + console.log("\nโœ… Direct function call completed successfully!"); + } else { + console.log("\nโŒ Direct function call failed:"); + console.log(result.feedback); + } + } catch (error) { + console.log("\nโŒ Error in direct function call:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐Ÿค– APPROACH 2: GameAgent Integration (for AI agents)"); + console.log("=".repeat(80)); + + // Create an agent to get the tokens database + const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Token Database Agent", + goal: "Call the get_tokens function with limit 50 and page 1 to retrieve cryptocurrency data from TokenMetrics database.", + description: + "You are an AI agent that retrieves TokenMetrics cryptocurrency data. Call the get_tokens function with limit=50 and page=1 parameters only.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTokens], + }), + ], + }); + + try { + agent.setLogger((agent, message) => { + console.log(`๐Ÿค– Agent: ${message}`); + }); + + await agent.init(); + + console.log("๐Ÿค– GameAgent initialized successfully!"); + console.log("๐Ÿ“Š Agent fetching TokenMetrics supported cryptocurrencies...\n"); + + // Run multiple steps to ensure the agent completes the task + for (let i = 0; i < 3; i++) { + console.log(`\n--- Agent Step ${i + 1} ---`); + const stepResult = await agent.step({ + verbose: true, + }); + + // Just run the steps without checking completion + console.log(`Step ${i + 1} completed`); + } + + console.log("\nโœ… GameAgent integration example completed!"); + + } catch (error) { + console.log("\nโŒ Error in GameAgent integration:"); + console.log(error); + } + + console.log("\n" + "=".repeat(80)); + console.log("๐ŸŽฏ SUMMARY"); + console.log("=".repeat(80)); + console.log("โœ… Both approaches are available for developers:"); + console.log(" 1. Direct function calls - for simple integrations"); + console.log(" 2. GameAgent integration - for AI agent frameworks"); + console.log("๐Ÿ’ก Use these token IDs with other TokenMetrics functions like getPrice, getTradingSignals, etc."); + console.log("=".repeat(80)); + + process.exit(0); +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.topMarketCap.ts b/plugins/tokenMetricsPlugin/src/examples/example.topMarketCap.ts new file mode 100644 index 00000000..015880ec --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.topMarketCap.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get top market cap cryptocurrencies +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Top Market Cap Agent", + goal: "Get the list of top cryptocurrencies by market capitalization to identify the most valuable and established digital assets.", + description: + "You are an AI agent specialized in identifying leading cryptocurrencies by market capitalization using TokenMetrics data. You provide information about the most valuable and established digital assets in the crypto market.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTopMarketCapTokens], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/examples/example.tradingSignals.ts b/plugins/tokenMetricsPlugin/src/examples/example.tradingSignals.ts new file mode 100644 index 00000000..26919b7e --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/examples/example.tradingSignals.ts @@ -0,0 +1,39 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../index"; + +const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, +}); + +// Create an agent to get AI-generated trading signals +const agent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Trading Signals Agent", + goal: "Get AI-generated trading signals for long and short positions to identify optimal buy and sell opportunities.", + description: + "You are an AI agent specialized in providing AI-generated trading signals using TokenMetrics algorithms. You analyze market conditions to provide bullish (1), bearish (-1), or neutral (0) signals with strength indicators for cryptocurrencies.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [tokenMetricsPlugin.getTradingSignals], + }), + ], +}); + +(async () => { + agent.setLogger((agent, message) => { + console.log(`-----[${agent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); diff --git a/plugins/tokenMetricsPlugin/src/index.ts b/plugins/tokenMetricsPlugin/src/index.ts new file mode 100644 index 00000000..b3238197 --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/index.ts @@ -0,0 +1,1172 @@ +import { + GameWorker, + GameFunction, + ExecutableGameFunctionResponse, + ExecutableGameFunctionStatus, + } from "@virtuals-protocol/game"; + import { exceptionHandler, tokenMetricsApiCall, tokenMetricsApiPostCall } from "./utils"; + + // Default configuration values + export const DEFAULT_BASE_API_URL = "https://api.tokenmetrics.com/v2"; + + // Interface for plugin configuration options + interface ITokenMetricsPluginOptions { + id?: string; + name?: string; + description?: string; + apiClientConfig: { + apiKey: string; + baseApiUrl?: string; + }; + } + + class TokenMetricsPlugin { + private id: string; + private name: string; + private description: string; + private apiKey: string; + private baseApiUrl: string; + + constructor(options: ITokenMetricsPluginOptions) { + this.id = options.id || "tokenmetrics_worker"; + this.name = options.name || "TokenMetrics AI Crypto Data Worker"; + this.description = + options.description || + "Worker that provides AI-powered cryptocurrency analysis using TokenMetrics data including trader grades, investor grades, trading signals, market metrics, and comprehensive AI reports"; + + this.apiKey = options.apiClientConfig.apiKey; + this.baseApiUrl = + options.apiClientConfig.baseApiUrl || DEFAULT_BASE_API_URL; + } + + public getWorker(data?: { + functions?: GameFunction[]; + getEnvironment?: () => Promise>; + }): GameWorker { + return new GameWorker({ + id: this.id, + name: this.name, + description: this.description, + functions: data?.functions || [ + this.getTokens, + this.getTopMarketCapTokens, + this.getPriceData, + this.getMoonshotTokens, + this.getTechnologyGrade, + this.getTmGrade, + this.getFundamentalGrade, + this.getTradingSignals, + this.getHourlyTradingSignals, + this.getMarketMetrics, + this.getQuantmetrics, + this.getHourlyOhlcv, + this.getDailyOhlcv, + this.getAiReports, + this.getCryptoInvestors, + this.getResistanceSupport, + this.getTokenMetricsAi, + this.getSentiments, + this.getScenarioAnalysis, + this.getCorrelation, + this.getIndices, + this.getIndicesHoldings, + this.getIndicesPerformance, + ], + getEnvironment: data?.getEnvironment, + }); + } + + get getTokens() { + return new GameFunction({ + name: "get_tokens", + description: + "Get the complete list of cryptocurrencies and their TOKEN_ID supported by TokenMetrics. To get ALL available tokens, use only 'limit' and 'page' parameters. Use search parameters (token_name, symbol, category) only when specifically searching for particular cryptocurrencies.", + args: [ + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + { + name: "token_name", + description: "Optional: Comma separated crypto asset names (e.g., Bitcoin, Ethereum). Only use when searching for specific tokens.", + type: "string", + }, + { + name: "symbol", + description: "Optional: Comma separated token symbols (e.g., BTC, ETH). Only use when searching for specific tokens.", + type: "string", + }, + { + name: "category", + description: "Optional: Comma separated category names. Only use when filtering by specific categories.", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying tokens list with limit: ${args.limit}, page: ${args.page}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/tokens", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getTopMarketCapTokens() { + return new GameFunction({ + name: "get_top_market_cap_tokens", + description: + "Get the list of coins for top market cap. Useful for finding the most valuable cryptocurrencies by market capitalization.", + args: [ + { + name: "top_k", + description: "Number of top cryptocurrencies to retrieve based on market cap (default: 100)", + type: "number", + default: 100, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying top ${args.top_k} tokens by market cap`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/top-market-cap-tokens", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getPriceData() { + return new GameFunction({ + name: "get_price_data", + description: + "Get token prices based on the provided token IDs. Returns current market prices for specified cryptocurrencies.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs (e.g., 3375,3306 for BTC,ETH)", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + if (!args.token_id) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + "token_id is required for querying price data" + ); + } + + logger(`Querying price data for token IDs: ${args.token_id}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/price", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getMoonshotTokens() { + return new GameFunction({ + name: "get_moonshot_tokens", + description: + "Get AI-curated token picks (Moonshots) with high breakout potential based on grades, sentiment, volume, and on-chain data to help users trade smarter and faster.", + args: [ + { + name: "type", + description: "Accepts 'active' or 'past' to fetch respective moonshots. Defaults to 'active' if not provided.", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying moonshot tokens with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/moonshot-tokens", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getTechnologyGrade() { + return new GameFunction({ + name: "get_technology_grade", + description: + "Get Technology Grade insights for a token, including activity score, security score, repository score, collaboration score, and DeFi scanner score.", + args: [ + { + name: "token_id", + description: "Token ID (e.g., 3375)", + type: "string", + }, + { + name: "token_name", + description: "Crypto Asset Names (e.g., Bitcoin, Ethereum)", + type: "string", + }, + { + name: "symbol", + description: "Token symbols (e.g., BTC, ETH)", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + if (!args.token_id && !args.token_name && !args.symbol) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + "At least one of token_id, token_name, or symbol is required for technology grade query" + ); + } + + logger(`Querying technology grade with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/technology-grade", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getTmGrade() { + return new GameFunction({ + name: "get_tm_grade", + description: + "Get the latest TM Grade for a token, including trader grade change, quant grade, signals, momentum, and 24-hour percentage changes for both TM Grade and Trader Grade.", + args: [ + { + name: "token_id", + description: "Token ID (e.g., 3375)", + type: "string", + }, + { + name: "token_name", + description: "Crypto Asset Names (e.g., Bitcoin, Ethereum)", + type: "string", + }, + { + name: "symbol", + description: "Token symbols (e.g., BTC, ETH)", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + if (!args.token_id && !args.token_name && !args.symbol) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + "At least one of token_id, token_name, or symbol is required for TM grade query" + ); + } + + logger(`Querying TM grade with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/tm-grade", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getFundamentalGrade() { + return new GameFunction({ + name: "get_fundamental_grade", + description: + "Get the latest Fundamental Grade insights for a token, including grade class, community score, exchange score, VC score, tokenomics score, and DeFi scanner score.", + args: [ + { + name: "token_id", + description: "Token ID (e.g., 3375)", + type: "string", + }, + { + name: "token_name", + description: "Crypto Asset Names (e.g., Bitcoin, Ethereum)", + type: "string", + }, + { + name: "symbol", + description: "Token symbols (e.g., BTC, ETH)", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + if (!args.token_id && !args.token_name && !args.symbol) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + "At least one of token_id, token_name, or symbol is required for fundamental grade query" + ); + } + + logger(`Querying fundamental grade with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/fundamental-grade", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getTradingSignals() { + return new GameFunction({ + name: "get_trading_signals", + description: + "Get the AI generated trading signals for long and short positions for all tokens. Provides buy/sell recommendations with signal strength.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "startDate", + description: "Start date in YYYY-MM-DD format", + type: "string", + }, + { + name: "endDate", + description: "End date in YYYY-MM-DD format", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "signal", + description: "Current signal value (1=bullish, -1=bearish, 0=no signal)", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying trading signals with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/trading-signals", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getHourlyTradingSignals() { + return new GameFunction({ + name: "get_hourly_trading_signals", + description: + "Get the AI generated trading signals for long and short positions for all tokens. Provides buy/sell recommendations with signal strength.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "startDate", + description: "Start date in YYYY-MM-DD format", + type: "string", + }, + { + name: "endDate", + description: "End date in YYYY-MM-DD format", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "signal", + description: "Current signal value (1=bullish, -1=bearish, 0=no signal)", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying hourly trading signals with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/hourly-trading-signals", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getMarketMetrics() { + return new GameFunction({ + name: "get_market_metrics", + description: + "Get the Market Analytics from Token Metrics including Bullish/Bearish Market indicator. Provides overall crypto market sentiment and conditions.", + args: [ + { + name: "startDate", + description: "Start date in YYYY-MM-DD format", + type: "string", + }, + { + name: "endDate", + description: "End date in YYYY-MM-DD format", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying market metrics with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/market-metrics", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getQuantmetrics() { + return new GameFunction({ + name: "get_quantmetrics", + description: + "Get the latest quantitative metrics for tokens. Provides advanced mathematical analysis and statistical data for cryptocurrencies.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying quantmetrics with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/quantmetrics", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getHourlyOhlcv() { + return new GameFunction({ + name: "get_hourly_ohlcv", + description: + "Get hourly OHLCV (Open, High, Low, Close, Volume) data for tokens. Essential for technical analysis and charting.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "token_name", + description: "Comma separated crypto asset names", + type: "string", + }, + { + name: "startDate", + description: "Start date in YYYY-MM-DD format (max 30 days from current)", + type: "string", + }, + { + name: "endDate", + description: "End date in YYYY-MM-DD format", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying hourly OHLCV data with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/hourly-ohlcv", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getDailyOhlcv() { + return new GameFunction({ + name: "get_daily_ohlcv", + description: + "Get daily OHLCV (Open, High, Low, Close, Volume) data for tokens. Perfect for longer-term technical analysis and trend identification.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "token_name", + description: "Comma separated crypto asset names", + type: "string", + }, + { + name: "startDate", + description: "Start date in YYYY-MM-DD format (max 30 days from current)", + type: "string", + }, + { + name: "endDate", + description: "End date in YYYY-MM-DD format", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying daily OHLCV data with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/daily-ohlcv", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getAiReports() { + return new GameFunction({ + name: "get_ai_reports", + description: + "Retrieve AI-generated reports providing comprehensive analyses of cryptocurrency tokens including deep dives, investment analyses, and code reviews.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying AI reports with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/ai-reports", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getCryptoInvestors() { + return new GameFunction({ + name: "get_crypto_investors", + description: + "Get the latest list of crypto investors and their scores. Provides insights into institutional and whale investor behavior.", + args: [ + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying crypto investors with limit: ${args.limit}, page: ${args.page}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/crypto-investors", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getResistanceSupport() { + return new GameFunction({ + name: "get_resistance_support", + description: + "Get the historical levels of resistance and support for each token. Critical for technical analysis and identifying key price levels.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs", + type: "string", + }, + { + name: "symbol", + description: "Comma separated token symbols", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying resistance and support levels with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/resistance-support", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getTokenMetricsAi() { + return new GameFunction({ + name: "get_tokenmetrics_ai", + description: + "Chat with TokenMetrics AI to get insights about cryptocurrency markets, trading strategies, and investment advice. Ask any crypto-related question.", + args: [ + { + name: "user_message", + description: "Your question or message to TokenMetrics AI (e.g., 'What is the next 100x coin?')", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + if (!args.user_message) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + "user_message is required for TokenMetrics AI chat" + ); + } + + logger(`Asking TokenMetrics AI: ${args.user_message}`); + + const requestBody = { + messages: [ + { + user: args.user_message + } + ] + }; + + return await tokenMetricsApiPostCall( + this.apiKey, + this.baseApiUrl, + "/tmai", + requestBody, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getSentiments() { + return new GameFunction({ + name: "get_sentiments", + description: + "Get the hourly sentiment score for Twitter, Reddit, and all the News, including quick summary of what happened. Useful for understanding market sentiment and social media trends.", + args: [ + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying sentiment data with limit: ${args.limit}, page: ${args.page}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/sentiments", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getScenarioAnalysis() { + return new GameFunction({ + name: "get_scenario_analysis", + description: + "Get the price prediction based on different Crypto Market scenarios. Provides forecasts under various market conditions (bullish, bearish, neutral).", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs. Click here to access the list of token IDs. Example: 3375,3306", + type: "string", + }, + { + name: "symbol", + description: "Comma separated Token Symbols. Click here to access the list of token symbols. Example: BTC,ETH", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying scenario analysis with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/scenario-analysis", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getCorrelation() { + return new GameFunction({ + name: "get_correlation", + description: + "Get the Top 10 and Bottom 10 correlation of tokens with the top 100 market cap tokens. Useful for portfolio diversification and understanding token relationships.", + args: [ + { + name: "token_id", + description: "Comma separated Token IDs. Click here to access the list of token IDs. Example: 3375,3306", + type: "string", + }, + { + name: "symbol", + description: "Comma separated Token Symbols. Click here to access the list of token symbols. Example: BTC,ETH", + type: "string", + }, + { + name: "category", + description: "Comma separated category name. Click here to access the list of categories. Example: layer-1,nft", + type: "string", + }, + { + name: "exchange", + description: "Comma separated exchange name. Click here to access the list of exchanges. Example: gate,binance", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying correlation data with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/correlation", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getIndices() { + return new GameFunction({ + name: "get_indices", + description: + "Get active and passive crypto indices with performance and market data. Provides comprehensive index information for portfolio tracking and analysis.", + args: [ + { + name: "indicesType", + description: "Filter to return indices by type: 'active' for actively managed, 'passive' for passively managed", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying indices with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/indices", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getIndicesHoldings() { + return new GameFunction({ + name: "get_indices_holdings", + description: + "Get the current holdings of a given index, along with their respective weight in %. Shows detailed portfolio composition of crypto indices.", + args: [ + { + name: "id", + description: "ID of the index. Example: 1", + type: "string", + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying indices holdings with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/indices-holdings", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + + get getIndicesPerformance() { + return new GameFunction({ + name: "get_indices_performance", + description: + "Get historical performance data for a given index, including cumulative return on investment (ROI) over time. Essential for analyzing index trends and evaluating investment performance.", + args: [ + { + name: "id", + description: "ID of the index. Example: 1", + type: "string", + }, + { + name: "startDate", + description: "Start Date accepts date as a string - YYYY-MM-DD format. Example: 2025-01-01", + type: "string", + }, + { + name: "endDate", + description: "End Date accepts date as a string - YYYY-MM-DD format. Example: 2025-06-01", + type: "string", + }, + { + name: "limit", + description: "Limit the number of items in response (default: 50)", + type: "number", + default: 50, + }, + { + name: "page", + description: "Page number for pagination (default: 1)", + type: "number", + default: 1, + }, + ] as const, + executable: async (args: any, logger: any) => { + try { + logger(`Querying indices performance with parameters: ${JSON.stringify(args)}`); + + return await tokenMetricsApiCall( + this.apiKey, + this.baseApiUrl, + "/indices-performance", + args, + logger + ); + } catch (e: any) { + return exceptionHandler(e, logger); + } + }, + }); + } + } + + export default TokenMetricsPlugin; diff --git a/plugins/tokenMetricsPlugin/src/utils.ts b/plugins/tokenMetricsPlugin/src/utils.ts new file mode 100644 index 00000000..3bb3a4ec --- /dev/null +++ b/plugins/tokenMetricsPlugin/src/utils.ts @@ -0,0 +1,473 @@ +import { + ExecutableGameFunctionResponse, + ExecutableGameFunctionStatus, + } from "@virtuals-protocol/game"; + + // Color coding for beautiful console output + const colors = { + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m' + }; + + // Format large numbers with commas + function formatNumber(num: any): string { + if (num === null || num === undefined) return 'N/A'; + const number = parseFloat(num); + if (isNaN(number)) return 'N/A'; + return number.toLocaleString(); + } + + // Format price with appropriate decimal places + function formatPrice(price: any): string { + if (price === null || price === undefined) return 'N/A'; + const num = parseFloat(price); + if (isNaN(num)) return 'N/A'; + + if (num >= 1) { + return `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; + } else if (num >= 0.01) { + return `$${num.toFixed(4)}`; + } else { + return `$${num.toFixed(8)}`; + } + } + + // Format tokens response for better readability + function formatTokensResponse(tokens: any[]): string { + if (!tokens || tokens.length === 0) { + return `${colors.yellow}No tokens found.${colors.reset}`; + } + + let output = `${colors.cyan}${colors.bright}๐Ÿช™ TokenMetrics Supported Cryptocurrencies${colors.reset}\n`; + output += `${colors.cyan}${'โ•'.repeat(60)}${colors.reset}\n\n`; + + output += `${colors.green}Found ${tokens.length} cryptocurrencies:${colors.reset}\n\n`; + + // Table header + output += `${colors.bright}${'ID'.padEnd(8)} ${'Symbol'.padEnd(12)} ${'Name'.padEnd(25)} ${'Category'.padEnd(15)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(8)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(25)} ${'โ”€'.repeat(15)}${colors.reset}\n`; + + // Show first 20 tokens with better formatting + const displayTokens = tokens.slice(0, 20); + displayTokens.forEach(token => { + const id = (token.TOKEN_ID || token.id || 'N/A').toString().padEnd(8); + const symbol = (token.TOKEN_SYMBOL || token.symbol || 'N/A').padEnd(12); + const name = (token.TOKEN_NAME || token.name || 'N/A').substring(0, 24).padEnd(25); + const category = (token.CATEGORY || token.category || 'N/A').substring(0, 14).padEnd(15); + + output += `${colors.white}${id} ${colors.yellow}${symbol} ${colors.green}${name} ${colors.blue}${category}${colors.reset}\n`; + }); + + if (tokens.length > 20) { + output += `\n${colors.dim}... and ${tokens.length - 20} more tokens${colors.reset}\n`; + } + + output += `\n${colors.cyan}${'โ•'.repeat(60)}${colors.reset}\n`; + output += `${colors.green}โœ… Total tokens available: ${formatNumber(tokens.length)}${colors.reset}\n`; + + return output; + } + + // Format price data response + function formatPriceResponse(priceData: any[]): string { + if (!priceData || priceData.length === 0) { + return `${colors.yellow}No price data found.${colors.reset}`; + } + + let output = `${colors.cyan}${colors.bright}๐Ÿ’ฐ Current Cryptocurrency Prices${colors.reset}\n`; + output += `${colors.cyan}${'โ•'.repeat(60)}${colors.reset}\n\n`; + + // Table header + output += `${colors.bright}${'Symbol'.padEnd(12)} ${'Name'.padEnd(20)} ${'Price'.padEnd(15)} ${'Change 24h'.padEnd(12)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(20)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)}${colors.reset}\n`; + + priceData.forEach(token => { + const symbol = (token.TOKEN_SYMBOL || token.symbol || 'N/A').padEnd(12); + const name = (token.TOKEN_NAME || token.name || 'N/A').substring(0, 19).padEnd(20); + const price = formatPrice(token.CURRENT_PRICE || token.PRICE || token.price).padEnd(15); + const change = token.change_24h ? + (token.change_24h >= 0 ? + `${colors.green}+${token.change_24h.toFixed(2)}%${colors.reset}` : + `${colors.red}${token.change_24h.toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + output += `${colors.yellow}${symbol} ${colors.green}${name} ${colors.white}${price} ${change}\n`; + }); + + output += `\n${colors.cyan}${'โ•'.repeat(60)}${colors.reset}\n`; + + return output; + } + + // Format indices response + function formatIndicesResponse(indices: any[]): string { + if (!indices || indices.length === 0) { + return `${colors.yellow}No indices found.${colors.reset}`; + } + + let output = `${colors.cyan}${colors.bright}๐Ÿ“Š TokenMetrics Crypto Indices${colors.reset}\n`; + output += `${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n\n`; + + output += `${colors.green}Found ${indices.length} crypto indices:${colors.reset}\n\n`; + + // Table header + output += `${colors.bright}${'ID'.padEnd(6)} ${'Name'.padEnd(25)} ${'Ticker'.padEnd(15)} ${'24H Change'.padEnd(12)} ${'1M Change'.padEnd(12)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(6)} ${'โ”€'.repeat(25)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(12)}${colors.reset}\n`; + + indices.forEach(index => { + // Use the actual field names from API response + const id = (index.ID || 'N/A').toString().padEnd(6); + const name = (index.NAME || 'N/A').substring(0, 24).padEnd(25); + const ticker = (index.TICKER || 'N/A').substring(0, 14).padEnd(15); + + // Format 24H change with color + const change24h = index['24H'] !== undefined ? + (index['24H'] >= 0 ? + `${colors.green}+${index['24H'].toFixed(2)}%${colors.reset}` : + `${colors.red}${index['24H'].toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + // Format 1M change with color + const change1m = index['1M'] !== undefined ? + (index['1M'] >= 0 ? + `${colors.green}+${index['1M'].toFixed(2)}%${colors.reset}` : + `${colors.red}${index['1M'].toFixed(2)}%${colors.reset}` + ).padEnd(20) : 'N/A'.padEnd(12); + + output += `${colors.white}${id} ${colors.yellow}${name} ${colors.blue}${ticker} ${change24h} ${change1m}${colors.reset}\n`; + }); + + output += `\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n`; + output += `${colors.green}โœ… Total indices available: ${formatNumber(indices.length)}${colors.reset}\n`; + + return output; + } + + // Format indices holdings response + function formatIndicesHoldingsResponse(holdings: any[]): string { + if (!holdings || holdings.length === 0) { + return `${colors.yellow}No holdings found for this index.${colors.reset}`; + } + + let output = `${colors.cyan}${colors.bright}๐Ÿฆ Index Holdings Breakdown${colors.reset}\n`; + output += `${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n\n`; + + output += `${colors.green}Found ${holdings.length} holdings in this index:${colors.reset}\n\n`; + + // Table header + output += `${colors.bright}${'Symbol'.padEnd(12)} ${'Name'.padEnd(20)} ${'Weight'.padEnd(10)} ${'Price'.padEnd(15)} ${'Value'.padEnd(12)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(20)} ${'โ”€'.repeat(10)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)}${colors.reset}\n`; + + let totalWeight = 0; + holdings.forEach(holding => { + // Use the actual field names from API response + const symbol = (holding.TOKEN_SYMBOL || 'N/A').padEnd(12); + const name = (holding.TOKEN_NAME || 'N/A').substring(0, 19).padEnd(20); + + // Format weight + const weightValue = holding.WEIGHT; + const weight = weightValue ? `${(weightValue * 100).toFixed(2)}%` : 'N/A'; + const weightPadded = weight.padEnd(10); + + // Format price + const priceValue = holding.PRICE || 0; + const price = formatPrice(priceValue).padEnd(15); + + // Format value (calculated from weight and price if needed) + const valueValue = holding.VALUE || (weightValue && priceValue ? weightValue * priceValue : null); + const value = valueValue ? formatPrice(valueValue) : 'N/A'; + const valuePadded = value.padEnd(12); + + if (weightValue) totalWeight += weightValue; + + output += `${colors.yellow}${symbol} ${colors.green}${name} ${colors.white}${weightPadded} ${colors.cyan}${price} ${colors.magenta}${valuePadded}${colors.reset}\n`; + }); + + output += `\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n`; + output += `${colors.green}โœ… Total holdings: ${formatNumber(holdings.length)}${colors.reset}\n`; + output += `${colors.green}โœ… Total weight: ${(totalWeight * 100).toFixed(2)}%${colors.reset}\n`; + + return output; + } + + // Format indices performance response + function formatIndicesPerformanceResponse(performance: any[]): string { + if (!performance || performance.length === 0) { + return `${colors.yellow}No performance data found for this index.${colors.reset}`; + } + + let output = `${colors.cyan}${colors.bright}๐Ÿ“ˆ Index Performance Analysis${colors.reset}\n`; + output += `${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n\n`; + + output += `${colors.green}Found ${performance.length} performance data points:${colors.reset}\n\n`; + + // Calculate performance metrics + const firstPoint = performance[0]; + const lastPoint = performance[performance.length - 1]; + + // Use the actual field names from API response + const firstROI = firstPoint.INDEX_CUMULATIVE_ROI; + const lastROI = lastPoint.INDEX_CUMULATIVE_ROI; + + const totalReturn = lastROI && firstROI ? + ((lastROI - firstROI) / Math.abs(firstROI) * 100) : 0; + + // Use the actual field names for date + const firstDate = firstPoint.DATE; + const lastDate = lastPoint.DATE; + + // Summary metrics + output += `${colors.bright}Performance Summary:${colors.reset}\n`; + output += `${colors.white}โ€ข Period: ${firstDate || 'N/A'} to ${lastDate || 'N/A'}${colors.reset}\n`; + output += `${colors.white}โ€ข Total Return: ${totalReturn >= 0 ? + `${colors.green}+${totalReturn.toFixed(2)}%${colors.reset}` : + `${colors.red}${totalReturn.toFixed(2)}%${colors.reset}`}${colors.reset}\n`; + output += `${colors.white}โ€ข Data Points: ${performance.length}${colors.reset}\n\n`; + + // Table header for recent performance + output += `${colors.bright}Recent Performance (Last 10 points):${colors.reset}\n`; + output += `${colors.bright}${'Date'.padEnd(12)} ${'ROI'.padEnd(15)} ${'Change'.padEnd(12)} ${'Trend'.padEnd(8)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(15)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(8)}${colors.reset}\n`; + + // Show last 10 data points + const recentData = performance.slice(-10); + recentData.forEach((point, index) => { + // Use the actual field names from API response + const dateValue = point.DATE; + const date = dateValue ? dateValue.toString().substring(0, 10).padEnd(12) : 'N/A'.padEnd(12); + + // Use the actual field name for ROI + const roiValue = point.INDEX_CUMULATIVE_ROI; + const roi = roiValue ? `${roiValue.toFixed(4)}%` : 'N/A'; + const roiPadded = roi.padEnd(15); + + let change = 'N/A'; + let trend = ''; + if (index > 0 && roiValue && recentData[index - 1].INDEX_CUMULATIVE_ROI) { + const prevROI = recentData[index - 1].INDEX_CUMULATIVE_ROI; + const changeValue = roiValue - prevROI; + change = changeValue >= 0 ? + `${colors.green}+${changeValue.toFixed(4)}%${colors.reset}` : + `${colors.red}${changeValue.toFixed(4)}%${colors.reset}`; + trend = changeValue >= 0 ? `${colors.green}โ†—${colors.reset}` : `${colors.red}โ†˜${colors.reset}`; + } + const changePadded = change.padEnd(20); + + output += `${colors.white}${date} ${colors.cyan}${roiPadded} ${changePadded} ${trend}${colors.reset}\n`; + }); + + output += `\n${colors.cyan}${'โ•'.repeat(70)}${colors.reset}\n`; + output += `${colors.green}โœ… Performance tracking period: ${performance.length} data points${colors.reset}\n`; + + return output; + } + + // Format hourly trading signals response + function formatHourlyTradingSignalsResponse(signals: any[]): string { + if (!signals || signals.length === 0) { + return `${colors.yellow}No hourly trading signals found.${colors.reset}`; + } + + let output = `${colors.magenta}${colors.bright}โฐ Hourly AI Trading Signals${colors.reset}\n`; + output += `${colors.magenta}${'โ•'.repeat(75)}${colors.reset}\n\n`; + + output += `${colors.green}Found ${signals.length} hourly trading signals:${colors.reset}\n\n`; + + // Table header + output += `${colors.bright}${'Symbol'.padEnd(12)} ${'Name'.padEnd(20)} ${'Signal'.padEnd(8)} ${'Strength'.padEnd(12)} ${'Confidence'.padEnd(12)} ${'Price'.padEnd(15)}${colors.reset}\n`; + output += `${colors.dim}${'โ”€'.repeat(12)} ${'โ”€'.repeat(20)} ${'โ”€'.repeat(8)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(12)} ${'โ”€'.repeat(15)}${colors.reset}\n`; + + signals.slice(0, 10).forEach(signal => { + const symbol = (signal.TOKEN_SYMBOL || 'N/A').padEnd(12); + const name = (signal.TOKEN_NAME || 'N/A').substring(0, 19).padEnd(20); + + // Format signal + const tradingSignal = signal.TRADING_SIGNAL || 0; + let signalText = 'HOLD'; + let signalColor = colors.yellow; + + if (tradingSignal === 1) { + signalText = 'BUY'; + signalColor = colors.green; + } else if (tradingSignal === -1) { + signalText = 'SELL'; + signalColor = colors.red; + } + const signalPadded = signalText.padEnd(8); + + // Format strength + const strength = signal.SIGNAL_STRENGTH || 0; + const strengthText = Math.abs(strength).toFixed(2); + const strengthPadded = strengthText.padEnd(12); + + // Format confidence + const confidence = signal.CONFIDENCE || 0; + const confidenceText = `${(confidence * 100).toFixed(1)}%`; + const confidencePadded = confidenceText.padEnd(12); + + // Format price + const price = formatPrice(signal.CURRENT_PRICE || 0).padEnd(15); + + output += `${colors.yellow}${symbol} ${colors.green}${name} ${signalColor}${signalPadded}${colors.reset} ${colors.white}${strengthPadded} ${colors.cyan}${confidencePadded} ${colors.white}${price}${colors.reset}\n`; + }); + + if (signals.length > 10) { + output += `\n${colors.dim}... and ${signals.length - 10} more signals${colors.reset}\n`; + } + + // Summary statistics + const buySignals = signals.filter(s => s.TRADING_SIGNAL === 1).length; + const sellSignals = signals.filter(s => s.TRADING_SIGNAL === -1).length; + const holdSignals = signals.filter(s => s.TRADING_SIGNAL === 0).length; + const avgConfidence = signals.reduce((sum, s) => sum + (s.CONFIDENCE || 0), 0) / signals.length; + + output += `\n${colors.magenta}${'โ•'.repeat(75)}${colors.reset}\n`; + output += `${colors.bright}๐Ÿ“Š Signal Summary:${colors.reset}\n`; + output += `${colors.green}๐Ÿ“ˆ BUY: ${buySignals}${colors.reset} | ${colors.red}๐Ÿ“‰ SELL: ${sellSignals}${colors.reset} | ${colors.yellow}โธ๏ธ HOLD: ${holdSignals}${colors.reset}\n`; + output += `${colors.bright}๐ŸŽฏ Average Confidence: ${(avgConfidence * 100).toFixed(1)}%${colors.reset}\n`; + output += `${colors.dim}โฐ Signals are updated hourly with real-time market data${colors.reset}\n`; + + return output; + } + + // Enhanced API call function with better formatting + export async function tokenMetricsApiCall( + apiKey: string, + baseApiUrl: string, + endpoint: string, + args: any, + logger: any + ) { + // Prepare headers for the request + const headers = { + "accept": "application/json", + "x-api-key": apiKey, + }; + + // Build query parameters + const queryParams = new URLSearchParams(); + Object.keys(args).forEach(key => { + if (args[key] !== undefined && args[key] !== null) { + queryParams.append(key, args[key].toString()); + } + }); + + // Prepare request URL + const url = `${baseApiUrl}${endpoint}${queryParams.toString() ? '?' + queryParams.toString() : ''}`; + + logger(`${colors.blue}๐Ÿ” Making API request to: ${endpoint}${colors.reset}`); + logger(`${colors.dim}๐Ÿ“‹ Full URL: ${url}${colors.reset}`); + logger(`${colors.dim}๐Ÿ“‹ Parameters: ${JSON.stringify(args)}${colors.reset}`); + logger(`${colors.dim}๐Ÿ“‹ Query string: ${queryParams.toString()}${colors.reset}`); + + // Make the API request + const response = await fetch(url, { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`); + } + + const jsonResponse: any = await response.json(); + + // Format response based on endpoint + let formattedMessage = ''; + + if (endpoint === '/tokens' && jsonResponse.data) { + formattedMessage = formatTokensResponse(jsonResponse.data); + } else if (endpoint === '/price' && jsonResponse.data) { + formattedMessage = formatPriceResponse(jsonResponse.data); + } else if (endpoint === '/indices' && jsonResponse.data) { + formattedMessage = formatIndicesResponse(jsonResponse.data); + } else if (endpoint === '/indices-holdings' && jsonResponse.data) { + formattedMessage = formatIndicesHoldingsResponse(jsonResponse.data); + } else if (endpoint === '/indices-performance' && jsonResponse.data) { + formattedMessage = formatIndicesPerformanceResponse(jsonResponse.data); + } else if (endpoint === '/hourly-trading-signals' && jsonResponse.data) { + formattedMessage = formatHourlyTradingSignalsResponse(jsonResponse.data); + } else { + // Default formatting for other endpoints + formattedMessage = `${colors.green}โœ… Successfully retrieved data from ${endpoint}${colors.reset}\n\n`; + formattedMessage += `${colors.cyan}Response Summary:${colors.reset}\n`; + + if (jsonResponse.data && Array.isArray(jsonResponse.data)) { + formattedMessage += `${colors.white}โ€ข Found ${jsonResponse.data.length} items${colors.reset}\n`; + if (jsonResponse.data.length > 0) { + formattedMessage += `${colors.white}โ€ข Sample data: ${JSON.stringify(jsonResponse.data[0], null, 2)}${colors.reset}\n`; + } + } else { + formattedMessage += `${colors.white}${JSON.stringify(jsonResponse, null, 2)}${colors.reset}\n`; + } + } + + logger(formattedMessage); + + // Include raw JSON data for programmatic access (used by chat interface) + const responseWithData = formattedMessage + `\n\nResponse: ${JSON.stringify(jsonResponse)}`; + + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Done, + responseWithData + ); + } + + export async function tokenMetricsApiPostCall( + apiKey: string, + baseApiUrl: string, + endpoint: string, + body: any, + logger: any + ) { + // Prepare headers for the POST request + const headers = { + "accept": "application/json", + "content-type": "application/json", + "x-api-key": apiKey, + }; + + // Prepare request URL + const url = `${baseApiUrl}${endpoint}`; + + logger(`${colors.blue}๐Ÿ” Making POST API request to: ${endpoint}${colors.reset}`); + logger(`Request body: ${JSON.stringify(body)}`); + + // Make the API request + const response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`); + } + + const jsonResponse: any = await response.json(); + + const formattedMessage = `${colors.green}โœ… Successfully queried TokenMetrics AI${colors.reset}\n\n${colors.cyan}AI Response:${colors.reset}\n${colors.white}${JSON.stringify(jsonResponse, null, 2)}${colors.reset}`; + logger(formattedMessage); + + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Done, + formattedMessage + ); + } + + export function exceptionHandler(e: any, logger: any) { + const errorMessage = `${colors.red}โŒ An error occurred while querying TokenMetrics data:${colors.reset}\n${colors.yellow}${e.message || "Unknown error"}${colors.reset}`; + logger(errorMessage); + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + errorMessage + ); + } diff --git a/plugins/tokenMetricsPlugin/tests/test-individual.ts b/plugins/tokenMetricsPlugin/tests/test-individual.ts new file mode 100644 index 00000000..4d806625 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-individual.ts @@ -0,0 +1,262 @@ +import { validateEnvironment, createTestPlugin, TestTracker, colors } from './test-setup'; + +async function testTokenMetricsEndpoints() { + console.log(`${colors.bright}๐Ÿš€ TokenMetrics Endpoint Testing Suite${colors.reset}`); + console.log(`${colors.bright}=====================================\n${colors.reset}`); + + // Validate environment + if (!validateEnvironment()) { + process.exit(1); + } + + const plugin = createTestPlugin(); + const tracker = new TestTracker(); + + // Test 1: Tokens Database + await testEndpoint(tracker, 'Tokens Database', async () => { + const worker = plugin.getWorker({ + functions: [plugin.getTokens] + }); + + // Simulate calling the function with test parameters + const result = await plugin.getTokens.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved token database' : result.feedback + }; + }); + + // Test 2: Top Market Cap Tokens + await testEndpoint(tracker, 'Top Market Cap Tokens', async () => { + const result = await plugin.getTopMarketCapTokens.executable( + { top_k: "10", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved top market cap tokens' : result.feedback + }; + }); + + // Test 3: Price Data (requires token_id, so we'll test with Bitcoin's ID) + await testEndpoint(tracker, 'Price Data', async () => { + const result = await plugin.getPriceData.executable( + { token_id: '3375' }, // Bitcoin's token ID + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved price data for Bitcoin' : result.feedback + }; + }); + + // Test 4: Trader Grades + await testEndpoint(tracker, 'Trader Grades', async () => { + const result = await plugin.getTraderGrades.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved trader grades' : result.feedback + }; + }); + + // Test 5: Investor Grades + await testEndpoint(tracker, 'Investor Grades', async () => { + const result = await plugin.getInvestorGrades.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved investor grades' : result.feedback + }; + }); + + // Test 6: Trading Signals + await testEndpoint(tracker, 'Trading Signals', async () => { + const result = await plugin.getTradingSignals.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved trading signals' : result.feedback + }; + }); + + // Test 7: Market Metrics + await testEndpoint(tracker, 'Market Metrics', async () => { + const result = await plugin.getMarketMetrics.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved market metrics' : result.feedback + }; + }); + + // Test 8: Quantmetrics + await testEndpoint(tracker, 'Quantmetrics', async () => { + const result = await plugin.getQuantmetrics.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved quantmetrics' : result.feedback + }; + }); + + // Test 9: Hourly OHLCV + await testEndpoint(tracker, 'Hourly OHLCV', async () => { + const result = await plugin.getHourlyOhlcv.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved hourly OHLCV data' : result.feedback + }; + }); + + // Test 10: Daily OHLCV + await testEndpoint(tracker, 'Daily OHLCV', async () => { + const result = await plugin.getDailyOhlcv.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved daily OHLCV data' : result.feedback + }; + }); + + // Test 11: AI Reports + await testEndpoint(tracker, 'AI Reports', async () => { + const result = await plugin.getAiReports.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved AI reports' : result.feedback + }; + }); + + // Test 12: Crypto Investors + await testEndpoint(tracker, 'Crypto Investors', async () => { + const result = await plugin.getCryptoInvestors.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved crypto investors data' : result.feedback + }; + }); + + // Test 13: Resistance & Support + await testEndpoint(tracker, 'Resistance & Support', async () => { + const result = await plugin.getResistanceSupport.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved resistance & support levels' : result.feedback + }; + }); + + // Test 14: TokenMetrics AI + await testEndpoint(tracker, 'TokenMetrics AI', async () => { + const result = await plugin.getTokenMetricsAi.executable( + { user_message: "What is the next 100x coin?" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully queried TokenMetrics AI' : result.feedback + }; + }); + + // Test 15: Sentiments + await testEndpoint(tracker, 'Sentiments', async () => { + const result = await plugin.getSentiments.executable( + { limit: "5", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved sentiment data' : result.feedback + }; + }); + + // Test 16: Scenario Analysis + await testEndpoint(tracker, 'Scenario Analysis', async () => { + const result = await plugin.getScenarioAnalysis.executable( + { token_id: "3375", limit: "5", page: "1" }, // Bitcoin's token ID + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved scenario analysis' : result.feedback + }; + }); + + // Test 17: Correlation + await testEndpoint(tracker, 'Correlation', async () => { + const result = await plugin.getCorrelation.executable( + { token_id: "3375", limit: "5", page: "1" }, // Bitcoin's token ID + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + return { + success: result.status === 'done', + message: result.status === 'done' ? 'Successfully retrieved correlation data' : result.feedback + }; + }); + + // Print final summary + tracker.printSummary(); +} + +async function testEndpoint( + tracker: TestTracker, + name: string, + testFn: () => Promise<{ success: boolean; message: string }> +) { + const startTime = tracker.startTest(name); + + try { + const result = await testFn(); + tracker.endTest(name, startTime, result.success, result.message); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + tracker.endTest(name, startTime, false, `Error: ${errorMessage}`); + } +} + +// Run the tests +testTokenMetricsEndpoints().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-integration.ts b/plugins/tokenMetricsPlugin/tests/test-integration.ts new file mode 100644 index 00000000..cdd82f62 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-integration.ts @@ -0,0 +1,63 @@ +import { GameAgent } from "@virtuals-protocol/game"; +import { validateEnvironment, createTestPlugin, TestTracker, colors, testConfig } from './test-setup'; + +async function testFullIntegration() { + console.log(`${colors.bright}๐Ÿ”— TokenMetrics Full Integration Test${colors.reset}`); + console.log(`${colors.bright}===================================\n${colors.reset}`); + + if (!validateEnvironment()) { + process.exit(1); + } + + const tracker = new TestTracker(); + const plugin = createTestPlugin(); + + // Test 1: Plugin Initialization + const startTime1 = tracker.startTest('Plugin Initialization'); + try { + const worker = plugin.getWorker({}); + const hasAllFunctions = worker.functions.length === 17; + tracker.endTest('Plugin Initialization', startTime1, hasAllFunctions, + `Plugin created with ${worker.functions.length}/17 functions`); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + tracker.endTest('Plugin Initialization', startTime1, false, errorMessage); + } + + // Test 2: Agent Creation + const startTime2 = tracker.startTest('Agent Creation with TokenMetrics'); + try { + const agent = new GameAgent(testConfig.gameApiKey, { + name: "Test TokenMetrics Agent", + goal: "Test the TokenMetrics integration", + description: "A test agent to verify TokenMetrics integration works properly", + workers: [plugin.getWorker({})], + }); + + // Test agent initialization + await agent.init(); + tracker.endTest('Agent Creation with TokenMetrics', startTime2, true, + 'Agent successfully created and initialized with TokenMetrics capabilities'); + + // Test 3: Agent Function Execution + const startTime3 = tracker.startTest('Agent Function Execution'); + try { + // We can't easily test the full agent.step() without more complex setup, + // but we can verify the agent has the right configuration + const hasWorkers = agent.workers && agent.workers.length > 0; + tracker.endTest('Agent Function Execution', startTime3, hasWorkers, + 'Agent properly configured with TokenMetrics workers'); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + tracker.endTest('Agent Function Execution', startTime3, false, errorMessage); + } + + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + tracker.endTest('Agent Creation with TokenMetrics', startTime2, false, errorMessage); + } + + tracker.printSummary(); +} + +testFullIntegration().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-scenarios/indices-endpoints-demo.ts b/plugins/tokenMetricsPlugin/tests/test-scenarios/indices-endpoints-demo.ts new file mode 100644 index 00000000..2b9c6a99 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-scenarios/indices-endpoints-demo.ts @@ -0,0 +1,93 @@ +import { validateEnvironment, createTestPlugin, colors } from '../test-setup'; + +// Add cyan color for better formatting +const extendedColors = { + ...colors, + cyan: '\x1b[36m' +}; + +async function demoIndicesEndpoints() { + console.log(`${extendedColors.blue}${extendedColors.bright}๐Ÿš€ TokenMetrics Indices Endpoints Demo${extendedColors.reset}`); + console.log(`${extendedColors.blue}${extendedColors.bright}======================================\n${extendedColors.reset}`); + + // Validate environment + if (!validateEnvironment()) { + process.exit(1); + } + + const plugin = createTestPlugin(); + + console.log(`${extendedColors.yellow}${extendedColors.bright}๐Ÿ“Š Demo 1: Crypto Indices Overview${extendedColors.reset}`); + console.log(`${extendedColors.yellow}Getting all available crypto indices with performance data...${extendedColors.reset}\n`); + + try { + const indicesResult = await plugin.getIndices.executable( + { limit: "20", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${extendedColors.green}โœ… Indices overview completed successfully\n${extendedColors.reset}`); + } catch (error) { + console.log(`${extendedColors.red}โŒ Indices overview failed: ${error}\n${extendedColors.reset}`); + } + + console.log(`${extendedColors.yellow}${extendedColors.bright}๐Ÿ’ผ Demo 2: Index Holdings Analysis${extendedColors.reset}`); + console.log(`${extendedColors.yellow}Getting detailed holdings for crypto index 1 (GLOBAL)...${extendedColors.reset}\n`); + + try { + const holdingsResult = await plugin.getIndicesHoldings.executable( + { id: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${extendedColors.green}โœ… Index holdings analysis completed successfully\n${extendedColors.reset}`); + } catch (error) { + console.log(`${extendedColors.red}โŒ Index holdings analysis failed: ${error}\n${extendedColors.reset}`); + } + + console.log(`${extendedColors.yellow}${extendedColors.bright}๐Ÿ“ˆ Demo 3: Index Performance Tracking${extendedColors.reset}`); + console.log(`${extendedColors.yellow}Getting historical performance data for crypto index 1...${extendedColors.reset}\n`); + + try { + const performanceResult = await plugin.getIndicesPerformance.executable( + { id: "1", limit: "30", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${extendedColors.green}โœ… Index performance tracking completed successfully\n${extendedColors.reset}`); + } catch (error) { + console.log(`${extendedColors.red}โŒ Index performance tracking failed: ${error}\n${extendedColors.reset}`); + } + + console.log(`${extendedColors.yellow}${extendedColors.bright}๐Ÿ” Demo 4: DeFi Index Deep Dive${extendedColors.reset}`); + console.log(`${extendedColors.yellow}Analyzing DeFi index (ID: 3) holdings and performance...${extendedColors.reset}\n`); + + try { + // Get DeFi index holdings + const defiHoldingsResult = await plugin.getIndicesHoldings.executable( + { id: "3" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + // Get DeFi index performance + const defiPerformanceResult = await plugin.getIndicesPerformance.executable( + { id: "3", limit: "15", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + + console.log(`${extendedColors.green}โœ… DeFi index deep dive completed successfully\n${extendedColors.reset}`); + } catch (error) { + console.log(`${extendedColors.red}โŒ DeFi index deep dive failed: ${error}\n${extendedColors.reset}`); + } + + console.log(`${extendedColors.green}${extendedColors.bright}๐ŸŽ‰ Indices Endpoints Demo Complete!${extendedColors.reset}`); + console.log(`${extendedColors.blue}All 3 new indices endpoints have been successfully demonstrated:${extendedColors.reset}`); + console.log(`${extendedColors.blue}โ€ข GET /indices - Overview of all crypto indices with performance${extendedColors.reset}`); + console.log(`${extendedColors.blue}โ€ข GET /indices-holdings - Detailed holdings breakdown by index${extendedColors.reset}`); + console.log(`${extendedColors.blue}โ€ข GET /indices-performance - Historical ROI tracking${extendedColors.reset}\n`); + + console.log(`${extendedColors.cyan}${extendedColors.bright}๐Ÿ’ก Integration Benefits:${extendedColors.reset}`); + console.log(`${extendedColors.cyan}โ€ข Portfolio diversification insights through index analysis${extendedColors.reset}`); + console.log(`${extendedColors.cyan}โ€ข Real-time holdings tracking with weights and prices${extendedColors.reset}`); + console.log(`${extendedColors.cyan}โ€ข Historical performance analysis for trend identification${extendedColors.reset}`); + console.log(`${extendedColors.cyan}โ€ข Cross-index comparison capabilities${extendedColors.reset}\n`); +} + +demoIndicesEndpoints().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-scenarios/market-analyst.ts b/plugins/tokenMetricsPlugin/tests/test-scenarios/market-analyst.ts new file mode 100644 index 00000000..163b4d3f --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-scenarios/market-analyst.ts @@ -0,0 +1 @@ + diff --git a/plugins/tokenMetricsPlugin/tests/test-scenarios/new-endpoints-demo.ts b/plugins/tokenMetricsPlugin/tests/test-scenarios/new-endpoints-demo.ts new file mode 100644 index 00000000..2f8bbf2c --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-scenarios/new-endpoints-demo.ts @@ -0,0 +1,74 @@ +import { validateEnvironment, createTestPlugin, colors } from '../test-setup'; + +async function demoNewEndpoints() { + console.log(`${colors.blue}${colors.bright}๐Ÿš€ TokenMetrics New Endpoints Demo${colors.reset}`); + console.log(`${colors.blue}${colors.bright}=====================================\n${colors.reset}`); + + // Validate environment + if (!validateEnvironment()) { + process.exit(1); + } + + const plugin = createTestPlugin(); + + console.log(`${colors.yellow}${colors.bright}๐Ÿค– Demo 1: TokenMetrics AI Chat${colors.reset}`); + console.log(`${colors.yellow}Asking: "What is the next 100x coin?"${colors.reset}\n`); + + try { + const aiResult = await plugin.getTokenMetricsAi.executable( + { user_message: "What is the next 100x coin?" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${colors.green}โœ… AI Chat completed successfully\n${colors.reset}`); + } catch (error) { + console.log(`${colors.red}โŒ AI Chat failed: ${error}\n${colors.reset}`); + } + + console.log(`${colors.yellow}${colors.bright}๐Ÿ“Š Demo 2: Market Sentiment Analysis${colors.reset}`); + console.log(`${colors.yellow}Getting latest sentiment data...${colors.reset}\n`); + + try { + const sentimentResult = await plugin.getSentiments.executable( + { limit: "3", page: "1" }, + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${colors.green}โœ… Sentiment analysis completed successfully\n${colors.reset}`); + } catch (error) { + console.log(`${colors.red}โŒ Sentiment analysis failed: ${error}\n${colors.reset}`); + } + + console.log(`${colors.yellow}${colors.bright}๐Ÿ”ฎ Demo 3: Scenario Analysis for Bitcoin${colors.reset}`); + console.log(`${colors.yellow}Getting price predictions for different market scenarios...${colors.reset}\n`); + + try { + const scenarioResult = await plugin.getScenarioAnalysis.executable( + { token_id: "3375", limit: "3", page: "1" }, // Bitcoin + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${colors.green}โœ… Scenario analysis completed successfully\n${colors.reset}`); + } catch (error) { + console.log(`${colors.red}โŒ Scenario analysis failed: ${error}\n${colors.reset}`); + } + + console.log(`${colors.yellow}${colors.bright}๐Ÿ”— Demo 4: Token Correlation Analysis${colors.reset}`); + console.log(`${colors.yellow}Getting correlation data for Bitcoin...${colors.reset}\n`); + + try { + const correlationResult = await plugin.getCorrelation.executable( + { token_id: "3375", limit: "3", page: "1" }, // Bitcoin + (msg: string) => console.log(` ๐Ÿ“ ${msg}`) + ); + console.log(`${colors.green}โœ… Correlation analysis completed successfully\n${colors.reset}`); + } catch (error) { + console.log(`${colors.red}โŒ Correlation analysis failed: ${error}\n${colors.reset}`); + } + + console.log(`${colors.green}${colors.bright}๐ŸŽ‰ New Endpoints Demo Complete!${colors.reset}`); + console.log(`${colors.blue}All 4 new endpoints have been successfully demonstrated:${colors.reset}`); + console.log(`${colors.blue}โ€ข TokenMetrics AI Chat - Interactive AI assistant${colors.reset}`); + console.log(`${colors.blue}โ€ข Sentiments - Market sentiment analysis${colors.reset}`); + console.log(`${colors.blue}โ€ข Scenario Analysis - Price predictions${colors.reset}`); + console.log(`${colors.blue}โ€ข Correlation - Token relationship analysis${colors.reset}\n`); +} + +demoNewEndpoints().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-scenarios/research-agent.ts b/plugins/tokenMetricsPlugin/tests/test-scenarios/research-agent.ts new file mode 100644 index 00000000..0e967de9 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-scenarios/research-agent.ts @@ -0,0 +1,61 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../../index"; +import { colors } from '../test-setup'; + +// This demonstrates how a user would create a research agent +async function createResearchAgent() { + console.log(`${colors.bright}๐Ÿ”ฌ Creating TokenMetrics Research Agent${colors.reset}`); + console.log(`${colors.bright}=====================================\n${colors.reset}`); + + const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, + }); + + // Create a comprehensive research agent + const researchAgent = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Crypto Research Agent", + goal: "Conduct comprehensive cryptocurrency research using AI-powered analysis, investor grades, and detailed reports for long-term investment decisions.", + description: + "You are an advanced crypto research agent powered by TokenMetrics AI. You conduct deep fundamental analysis, evaluate long-term investment potential, generate comprehensive reports, and analyze institutional investor behavior. You focus on technology assessment, market positioning, and strategic investment opportunities.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [ + tokenMetricsPlugin.getInvestorGrades, // Long-term analysis + tokenMetricsPlugin.getAiReports, // Comprehensive research + tokenMetricsPlugin.getCryptoInvestors, // Institutional insights + tokenMetricsPlugin.getQuantmetrics, // Advanced analytics + tokenMetricsPlugin.getTokens, // Token database + tokenMetricsPlugin.getTopMarketCapTokens, // Market leaders + ], + }), + ], + }); + + researchAgent.setLogger((agent, message) => { + console.log(`${colors.blue}[${agent.name}]${colors.reset} ${message}\n`); + }); + + console.log(`${colors.green}โœ… Research agent created successfully!${colors.reset}`); + console.log(`${colors.yellow}๐Ÿ“‹ Agent capabilities:${colors.reset}`); + console.log(` โ€ข Long-term investor grade analysis`); + console.log(` โ€ข Comprehensive AI-generated research reports`); + console.log(` โ€ข Institutional investor behavior analysis`); + console.log(` โ€ข Advanced quantitative metrics`); + console.log(` โ€ข Complete token database access`); + console.log(` โ€ข Market capitalization rankings`); + console.log(`\n${colors.bright}๐Ÿš€ Starting research agent... (Press Ctrl+C to stop)${colors.reset}\n`); + + await researchAgent.init(); + + while (true) { + await researchAgent.step({ + verbose: true, + }); + } +} + +createResearchAgent().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-scenarios/trading-bot.ts b/plugins/tokenMetricsPlugin/tests/test-scenarios/trading-bot.ts new file mode 100644 index 00000000..b61b76f5 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-scenarios/trading-bot.ts @@ -0,0 +1,61 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import { GameAgent } from "@virtuals-protocol/game"; +import TokenMetricsPlugin from "../../index"; +import { colors } from '../test-setup'; + +// This demonstrates how a user would create a trading bot using TokenMetrics +async function createTradingBot() { + console.log(`${colors.bright}๐Ÿค– Creating TokenMetrics Trading Bot${colors.reset}`); + console.log(`${colors.bright}==================================\n${colors.reset}`); + + const tokenMetricsPlugin = new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: process.env.TOKENMETRICS_API_KEY!, + }, + }); + + // Create a focused trading bot with specific functions + const tradingBot = new GameAgent(process.env.GAME_API_KEY ?? "", { + name: "TokenMetrics Trading Bot", + goal: "Analyze cryptocurrencies for short-term trading opportunities using AI-powered grades and signals.", + description: + "You are a specialized crypto trading bot powered by TokenMetrics AI. You analyze trader grades, trading signals, and market metrics to identify optimal entry and exit points for short-term trades. You focus on technical analysis and momentum indicators.", + workers: [ + tokenMetricsPlugin.getWorker({ + functions: [ + tokenMetricsPlugin.getTraderGrades, // Short-term analysis + tokenMetricsPlugin.getTradingSignals, // Buy/sell signals + tokenMetricsPlugin.getMarketMetrics, // Market conditions + tokenMetricsPlugin.getPriceData, // Current prices + tokenMetricsPlugin.getResistanceSupport, // Technical levels + ], + }), + ], + }); + + // Set up detailed logging to see what the bot is thinking + tradingBot.setLogger((agent, message) => { + console.log(`${colors.blue}[${agent.name}]${colors.reset} ${message}\n`); + }); + + console.log(`${colors.green}โœ… Trading bot created successfully!${colors.reset}`); + console.log(`${colors.yellow}๐Ÿ“‹ Bot capabilities:${colors.reset}`); + console.log(` โ€ข Trader grade analysis for short-term opportunities`); + console.log(` โ€ข AI-generated trading signals (buy/sell recommendations)`); + console.log(` โ€ข Market condition assessment`); + console.log(` โ€ข Real-time price data`); + console.log(` โ€ข Technical support and resistance levels`); + console.log(`\n${colors.bright}๐Ÿš€ Starting bot... (Press Ctrl+C to stop)${colors.reset}\n`); + + await tradingBot.init(); + + // Run the bot - it will continuously analyze and provide trading insights + while (true) { + await tradingBot.step({ + verbose: true, + }); + } +} + +createTradingBot().catch(console.error); diff --git a/plugins/tokenMetricsPlugin/tests/test-setup.ts b/plugins/tokenMetricsPlugin/tests/test-setup.ts new file mode 100644 index 00000000..f75b95b9 --- /dev/null +++ b/plugins/tokenMetricsPlugin/tests/test-setup.ts @@ -0,0 +1,140 @@ +import { config } from "dotenv"; +config({ path: "./.env" }); +import TokenMetricsPlugin from "../src/index"; + +// Color coding for test results +export const colors = { + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m', + bright: '\x1b[1m' +}; + +// Test configuration +export const testConfig = { + tokenMetricsApiKey: process.env.TOKENMETRICS_API_KEY!, + gameApiKey: process.env.GAME_API_KEY!, + timeout: 30000, // 30 seconds timeout for API calls +}; + +// Validate environment setup +export function validateEnvironment(): boolean { + console.log(`${colors.blue}${colors.bright}๐Ÿ” Validating Environment Setup...${colors.reset}\n`); + + let isValid = true; + + // Check TokenMetrics API Key + if (!testConfig.tokenMetricsApiKey) { + console.log(`${colors.red}โŒ TOKENMETRICS_API_KEY is missing in .env file${colors.reset}`); + isValid = false; + } else if (!testConfig.tokenMetricsApiKey.startsWith('tm-')) { + console.log(`${colors.yellow}โš ๏ธ TokenMetrics API key should start with 'tm-'${colors.reset}`); + } else { + console.log(`${colors.green}โœ… TokenMetrics API Key found and properly formatted${colors.reset}`); + } + + // Check GAME API Key + if (!testConfig.gameApiKey) { + console.log(`${colors.red}โŒ GAME_API_KEY is missing in .env file${colors.reset}`); + isValid = false; + } else { + console.log(`${colors.green}โœ… Virtuals Protocol GAME API Key found${colors.reset}`); + } + + if (!isValid) { + console.log(`\n${colors.red}${colors.bright}โŒ Environment setup incomplete. Please check your .env file.${colors.reset}\n`); + console.log(`${colors.yellow}Required .env format:${colors.reset}`); + console.log(`GAME_API_KEY=your_game_api_key_here`); + console.log(`TOKENMETRICS_API_KEY=tm-your-api-key-here\n`); + } else { + console.log(`\n${colors.green}${colors.bright}โœ… Environment setup complete!${colors.reset}\n`); + } + + return isValid; +} + +// Create plugin instance for testing +export function createTestPlugin(): TokenMetricsPlugin { + return new TokenMetricsPlugin({ + apiClientConfig: { + apiKey: testConfig.tokenMetricsApiKey, + }, + }); +} + +// Test result tracker +export class TestTracker { + private passed = 0; + private failed = 0; + private results: { name: string; status: 'PASS' | 'FAIL'; message: string; duration: number }[] = []; + + startTest(name: string): number { + console.log(`${colors.blue}๐Ÿงช Testing: ${name}${colors.reset}`); + return Date.now(); + } + + endTest(name: string, startTime: number, success: boolean, message: string = '') { + const duration = Date.now() - startTime; + const status = success ? 'PASS' : 'FAIL'; + + if (success) { + this.passed++; + console.log(`${colors.green}โœ… PASS: ${name} (${duration}ms)${colors.reset}`); + if (message) console.log(` ${colors.green}${message}${colors.reset}`); + } else { + this.failed++; + console.log(`${colors.red}โŒ FAIL: ${name} (${duration}ms)${colors.reset}`); + if (message) console.log(` ${colors.red}${message}${colors.reset}`); + } + + this.results.push({ name, status, message, duration }); + console.log(''); // Empty line for readability + } + + printSummary() { + console.log(`${colors.bright}๐Ÿ“Š TEST SUMMARY${colors.reset}`); + console.log(`${colors.bright}===============${colors.reset}`); + console.log(`${colors.green}โœ… Passed: ${this.passed}${colors.reset}`); + console.log(`${colors.red}โŒ Failed: ${this.failed}${colors.reset}`); + console.log(`๐Ÿ“ˆ Total: ${this.passed + this.failed}`); + console.log(`๐ŸŽฏ Success Rate: ${((this.passed / (this.passed + this.failed)) * 100).toFixed(1)}%\n`); + + if (this.failed > 0) { + console.log(`${colors.red}${colors.bright}Failed Tests:${colors.reset}`); + this.results + .filter(r => r.status === 'FAIL') + .forEach(r => console.log(`${colors.red}โ€ข ${r.name}: ${r.message}${colors.reset}`)); + } + } +} + +// Main execution when run directly +if (require.main === module) { + console.log(`${colors.blue}${colors.bright}๐Ÿš€ TokenMetrics Virtuals Plugin - Test Setup${colors.reset}\n`); + + const isValid = validateEnvironment(); + + if (isValid) { + console.log(`${colors.green}${colors.bright}๐ŸŽ‰ Ready to run tests!${colors.reset}`); + console.log(`${colors.blue}Available commands:${colors.reset}`); + console.log(`${colors.yellow}๐Ÿš€ Interactive Chat:${colors.reset}`); + console.log(`โ€ข npm run chat - Interactive TokenMetrics AI chat interface`); + console.log(`${colors.blue}๐Ÿ“‹ Test Commands:${colors.reset}`); + console.log(`โ€ข npm run test:individual - Run individual API tests`); + console.log(`โ€ข npm run test:integration - Run integration tests`); + console.log(`โ€ข npm run test:all - Run all tests`); + console.log(`โ€ข npm run demo:trading-bot - Demo trading bot scenario`); + console.log(`โ€ข npm run demo:research-agent - Demo research agent scenario`); + console.log(`โ€ข npm run demo:new-endpoints - Demo new endpoints (AI, Sentiments, Scenario Analysis, Correlation)`); + console.log(`โ€ข npm run demo:indices - Demo crypto indices endpoints (Indices, Holdings, Performance)`); + console.log(`${colors.blue}๐Ÿ“Š Individual Examples:${colors.reset}`); + console.log(`โ€ข npm run example:indices - Crypto indices overview`); + console.log(`โ€ข npm run example:indices-holdings - Index portfolio composition`); + console.log(`โ€ข npm run example:indices-performance - Historical index performance\n`); + process.exit(0); + } else { + process.exit(1); + } +} diff --git a/plugins/tokenMetricsPlugin/tsconfig.json b/plugins/tokenMetricsPlugin/tsconfig.json new file mode 100644 index 00000000..86cdf3bc --- /dev/null +++ b/plugins/tokenMetricsPlugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}