“Transcendental-Programmer” commited on
Commit
20eee66
·
1 Parent(s): 539f014

feat: Initial commit with project structure and initial files

Browse files
.env.example ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Google Gemini API Key (Required)
2
+ GEMINI_API_KEY=your_gemini_api_key_here
3
+
4
+ # CoinGecko API Key (Optional - for higher rate limits)
5
+ COINGECKO_API_KEY=your_coingecko_api_key_here
6
+
7
+ # CryptoCompare API Key (Optional - for additional data sources)
8
+ CRYPTOCOMPARE_API_KEY=your_cryptocompare_api_key_here
app.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import json
4
+ from src.enhanced_agent import EnhancedResearchAgent
5
+ from src.portfolio_analyzer import portfolio_analyzer
6
+ from src.visualizations import create_price_chart, create_market_overview, create_comparison_chart
7
+ from src.cache_manager import cache_manager
8
+ import asyncio
9
+
10
+ research_agent = EnhancedResearchAgent()
11
+
12
+ async def process_research_query(query, history):
13
+ try:
14
+ if not query.strip():
15
+ return history + [["Please enter a research query.", ""]]
16
+
17
+ response = await research_agent.research_with_context(query)
18
+ return history + [[query, response]]
19
+ except Exception as e:
20
+ error_msg = f"Enhanced research failed: {str(e)}"
21
+ return history + [[query, error_msg]]
22
+
23
+ def research_query_sync(query, history):
24
+ return asyncio.run(process_research_query(query, history))
25
+
26
+ async def get_market_data():
27
+ try:
28
+ data = await research_agent.get_comprehensive_market_data()
29
+ chart = create_market_overview(data)
30
+ return chart
31
+ except Exception as e:
32
+ return f"Enhanced market data unavailable: {str(e)}"
33
+
34
+ def get_market_data_sync():
35
+ return asyncio.run(get_market_data())
36
+
37
+ async def get_price_chart(symbol):
38
+ try:
39
+ if not symbol.strip():
40
+ return "Please enter a cryptocurrency symbol"
41
+
42
+ data = await research_agent.get_price_history(symbol)
43
+ chart = create_price_chart(data, symbol)
44
+ return chart
45
+ except Exception as e:
46
+ return f"Chart generation failed: {str(e)}"
47
+
48
+ def get_price_chart_sync(symbol):
49
+ return asyncio.run(get_price_chart(symbol))
50
+
51
+ async def analyze_portfolio_async(portfolio_text):
52
+ try:
53
+ if not portfolio_text.strip():
54
+ return "Please enter your portfolio holdings in JSON format"
55
+
56
+ holdings = json.loads(portfolio_text)
57
+ analysis = await portfolio_analyzer.analyze_portfolio(holdings)
58
+
59
+ result = f"📊 PORTFOLIO ANALYSIS\n\n"
60
+ result += f"💰 Total Value: ${analysis['total_value']:,.2f}\n"
61
+ result += f"📈 24h Change: ${analysis['change_24h']:+,.2f} ({analysis['change_24h_percentage']:+.2f}%)\n\n"
62
+
63
+ result += "🏦 ASSET ALLOCATION:\n"
64
+ for asset in analysis['asset_allocation'][:10]:
65
+ result += f"• {asset['name']} ({asset['symbol']}): {asset['percentage']:.1f}% (${asset['value']:,.2f})\n"
66
+
67
+ result += f"\n⚠️ RISK ASSESSMENT:\n"
68
+ result += f"Overall Risk: {analysis['risk_metrics']['overall_risk']}\n"
69
+ result += f"Diversification Score: {analysis['risk_metrics']['diversification_score']}/10\n"
70
+ result += f"Largest Position: {analysis['risk_metrics']['largest_holding_percentage']:.1f}%\n\n"
71
+
72
+ result += "💡 RECOMMENDATIONS:\n"
73
+ for i, rec in enumerate(analysis['recommendations'], 1):
74
+ result += f"{i}. {rec}\n"
75
+
76
+ return result
77
+
78
+ except json.JSONDecodeError:
79
+ return "❌ Invalid JSON format. Please use format: [{'symbol': 'BTC', 'amount': 1.0}, {'symbol': 'ETH', 'amount': 10.0}]"
80
+ except Exception as e:
81
+ return f"❌ Portfolio analysis failed: {str(e)}"
82
+
83
+ async def get_defi_analysis_async():
84
+ try:
85
+ data = await research_agent.get_defi_analysis()
86
+
87
+ result = "🏦 DeFi ECOSYSTEM ANALYSIS\n\n"
88
+
89
+ if "top_protocols" in data:
90
+ result += "📊 TOP PROTOCOLS BY TVL:\n"
91
+ for i, protocol in enumerate(data["top_protocols"][:10], 1):
92
+ name = protocol.get("name", "Unknown")
93
+ tvl = protocol.get("tvl", 0)
94
+ chain = protocol.get("chain", "Unknown")
95
+ change = protocol.get("change_1d", 0)
96
+ result += f"{i:2d}. {name} ({chain}): ${tvl/1e9:.2f}B TVL ({change:+.2f}%)\n"
97
+
98
+ if "top_yields" in data:
99
+ result += "\n💰 HIGH YIELD OPPORTUNITIES:\n"
100
+ for i, pool in enumerate(data["top_yields"][:5], 1):
101
+ symbol = pool.get("symbol", "Unknown")
102
+ apy = pool.get("apy", 0)
103
+ tvl = pool.get("tvlUsd", 0)
104
+ result += f"{i}. {symbol}: {apy:.2f}% APY (${tvl/1e6:.1f}M TVL)\n"
105
+
106
+ return result
107
+
108
+ except Exception as e:
109
+ return f"❌ DeFi analysis failed: {str(e)}"
110
+
111
+ def clear_cache():
112
+ cache_manager.clear()
113
+ return "Cache cleared successfully"
114
+
115
+ def analyze_portfolio_sync(portfolio_text):
116
+ return asyncio.run(analyze_portfolio_async(portfolio_text))
117
+
118
+ def get_defi_analysis_sync():
119
+ return asyncio.run(get_defi_analysis_async())
120
+
121
+ with gr.Blocks(
122
+ title="Web3 Research Co-Pilot",
123
+ theme=gr.themes.Soft(primary_hue="blue", secondary_hue="gray"),
124
+ css="""
125
+ .container { max-width: 1200px; margin: 0 auto; }
126
+ .header { text-align: center; padding: 20px; }
127
+ .chat-container { min-height: 400px; }
128
+ .chart-container { min-height: 500px; }
129
+ """
130
+ ) as app:
131
+
132
+ gr.Markdown("# 🚀 Web3 Research Co-Pilot", elem_classes=["header"])
133
+ gr.Markdown("*AI-powered cryptocurrency research with real-time data integration*", elem_classes=["header"])
134
+
135
+ with gr.Tabs():
136
+
137
+ with gr.Tab("🤖 Research Chat"):
138
+ with gr.Row():
139
+ with gr.Column(scale=3):
140
+ chatbot = gr.Chatbot(
141
+ value=[],
142
+ height=400,
143
+ elem_classes=["chat-container"],
144
+ show_label=False
145
+ )
146
+
147
+ with gr.Row():
148
+ query_input = gr.Textbox(
149
+ placeholder="Ask about crypto markets, prices, trends, analysis...",
150
+ scale=4,
151
+ show_label=False
152
+ )
153
+ submit_btn = gr.Button("Research", variant="primary")
154
+
155
+ gr.Examples(
156
+ examples=[
157
+ "What's the current Bitcoin price and trend?",
158
+ "Compare Ethereum vs Solana DeFi ecosystems",
159
+ "Analyze top DeFi protocols and TVL trends",
160
+ "What are the trending coins and latest crypto news?",
161
+ "Show me high-yield DeFi opportunities with risk analysis"
162
+ ],
163
+ inputs=query_input
164
+ )
165
+
166
+ with gr.Column(scale=1):
167
+ gr.Markdown("### 📊 Quick Actions")
168
+ market_btn = gr.Button("Market Overview", size="sm")
169
+ market_output = gr.HTML()
170
+
171
+ clear_btn = gr.Button("Clear Cache", size="sm", variant="secondary")
172
+ clear_output = gr.Textbox(show_label=False, interactive=False)
173
+
174
+ with gr.Tab("📈 Price Charts"):
175
+ with gr.Row():
176
+ symbol_input = gr.Textbox(
177
+ label="Cryptocurrency Symbol",
178
+ placeholder="BTC, ETH, SOL, etc.",
179
+ value="BTC"
180
+ )
181
+ chart_btn = gr.Button("Generate Chart", variant="primary")
182
+
183
+ chart_output = gr.HTML(elem_classes=["chart-container"])
184
+
185
+ gr.Examples(
186
+ examples=["BTC", "ETH", "SOL", "ADA", "DOT"],
187
+ inputs=symbol_input
188
+ )
189
+
190
+ with gr.Tab("💼 Portfolio Analysis"):
191
+ with gr.Row():
192
+ with gr.Column(scale=1):
193
+ portfolio_input = gr.Textbox(
194
+ label="Portfolio Holdings (JSON Format)",
195
+ placeholder='[{"symbol": "BTC", "amount": 1.0}, {"symbol": "ETH", "amount": 10.0}, {"symbol": "SOL", "amount": 50.0}]',
196
+ lines=5,
197
+ info="Enter your crypto holdings in JSON format with symbol and amount"
198
+ )
199
+ portfolio_btn = gr.Button("Analyze Portfolio", variant="primary")
200
+
201
+ with gr.Column(scale=2):
202
+ portfolio_output = gr.Textbox(
203
+ label="Portfolio Analysis Results",
204
+ lines=20,
205
+ show_copy_button=True,
206
+ interactive=False
207
+ )
208
+
209
+ gr.Examples(
210
+ examples=[
211
+ '[{"symbol": "BTC", "amount": 0.5}, {"symbol": "ETH", "amount": 5.0}]',
212
+ '[{"symbol": "BTC", "amount": 1.0}, {"symbol": "ETH", "amount": 10.0}, {"symbol": "SOL", "amount": 100.0}]'
213
+ ],
214
+ inputs=portfolio_input
215
+ )
216
+
217
+ with gr.Tab("🏦 DeFi Analytics"):
218
+ defi_btn = gr.Button("Get DeFi Ecosystem Analysis", variant="primary", size="lg")
219
+ defi_output = gr.Textbox(
220
+ label="DeFi Analysis Results",
221
+ lines=25,
222
+ show_copy_button=True,
223
+ interactive=False,
224
+ info="Comprehensive DeFi protocol analysis with TVL data and yield opportunities"
225
+ )
226
+
227
+ with gr.Tab("ℹ️ About"):
228
+ gr.Markdown("""
229
+ ## 🚀 Enhanced Features
230
+ - **Multi-API Integration**: CoinGecko, CryptoCompare, and DeFiLlama data sources
231
+ - **AI-Powered Analysis**: Google Gemini 2.5 Flash with contextual market intelligence
232
+ - **DeFi Analytics**: Protocol TVL analysis, yield farming opportunities, and ecosystem insights
233
+ - **Portfolio Analysis**: Risk assessment, diversification scoring, and personalized recommendations
234
+ - **News Integration**: Real-time crypto news aggregation and sentiment analysis
235
+ - **Interactive Charts**: Advanced price visualizations with technical indicators
236
+ - **Smart Caching**: Optimized performance with intelligent data caching (TTL-based)
237
+ - **Rate Limiting**: Respectful API usage with automatic throttling
238
+
239
+ ## 🎯 Core Capabilities
240
+ 1. **Enhanced Research Chat**: Context-aware conversations with real-time market data integration
241
+ 2. **Advanced Price Charts**: Interactive visualizations with 30-day historical data
242
+ 3. **Portfolio Optimization**: Comprehensive portfolio analysis with risk metrics and recommendations
243
+ 4. **DeFi Intelligence**: Protocol rankings, TVL trends, and high-yield opportunity identification
244
+ 5. **Market Intelligence**: Global market metrics, trending assets, and breaking news analysis
245
+
246
+ ## 💡 Query Examples
247
+ - "Analyze Bitcoin vs Ethereum DeFi ecosystem performance"
248
+ - "What are the top DeFi protocols by TVL with lowest risk?"
249
+ - "Show me high-yield farming opportunities under 15% volatility"
250
+ - "Compare my portfolio risk to market benchmarks"
251
+ - "Latest crypto news impact on altcoin market sentiment"
252
+ - "Which Layer 1 protocols have strongest DeFi adoption?"
253
+
254
+ ## 🔧 Technical Architecture
255
+ - **Async Processing**: Non-blocking operations for optimal performance
256
+ - **Error Handling**: Comprehensive exception management with graceful degradation
257
+ - **Symbol Mapping**: Intelligent cryptocurrency identifier resolution
258
+ - **Data Validation**: Input sanitization and response formatting
259
+ """)
260
+
261
+ submit_btn.click(
262
+ research_query_sync,
263
+ inputs=[query_input, chatbot],
264
+ outputs=chatbot
265
+ ).then(lambda: "", outputs=query_input)
266
+
267
+ query_input.submit(
268
+ research_query_sync,
269
+ inputs=[query_input, chatbot],
270
+ outputs=chatbot
271
+ ).then(lambda: "", outputs=query_input)
272
+
273
+ chart_btn.click(
274
+ get_price_chart_sync,
275
+ inputs=symbol_input,
276
+ outputs=chart_output
277
+ )
278
+
279
+ symbol_input.submit(
280
+ get_price_chart_sync,
281
+ inputs=symbol_input,
282
+ outputs=chart_output
283
+ )
284
+
285
+ market_btn.click(
286
+ get_market_data_sync,
287
+ outputs=market_output
288
+ )
289
+
290
+ clear_btn.click(
291
+ clear_cache,
292
+ outputs=clear_output
293
+ )
294
+
295
+ portfolio_btn.click(
296
+ analyze_portfolio_sync,
297
+ inputs=portfolio_input,
298
+ outputs=portfolio_output
299
+ )
300
+
301
+ defi_btn.click(
302
+ get_defi_analysis_sync,
303
+ outputs=defi_output
304
+ )
305
+
306
+ if __name__ == "__main__":
307
+ app.launch(
308
+ server_name="0.0.0.0",
309
+ server_port=5000,
310
+ share=False,
311
+ show_error=True
312
+ )
attached_assets/Pasted--Complete-Web3-Research-Co-Pilot-Project-Plan-Structure-Project-Directory-Structure--1754811430335_1754811430338.txt ADDED
@@ -0,0 +1,992 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Complete Web3 Research Co-Pilot Project Plan & Structure
2
+
3
+ ## 🏗️ **Project Directory Structure**
4
+
5
+ ```
6
+ web3-research-copilot/
7
+ ├── README.md
8
+ ├── requirements.txt
9
+ ├── app.py # Main Gradio application
10
+ ├── .env.example # Environment variables template
11
+ ├── .gitignore
12
+ ├── Dockerfile # For HF Spaces deployment
13
+ ├──
14
+ ├── src/
15
+ │ ├── __init__.py
16
+ │ ├── agent/
17
+ │ │ ├── __init__.py
18
+ │ │ ├── research_agent.py # Main LangChain agent
19
+ │ │ ├── query_planner.py # Multi-step query breakdown
20
+ │ │ ├── memory_manager.py # Conversation memory
21
+ │ │ └── response_formatter.py # Output formatting
22
+ │ │
23
+ │ ├── tools/
24
+ │ │ ├── __init__.py
25
+ │ │ ├── base_tool.py # Abstract base tool class
26
+ │ │ ├── coingecko_tool.py # CoinGecko API integration
27
+ │ │ ├── defillama_tool.py # DeFiLlama API integration
28
+ │ │ ├── etherscan_tool.py # Etherscan API integration
29
+ │ │ ├── cryptocompare_tool.py # CryptoCompare API integration
30
+ │ │ └── social_tool.py # Social media data (Twitter API)
31
+ │ │
32
+ │ ├── data/
33
+ │ │ ├── __init__.py
34
+ │ │ ├── processors/
35
+ │ │ │ ├── __init__.py
36
+ │ │ │ ├── price_processor.py
37
+ │ │ │ ├── volume_processor.py
38
+ │ │ │ └── social_processor.py
39
+ │ │ ├── cache/
40
+ │ │ │ ├── __init__.py
41
+ │ │ │ └── redis_cache.py # Simple in-memory cache
42
+ │ │ └── validators/
43
+ │ │ ├── __init__.py
44
+ │ │ └── data_validator.py
45
+ │ │
46
+ │ ├── ui/
47
+ │ │ ├── __init__.py
48
+ │ │ ├── gradio_interface.py # Main UI components
49
+ │ │ ├── components/
50
+ │ │ │ ├── __init__.py
51
+ │ │ │ ├── chat_component.py
52
+ │ │ │ ├── chart_component.py
53
+ │ │ │ └── table_component.py
54
+ │ │ └── styles/
55
+ │ │ ├── custom.css
56
+ │ │ └── theme.py
57
+ │ │
58
+ │ ├── api/
59
+ │ │ ├── __init__.py
60
+ │ │ ├── airaa_integration.py # AIRAA-specific API endpoints
61
+ │ │ ├── webhook_handler.py # For AIRAA integration
62
+ │ │ └── rate_limiter.py # API rate limiting
63
+ │ │
64
+ │ ├── utils/
65
+ │ │ ├── __init__.py
66
+ │ │ ├── logger.py # Logging configuration
67
+ │ │ ├── config.py # Configuration management
68
+ │ │ ├── exceptions.py # Custom exceptions
69
+ │ │ └── helpers.py # Utility functions
70
+ │ │
71
+ │ └── visualizations/
72
+ │ ├── __init__.py
73
+ │ ├── plotly_charts.py # Interactive charts
74
+ │ ├── tables.py # Data tables
75
+ │ └── export_utils.py # Data export functions
76
+
77
+ ├── tests/
78
+ │ ├── __init__.py
79
+ │ ├── test_agent/
80
+ │ │ ├── test_research_agent.py
81
+ │ │ └── test_query_planner.py
82
+ │ ├── test_tools/
83
+ │ │ ├── test_coingecko.py
84
+ │ │ └── test_defillama.py
85
+ │ └── test_integration/
86
+ │ └── test_airaa_integration.py
87
+
88
+ ├── docs/
89
+ │ ├── API.md # API documentation
90
+ │ ├── DEPLOYMENT.md # Deployment guide
91
+ │ ├── AIRAA_INTEGRATION.md # AIRAA integration guide
92
+ │ └── USER_GUIDE.md # User documentation
93
+
94
+ ├── examples/
95
+ │ ├── sample_queries.py # Example research queries
96
+ │ ├── api_examples.py # API usage examples
97
+ │ └── airaa_webhook_example.py # AIRAA integration example
98
+
99
+ └── deployment/
100
+ ├── docker-compose.yml
101
+ ├── nginx.conf
102
+ └── huggingface_spaces/
103
+ ├── spaces_config.yml
104
+ └── deployment_script.sh
105
+ ```
106
+
107
+ ## 📋 **Detailed Implementation Plan**
108
+
109
+ ### **Phase 1: Foundation Setup (Days 1-2)**
110
+
111
+ #### Day 1: Project Structure & Core Setup
112
+ ```bash
113
+ # Setup commands
114
+ mkdir web3-research-copilot
115
+ cd web3-research-copilot
116
+ python -m venv venv
117
+ source venv/bin/activate # On Windows: venv\Scripts\activate
118
+ pip install --upgrade pip
119
+ ```
120
+
121
+ **Key Files to Create:**
122
+
123
+ **requirements.txt**
124
+ ```txt
125
+ # Core AI/ML
126
+ langchain==0.1.0
127
+ langchain-google-genai==1.0.0
128
+ langchain-community==0.0.20
129
+
130
+ # Web Framework
131
+ gradio==4.15.0
132
+ fastapi==0.108.0
133
+ uvicorn==0.25.0
134
+
135
+ # Data Processing
136
+ pandas==2.1.4
137
+ numpy==1.24.3
138
+ requests==2.31.0
139
+ python-dotenv==1.0.0
140
+
141
+ # Visualization
142
+ plotly==5.17.0
143
+ matplotlib==3.8.2
144
+
145
+ # Utilities
146
+ pydantic==2.5.2
147
+ python-dateutil==2.8.2
148
+ tenacity==8.2.3
149
+
150
+ # Caching & Performance
151
+ diskcache==5.6.3
152
+ asyncio-throttle==1.0.2
153
+
154
+ # Testing
155
+ pytest==7.4.3
156
+ pytest-asyncio==0.21.1
157
+ ```
158
+
159
+ **src/utils/config.py**
160
+ ```python
161
+ import os
162
+ from dotenv import load_dotenv
163
+ from dataclasses import dataclass
164
+ from typing import Optional
165
+
166
+ load_dotenv()
167
+
168
+ @dataclass
169
+ class Config:
170
+ # AI Configuration
171
+ GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY")
172
+ GEMINI_MODEL: str = "gemini-pro"
173
+
174
+ # API Keys
175
+ COINGECKO_API_KEY: Optional[str] = os.getenv("COINGECKO_API_KEY")
176
+ ETHERSCAN_API_KEY: str = os.getenv("ETHERSCAN_API_KEY")
177
+ CRYPTOCOMPARE_API_KEY: Optional[str] = os.getenv("CRYPTOCOMPARE_API_KEY")
178
+
179
+ # Rate Limits (per minute)
180
+ COINGECKO_RATE_LIMIT: int = 10
181
+ ETHERSCAN_RATE_LIMIT: int = 5
182
+ GEMINI_RATE_LIMIT: int = 15
183
+
184
+ # Cache Configuration
185
+ CACHE_TTL: int = 300 # 5 minutes
186
+ CACHE_SIZE: int = 100
187
+
188
+ # UI Configuration
189
+ UI_TITLE: str = "Web3 Research Co-Pilot"
190
+ UI_DESCRIPTION: str = "AI-powered crypto research assistant"
191
+
192
+ # AIRAA Integration
193
+ AIRAA_WEBHOOK_URL: Optional[str] = os.getenv("AIRAA_WEBHOOK_URL")
194
+ AIRAA_API_KEY: Optional[str] = os.getenv("AIRAA_API_KEY")
195
+
196
+ # Logging
197
+ LOG_LEVEL: str = "INFO"
198
+ LOG_FILE: str = "app.log"
199
+
200
+ config = Config()
201
+ ```
202
+
203
+ #### Day 2: Base Tool Architecture
204
+
205
+ **src/tools/base_tool.py**
206
+ ```python
207
+ from abc import ABC, abstractmethod
208
+ from typing import Dict, Any, Optional
209
+ from langchain.tools import BaseTool
210
+ from pydantic import BaseModel, Field
211
+ import asyncio
212
+ from tenacity import retry, stop_after_attempt, wait_exponential
213
+ from src.utils.logger import get_logger
214
+
215
+ logger = get_logger(__name__)
216
+
217
+ class Web3ToolInput(BaseModel):
218
+ query: str = Field(description="The search query or parameter")
219
+ filters: Optional[Dict[str, Any]] = Field(default=None, description="Additional filters")
220
+
221
+ class BaseWeb3Tool(BaseTool, ABC):
222
+ """Base class for all Web3 data tools"""
223
+
224
+ name: str = "base_web3_tool"
225
+ description: str = "Base Web3 tool"
226
+ args_schema = Web3ToolInput
227
+
228
+ def __init__(self, **kwargs):
229
+ super().__init__(**kwargs)
230
+ self.rate_limiter = self._setup_rate_limiter()
231
+
232
+ @abstractmethod
233
+ def _setup_rate_limiter(self):
234
+ """Setup rate limiting for the specific API"""
235
+ pass
236
+
237
+ @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
238
+ async def _make_api_call(self, url: str, params: Dict[str, Any]) -> Dict[str, Any]:
239
+ """Make rate-limited API call with retry logic"""
240
+ await self.rate_limiter.acquire()
241
+ # Implementation will be in specific tools
242
+ pass
243
+
244
+ @abstractmethod
245
+ def _process_response(self, response: Dict[str, Any]) -> str:
246
+ """Process API response into readable format"""
247
+ pass
248
+
249
+ def _run(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
250
+ """Synchronous tool execution"""
251
+ return asyncio.run(self._arun(query, filters))
252
+
253
+ @abstractmethod
254
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
255
+ """Asynchronous tool execution"""
256
+ pass
257
+ ```
258
+
259
+ ### **Phase 2: Data Tools Implementation (Days 3-4)**
260
+
261
+ #### CoinGecko Tool
262
+ **src/tools/coingecko_tool.py**
263
+ ```python
264
+ import aiohttp
265
+ from typing import Dict, Any, Optional
266
+ from asyncio_throttle import Throttler
267
+ from src.tools.base_tool import BaseWeb3Tool, Web3ToolInput
268
+ from src.utils.config import config
269
+
270
+ class CoinGeckoTool(BaseWeb3Tool):
271
+ name = "coingecko_price_data"
272
+ description = """
273
+ Get cryptocurrency price, volume, market cap and trend data from CoinGecko.
274
+ Useful for: price analysis, market cap rankings, volume trends, price changes.
275
+ Input should be a cryptocurrency name or symbol (e.g., 'bitcoin', 'ethereum', 'BTC').
276
+ """
277
+
278
+ def _setup_rate_limiter(self):
279
+ return Throttler(rate_limit=config.COINGECKO_RATE_LIMIT, period=60)
280
+
281
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
282
+ try:
283
+ # Clean and format the query
284
+ coin_id = self._format_coin_id(query)
285
+
286
+ # API endpoints
287
+ base_url = "https://api.coingecko.com/api/v3"
288
+
289
+ if filters and filters.get("type") == "trending":
290
+ url = f"{base_url}/search/trending"
291
+ params = {}
292
+ elif filters and filters.get("type") == "market_data":
293
+ url = f"{base_url}/coins/{coin_id}"
294
+ params = {"localization": "false", "tickers": "false", "community_data": "false"}
295
+ else:
296
+ url = f"{base_url}/simple/price"
297
+ params = {
298
+ "ids": coin_id,
299
+ "vs_currencies": "usd",
300
+ "include_24hr_change": "true",
301
+ "include_24hr_vol": "true",
302
+ "include_market_cap": "true"
303
+ }
304
+
305
+ async with aiohttp.ClientSession() as session:
306
+ await self.rate_limiter.acquire()
307
+ async with session.get(url, params=params) as response:
308
+ if response.status == 200:
309
+ data = await response.json()
310
+ return self._process_response(data, filters)
311
+ else:
312
+ return f"Error fetching data: HTTP {response.status}"
313
+
314
+ except Exception as e:
315
+ return f"Error in CoinGecko tool: {str(e)}"
316
+
317
+ def _format_coin_id(self, query: str) -> str:
318
+ """Convert common symbols to CoinGecko IDs"""
319
+ symbol_map = {
320
+ "btc": "bitcoin",
321
+ "eth": "ethereum",
322
+ "usdc": "usd-coin",
323
+ "usdt": "tether",
324
+ "bnb": "binancecoin"
325
+ }
326
+ return symbol_map.get(query.lower(), query.lower())
327
+
328
+ def _process_response(self, data: Dict[str, Any], filters: Optional[Dict[str, Any]] = None) -> str:
329
+ """Format CoinGecko response into readable text"""
330
+ if not data:
331
+ return "No data found"
332
+
333
+ if filters and filters.get("type") == "trending":
334
+ trending = data.get("coins", [])[:5]
335
+ result = "🔥 Trending Cryptocurrencies:\n"
336
+ for i, coin in enumerate(trending, 1):
337
+ name = coin.get("item", {}).get("name", "Unknown")
338
+ symbol = coin.get("item", {}).get("symbol", "")
339
+ result += f"{i}. {name} ({symbol.upper()})\n"
340
+ return result
341
+
342
+ elif filters and filters.get("type") == "market_data":
343
+ name = data.get("name", "Unknown")
344
+ symbol = data.get("symbol", "").upper()
345
+ current_price = data.get("market_data", {}).get("current_price", {}).get("usd", 0)
346
+ market_cap = data.get("market_data", {}).get("market_cap", {}).get("usd", 0)
347
+ volume_24h = data.get("market_data", {}).get("total_volume", {}).get("usd", 0)
348
+ price_change_24h = data.get("market_data", {}).get("price_change_percentage_24h", 0)
349
+
350
+ result = f"📊 {name} ({symbol}) Market Data:\n"
351
+ result += f"💰 Price: ${current_price:,.2f}\n"
352
+ result += f"📈 24h Change: {price_change_24h:+.2f}%\n"
353
+ result += f"🏦 Market Cap: ${market_cap:,.0f}\n"
354
+ result += f"📊 24h Volume: ${volume_24h:,.0f}\n"
355
+ return result
356
+
357
+ else:
358
+ # Simple price data
359
+ result = "💰 Price Data:\n"
360
+ for coin_id, coin_data in data.items():
361
+ price = coin_data.get("usd", 0)
362
+ change_24h = coin_data.get("usd_24h_change", 0)
363
+ volume_24h = coin_data.get("usd_24h_vol", 0)
364
+ market_cap = coin_data.get("usd_market_cap", 0)
365
+
366
+ result += f"🪙 {coin_id.title()}:\n"
367
+ result += f" 💵 Price: ${price:,.2f}\n"
368
+ result += f" 📈 24h Change: {change_24h:+.2f}%\n"
369
+ result += f" 📊 Volume: ${volume_24h:,.0f}\n"
370
+ result += f" 🏦 Market Cap: ${market_cap:,.0f}\n"
371
+
372
+ return result
373
+ ```
374
+
375
+ ### **Phase 3: LangChain Agent Implementation (Days 5-6)**
376
+
377
+ #### Main Research Agent
378
+ **src/agent/research_agent.py**
379
+ ```python
380
+ from langchain.agents import AgentExecutor, create_openai_tools_agent
381
+ from langchain_google_genai import ChatGoogleGenerativeAI
382
+ from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
383
+ from langchain.memory import ConversationBufferWindowMemory
384
+ from typing import List, Dict, Any
385
+ import asyncio
386
+
387
+ from src.tools.coingecko_tool import CoinGeckoTool
388
+ from src.tools.defillama_tool import DeFiLlamaTool
389
+ from src.tools.etherscan_tool import EtherscanTool
390
+ from src.agent.query_planner import QueryPlanner
391
+ from src.utils.config import config
392
+ from src.utils.logger import get_logger
393
+
394
+ logger = get_logger(__name__)
395
+
396
+ class Web3ResearchAgent:
397
+ def __init__(self):
398
+ self.llm = ChatGoogleGenerativeAI(
399
+ model=config.GEMINI_MODEL,
400
+ google_api_key=config.GEMINI_API_KEY,
401
+ temperature=0.1,
402
+ max_tokens=2048
403
+ )
404
+
405
+ self.tools = self._setup_tools()
406
+ self.query_planner = QueryPlanner(self.llm)
407
+ self.memory = ConversationBufferWindowMemory(
408
+ memory_key="chat_history",
409
+ return_messages=True,
410
+ k=10
411
+ )
412
+
413
+ self.agent = self._create_agent()
414
+ self.agent_executor = AgentExecutor(
415
+ agent=self.agent,
416
+ tools=self.tools,
417
+ memory=self.memory,
418
+ verbose=True,
419
+ max_iterations=5,
420
+ handle_parsing_errors=True
421
+ )
422
+
423
+ def _setup_tools(self) -> List:
424
+ """Initialize all available tools"""
425
+ return [
426
+ CoinGeckoTool(),
427
+ DeFiLlamaTool(),
428
+ EtherscanTool(),
429
+ ]
430
+
431
+ def _create_agent(self):
432
+ """Create the LangChain agent with custom prompt"""
433
+ system_prompt = """
434
+ You are an expert Web3 research assistant. Your job is to help users analyze cryptocurrency markets,
435
+ DeFi protocols, and blockchain data by using available tools to gather accurate information.
436
+
437
+ Available tools:
438
+ - CoinGecko: Get price, volume, market cap data for cryptocurrencies
439
+ - DeFiLlama: Get DeFi protocol data, TVL, yields information
440
+ - Etherscan: Get on-chain transaction and address data
441
+
442
+ Guidelines:
443
+ 1. Break down complex queries into specific, actionable steps
444
+ 2. Use multiple tools when needed to provide comprehensive analysis
445
+ 3. Always cite your data sources in responses
446
+ 4. Format responses with clear headers, bullet points, and emojis for readability
447
+ 5. If data is unavailable, suggest alternative approaches or mention limitations
448
+ 6. Provide context and explanations for technical terms
449
+ 7. Include relevant charts or visualizations when possible
450
+
451
+ When responding:
452
+ - Start with a brief summary
453
+ - Present data in organized sections
454
+ - Include methodology notes
455
+ - End with key insights or actionable conclusions
456
+ """
457
+
458
+ prompt = ChatPromptTemplate.from_messages([
459
+ ("system", system_prompt),
460
+ MessagesPlaceholder("chat_history"),
461
+ ("human", "{input}"),
462
+ MessagesPlaceholder("agent_scratchpad")
463
+ ])
464
+
465
+ return create_openai_tools_agent(
466
+ llm=self.llm,
467
+ tools=self.tools,
468
+ prompt=prompt
469
+ )
470
+
471
+ async def research_query(self, query: str) -> Dict[str, Any]:
472
+ """Main entry point for research queries"""
473
+ try:
474
+ logger.info(f"Processing query: {query}")
475
+
476
+ # Plan the research approach
477
+ research_plan = await self.query_planner.plan_research(query)
478
+ logger.info(f"Research plan: {research_plan}")
479
+
480
+ # Execute the research
481
+ result = await self._execute_research(query, research_plan)
482
+
483
+ # Format and return response
484
+ return {
485
+ "success": True,
486
+ "query": query,
487
+ "research_plan": research_plan,
488
+ "result": result,
489
+ "sources": self._extract_sources(result),
490
+ "metadata": {
491
+ "tools_used": [tool.name for tool in self.tools],
492
+ "timestamp": self._get_timestamp()
493
+ }
494
+ }
495
+
496
+ except Exception as e:
497
+ logger.error(f"Error processing query: {str(e)}")
498
+ return {
499
+ "success": False,
500
+ "query": query,
501
+ "error": str(e),
502
+ "metadata": {"timestamp": self._get_timestamp()}
503
+ }
504
+
505
+ async def _execute_research(self, query: str, plan: Dict[str, Any]) -> str:
506
+ """Execute the research plan using LangChain agent"""
507
+ try:
508
+ # Add planning context to the query
509
+ enhanced_query = f"""
510
+ Research Query: {query}
511
+
512
+ Research Plan: {plan.get('steps', [])}
513
+ Priority Focus: {plan.get('priority', 'general analysis')}
514
+
515
+ Please execute this research systematically and provide a comprehensive analysis.
516
+ """
517
+
518
+ response = await asyncio.to_thread(
519
+ self.agent_executor.invoke,
520
+ {"input": enhanced_query}
521
+ )
522
+
523
+ return response.get("output", "No response generated")
524
+
525
+ except Exception as e:
526
+ logger.error(f"Error executing research: {str(e)}")
527
+ return f"Research execution error: {str(e)}"
528
+
529
+ def _extract_sources(self, result: str) -> List[str]:
530
+ """Extract data sources from the result"""
531
+ sources = []
532
+ if "CoinGecko" in result:
533
+ sources.append("CoinGecko API")
534
+ if "DeFiLlama" in result:
535
+ sources.append("DeFiLlama API")
536
+ if "Etherscan" in result:
537
+ sources.append("Etherscan API")
538
+ return sources
539
+
540
+ def _get_timestamp(self) -> str:
541
+ """Get current timestamp"""
542
+ from datetime import datetime
543
+ return datetime.now().isoformat()
544
+ ```
545
+
546
+ ### **Phase 4: UI & Integration (Days 7-8)**
547
+
548
+ #### Main Gradio Application
549
+ **app.py**
550
+ ```python
551
+ import gradio as gr
552
+ import asyncio
553
+ import json
554
+ from datetime import datetime
555
+ from typing import List, Tuple
556
+
557
+ from src.agent.research_agent import Web3ResearchAgent
558
+ from src.api.airaa_integration import AIRAAIntegration
559
+ from src.visualizations.plotly_charts import create_price_chart, create_volume_chart
560
+ from src.utils.config import config
561
+ from src.utils.logger import get_logger
562
+
563
+ logger = get_logger(__name__)
564
+
565
+ class Web3CoPilotApp:
566
+ def __init__(self):
567
+ self.agent = Web3ResearchAgent()
568
+ self.airaa_integration = AIRAAIntegration()
569
+
570
+ def create_interface(self):
571
+ """Create the main Gradio interface"""
572
+
573
+ # Custom CSS for better styling
574
+ custom_css = """
575
+ .container { max-width: 1200px; margin: 0 auto; }
576
+ .chat-container { height: 600px; }
577
+ .query-box { font-size: 16px; }
578
+ .examples-box { background: #f8f9fa; padding: 15px; border-radius: 8px; }
579
+ """
580
+
581
+ with gr.Blocks(
582
+ title=config.UI_TITLE,
583
+ css=custom_css,
584
+ theme=gr.themes.Soft()
585
+ ) as demo:
586
+
587
+ # Header
588
+ gr.Markdown(f"""
589
+ # 🚀 {config.UI_TITLE}
590
+
591
+ {config.UI_DESCRIPTION}
592
+
593
+ **Powered by**: Gemini AI • CoinGecko • DeFiLlama • Etherscan
594
+ """)
595
+
596
+ with gr.Row():
597
+ with gr.Column(scale=2):
598
+ # Main Chat Interface
599
+ chatbot = gr.Chatbot(
600
+ label="Research Assistant",
601
+ height=600,
602
+ show_label=True,
603
+ container=True,
604
+ elem_classes=["chat-container"]
605
+ )
606
+
607
+ with gr.Row():
608
+ query_input = gr.Textbox(
609
+ placeholder="Ask me about crypto markets, DeFi protocols, or on-chain data...",
610
+ label="Research Query",
611
+ lines=2,
612
+ elem_classes=["query-box"]
613
+ )
614
+ submit_btn = gr.Button("🔍 Research", variant="primary")
615
+
616
+ # Quick action buttons
617
+ with gr.Row():
618
+ clear_btn = gr.Button("🗑️ Clear", variant="secondary")
619
+ export_btn = gr.Button("📊 Export", variant="secondary")
620
+
621
+ with gr.Column(scale=1):
622
+ # Example queries sidebar
623
+ gr.Markdown("### 💡 Example Queries")
624
+
625
+ examples = [
626
+ "What's the current price and 24h change for Bitcoin?",
627
+ "Show me top DeFi protocols by TVL",
628
+ "Which tokens had highest volume yesterday?",
629
+ "Compare Ethereum vs Solana market metrics",
630
+ "What are the trending cryptocurrencies today?"
631
+ ]
632
+
633
+ for example in examples:
634
+ example_btn = gr.Button(example, size="sm")
635
+ example_btn.click(
636
+ lambda x=example: x,
637
+ outputs=query_input
638
+ )
639
+
640
+ # Data visualization area
641
+ gr.Markdown("### 📈 Visualizations")
642
+ chart_output = gr.Plot(label="Charts")
643
+
644
+ # Export options
645
+ gr.Markdown("### 📤 Export Options")
646
+ export_format = gr.Radio(
647
+ choices=["JSON", "CSV", "PDF"],
648
+ value="JSON",
649
+ label="Format"
650
+ )
651
+
652
+ # Chat functionality
653
+ def respond(message: str, history: List[Tuple[str, str]]):
654
+ """Process user message and generate response"""
655
+ if not message.strip():
656
+ return history, ""
657
+
658
+ try:
659
+ # Show loading message
660
+ history.append((message, "🔍 Researching... Please wait."))
661
+ yield history, ""
662
+
663
+ # Process the query
664
+ result = asyncio.run(self.agent.research_query(message))
665
+
666
+ if result["success"]:
667
+ response = result["result"]
668
+
669
+ # Add metadata footer
670
+ sources = ", ".join(result["sources"])
671
+ response += f"\n\n---\n📊 **Sources**: {sources}"
672
+ response += f"\n⏰ **Generated**: {datetime.now().strftime('%H:%M:%S')}"
673
+
674
+ # Send to AIRAA if configured
675
+ if config.AIRAA_WEBHOOK_URL:
676
+ asyncio.run(self.airaa_integration.send_research_data(result))
677
+
678
+ else:
679
+ response = f"❌ Error: {result.get('error', 'Unknown error occurred')}"
680
+
681
+ # Update history
682
+ history[-1] = (message, response)
683
+
684
+ except Exception as e:
685
+ logger.error(f"Error in respond: {str(e)}")
686
+ history[-1] = (message, f"❌ System error: {str(e)}")
687
+
688
+ yield history, ""
689
+
690
+ def clear_chat():
691
+ """Clear chat history"""
692
+ return [], ""
693
+
694
+ def export_conversation(history: List[Tuple[str, str]], format_type: str):
695
+ """Export conversation in selected format"""
696
+ try:
697
+ if format_type == "JSON":
698
+ data = {
699
+ "conversation": [{"query": q, "response": r} for q, r in history],
700
+ "exported_at": datetime.now().isoformat()
701
+ }
702
+ return json.dumps(data, indent=2)
703
+
704
+ elif format_type == "CSV":
705
+ import csv
706
+ import io
707
+ output = io.StringIO()
708
+ writer = csv.writer(output)
709
+ writer.writerow(["Query", "Response", "Timestamp"])
710
+ for q, r in history:
711
+ writer.writerow([q, r, datetime.now().isoformat()])
712
+ return output.getvalue()
713
+
714
+ else: # PDF
715
+ return "PDF export not implemented yet"
716
+
717
+ except Exception as e:
718
+ return f"Export error: {str(e)}"
719
+
720
+ # Event handlers
721
+ submit_btn.click(
722
+ respond,
723
+ inputs=[query_input, chatbot],
724
+ outputs=[chatbot, query_input]
725
+ )
726
+
727
+ query_input.submit(
728
+ respond,
729
+ inputs=[query_input, chatbot],
730
+ outputs=[chatbot, query_input]
731
+ )
732
+
733
+ clear_btn.click(
734
+ clear_chat,
735
+ outputs=[chatbot, query_input]
736
+ )
737
+
738
+ export_btn.click(
739
+ export_conversation,
740
+ inputs=[chatbot, export_format],
741
+ outputs=gr.Textbox(label="Exported Data")
742
+ )
743
+
744
+ return demo
745
+
746
+ if __name__ == "__main__":
747
+ app = Web3CoPilotApp()
748
+ interface = app.create_interface()
749
+
750
+ interface.launch(
751
+ server_name="0.0.0.0",
752
+ server_port=7860,
753
+ share=True,
754
+ show_api=True
755
+ )
756
+ ```
757
+
758
+ ### **Phase 5: AIRAA Integration & Deployment**
759
+
760
+ #### AIRAA Integration Module
761
+ **src/api/airaa_integration.py**
762
+ ```python
763
+ import aiohttp
764
+ import json
765
+ from typing import Dict, Any, Optional
766
+ from src.utils.config import config
767
+ from src.utils.logger import get_logger
768
+
769
+ logger = get_logger(__name__)
770
+
771
+ class AIRAAIntegration:
772
+ """Handle integration with AIRAA platform"""
773
+
774
+ def __init__(self):
775
+ self.webhook_url = config.AIRAA_WEBHOOK_URL
776
+ self.api_key = config.AIRAA_API_KEY
777
+ self.enabled = bool(self.webhook_url)
778
+
779
+ async def send_research_data(self, research_result: Dict[str, Any]) -> bool:
780
+ """Send research data to AIRAA webhook"""
781
+ if not self.enabled:
782
+ logger.info("AIRAA integration not configured, skipping")
783
+ return False
784
+
785
+ try:
786
+ payload = self._format_for_airaa(research_result)
787
+
788
+ headers = {
789
+ "Content-Type": "application/json",
790
+ "User-Agent": "Web3-Research-Copilot/1.0"
791
+ }
792
+
793
+ if self.api_key:
794
+ headers["Authorization"] = f"Bearer {self.api_key}"
795
+
796
+ async with aiohttp.ClientSession() as session:
797
+ async with session.post(
798
+ self.webhook_url,
799
+ json=payload,
800
+ headers=headers,
801
+ timeout=aiohttp.ClientTimeout(total=30)
802
+ ) as response:
803
+
804
+ if response.status == 200:
805
+ logger.info("Successfully sent data to AIRAA")
806
+ return True
807
+ else:
808
+ logger.warning(f"AIRAA webhook returned {response.status}")
809
+ return False
810
+
811
+ except Exception as e:
812
+ logger.error(f"Failed to send data to AIRAA: {str(e)}")
813
+ return False
814
+
815
+ def _format_for_airaa(self, research_result: Dict[str, Any]) -> Dict[str, Any]:
816
+ """Format research result for AIRAA consumption"""
817
+ return {
818
+ "source": "web3-research-copilot",
819
+ "timestamp": research_result["metadata"]["timestamp"],
820
+ "query": research_result["query"],
821
+ "research_plan": research_result.get("research_plan"),
822
+ "findings": research_result["result"],
823
+ "data_sources": research_result["sources"],
824
+ "confidence_score": self._calculate_confidence(research_result),
825
+ "tags": self._extract_tags(research_result["query"]),
826
+ "structured_data": self._extract_structured_data(research_result["result"])
827
+ }
828
+
829
+ def _calculate_confidence(self, result: Dict[str, Any]) -> float:
830
+ """Calculate confidence score based on data sources and completeness"""
831
+ base_score = 0.7
832
+
833
+ # Boost for multiple sources
834
+ source_count = len(result.get("sources", []))
835
+ source_boost = min(source_count * 0.1, 0.3)
836
+
837
+ # Reduce for errors
838
+ error_penalty = 0.3 if not result.get("success", True) else 0
839
+
840
+ return max(0.0, min(1.0, base_score + source_boost - error_penalty))
841
+
842
+ def _extract_tags(self, query: str) -> List[str]:
843
+ """Extract relevant tags from query"""
844
+ tags = []
845
+ query_lower = query.lower()
846
+
847
+ # Asset tags
848
+ if any(word in query_lower for word in ["bitcoin", "btc"]):
849
+ tags.append("bitcoin")
850
+ if any(word in query_lower for word in ["ethereum", "eth"]):
851
+ tags.append("ethereum")
852
+
853
+ # Category tags
854
+ if any(word in query_lower for word in ["defi", "defillama"]):
855
+ tags.append("defi")
856
+ if any(word in query_lower for word in ["price", "market"]):
857
+ tags.append("market-analysis")
858
+ if any(word in query_lower for word in ["volume", "trading"]):
859
+ tags.append("trading-volume")
860
+
861
+ return tags
862
+
863
+ def _extract_structured_data(self, result_text: str) -> Dict[str, Any]:
864
+ """Extract structured data from result text"""
865
+ structured = {}
866
+
867
+ # Extract price data (simple regex matching)
868
+ import re
869
+
870
+ price_matches = re.findall(r'\$([0-9,]+\.?[0-9]*)', result_text)
871
+ if price_matches:
872
+ structured["prices"] = [float(p.replace(',', '')) for p in price_matches[:5]]
873
+
874
+ percentage_matches = re.findall(r'([+-]?[0-9]+\.?[0-9]*)%', result_text)
875
+ if percentage_matches:
876
+ structured["percentages"] = [float(p) for p in percentage_matches[:5]]
877
+
878
+ return structured
879
+ ```
880
+
881
+ ## 🚀 **Deployment Configuration**
882
+
883
+ ### **Hugging Face Spaces Configuration**
884
+
885
+ **deployment/huggingface_spaces/spaces_config.yml**
886
+ ```yaml
887
+ title: "Web3 Research Co-Pilot"
888
+ emoji: "🚀"
889
+ colorFrom: "blue"
890
+ colorTo: "purple"
891
+ sdk: "gradio"
892
+ sdk_version: "4.15.0"
893
+ app_file: "app.py"
894
+ pinned: false
895
+ license: "mit"
896
+
897
+ # Environment variables needed
898
+ secrets:
899
+ - GEMINI_API_KEY
900
+ - ETHERSCAN_API_KEY
901
+ - AIRAA_WEBHOOK_URL
902
+ - AIRAA_API_KEY
903
+
904
+ # Resource requirements
905
+ hardware: "cpu-basic" # Free tier
906
+ ```
907
+
908
+ ### **Docker Configuration**
909
+ **Dockerfile**
910
+ ```dockerfile
911
+ FROM python:3.11-slim
912
+
913
+ WORKDIR /app
914
+
915
+ # Install system dependencies
916
+ RUN apt-get update && apt-get install -y \
917
+ git \
918
+ && rm -rf /var/lib/apt/lists/*
919
+
920
+ # Copy requirements and install Python dependencies
921
+ COPY requirements.txt .
922
+ RUN pip install --no-cache-dir -r requirements.txt
923
+
924
+ # Copy application code
925
+ COPY . .
926
+
927
+ # Expose port
928
+ EXPOSE 7860
929
+
930
+ # Set environment variables
931
+ ENV PYTHONPATH=/app
932
+ ENV GRADIO_SERVER_NAME=0.0.0.0
933
+ ENV GRADIO_SERVER_PORT=7860
934
+
935
+ # Run the application
936
+ CMD ["python", "app.py"]
937
+ ```
938
+
939
+ ## 📊 **Testing Strategy**
940
+
941
+ ### **Unit Tests**
942
+ ```python
943
+ # tests/test_tools/test_coingecko.py
944
+ import pytest
945
+ import asyncio
946
+ from src.tools.coingecko_tool import CoinGeckoTool
947
+
948
+ @pytest.fixture
949
+ def coingecko_tool():
950
+ return CoinGeckoTool()
951
+
952
+ @pytest.mark.asyncio
953
+ async def test_bitcoin_price_fetch(coingecko_tool):
954
+ result = await coingecko_tool._arun("bitcoin")
955
+ assert "Price:" in result
956
+ assert "bitcoin" in result.lower()
957
+
958
+ @pytest.mark.asyncio
959
+ async def test_trending_coins(coingecko_tool):
960
+ result = await coingecko_tool._arun("trending", {"type": "trending"})
961
+ assert "Trending" in result
962
+ ```
963
+
964
+ ## 📅 **8-Day Development Timeline**
965
+
966
+ ### **Detailed Daily Schedule**
967
+
968
+ **Days 1-2: Foundation**
969
+ - ✅ Project structure setup
970
+ - ✅ Configuration management
971
+ - ✅ Base tool architecture
972
+ - ✅ Logging and utilities
973
+
974
+ **Days 3-4: Core Tools**
975
+ - ✅ CoinGecko integration (price data)
976
+ - ✅ DeFiLlama integration (DeFi data)
977
+ - ✅ Etherscan integration (on-chain data)
978
+ - ✅ Rate limiting and error handling
979
+
980
+ **Days 5-6: AI Agent**
981
+ - ✅ LangChain agent setup
982
+ - ✅ Query planning logic
983
+ - ✅ Memory management
984
+ - ✅ Response formatting
985
+
986
+ **Days 7-8: UI & Deployment**
987
+ - ✅ Gradio interface
988
+ - ✅ AIRAA integration
989
+ - ✅ HuggingFace Spaces deployment
990
+ - ✅ Testing and documentation
991
+
992
+ This comprehensive plan provides a production-ready Web3 research co-pilot that integrates seamlessly with AIRAA's platform while utilizing only free resources and APIs.
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "repl-nix-workspace"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "aiohttp>=3.12.15",
8
+ "asyncio-throttle>=1.0.2",
9
+ "diskcache>=5.6.3",
10
+ "google-genai>=1.29.0",
11
+ "gradio>=5.42.0",
12
+ "langchain>=0.3.27",
13
+ "langchain-community>=0.3.27",
14
+ "langchain-google-genai>=2.1.9",
15
+ "numpy>=2.3.2",
16
+ "pandas>=2.3.1",
17
+ "plotly>=6.2.0",
18
+ "pydantic>=2.11.7",
19
+ "python-dateutil>=2.9.0.post0",
20
+ "python-dotenv>=1.1.1",
21
+ "streamlit>=1.48.0",
22
+ "tenacity>=9.1.2",
23
+ "trafilatura>=2.0.0",
24
+ ]
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ aiohttp==3.10.11
2
+ gradio==5.8.0
3
+ google-generativeai==0.8.3
4
+ plotly==5.24.1
5
+ pandas==2.2.3
6
+ python-dotenv==1.0.1
7
+ asyncio-throttle==1.0.2
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (147 Bytes). View file
 
src/__pycache__/api_clients.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
src/__pycache__/cache_manager.cpython-311.pyc ADDED
Binary file (3.19 kB). View file
 
src/__pycache__/config.cpython-311.pyc ADDED
Binary file (1.36 kB). View file
 
src/__pycache__/defillama_client.cpython-311.pyc ADDED
Binary file (5.63 kB). View file
 
src/__pycache__/enhanced_agent.cpython-311.pyc ADDED
Binary file (19 kB). View file
 
src/__pycache__/news_aggregator.cpython-311.pyc ADDED
Binary file (6.04 kB). View file
 
src/__pycache__/portfolio_analyzer.cpython-311.pyc ADDED
Binary file (11.8 kB). View file
 
src/__pycache__/research_agent.cpython-311.pyc ADDED
Binary file (12.4 kB). View file
 
src/__pycache__/visualizations.cpython-311.pyc ADDED
Binary file (11.8 kB). View file
 
src/api_clients.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ import time
4
+ from typing import Dict, Any, Optional, List
5
+ from src.config import config
6
+ import json
7
+
8
+ class RateLimiter:
9
+ def __init__(self, delay: float):
10
+ self.delay = delay
11
+ self.last_call = 0
12
+
13
+ async def acquire(self):
14
+ now = time.time()
15
+ elapsed = now - self.last_call
16
+ if elapsed < self.delay:
17
+ await asyncio.sleep(self.delay - elapsed)
18
+ self.last_call = time.time()
19
+
20
+ class CoinGeckoClient:
21
+ def __init__(self):
22
+ self.rate_limiter = RateLimiter(config.RATE_LIMIT_DELAY)
23
+ self.session = None
24
+
25
+ async def get_session(self):
26
+ if self.session is None:
27
+ timeout = aiohttp.ClientTimeout(total=config.REQUEST_TIMEOUT)
28
+ self.session = aiohttp.ClientSession(timeout=timeout)
29
+ return self.session
30
+
31
+ async def _make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
32
+ await self.rate_limiter.acquire()
33
+
34
+ url = f"{config.COINGECKO_BASE_URL}/{endpoint}"
35
+ if params is None:
36
+ params = {}
37
+
38
+ if config.COINGECKO_API_KEY:
39
+ params["x_cg_demo_api_key"] = config.COINGECKO_API_KEY
40
+
41
+ session = await self.get_session()
42
+
43
+ for attempt in range(config.MAX_RETRIES):
44
+ try:
45
+ async with session.get(url, params=params) as response:
46
+ if response.status == 200:
47
+ return await response.json()
48
+ elif response.status == 429:
49
+ await asyncio.sleep(2 ** attempt)
50
+ continue
51
+ else:
52
+ raise Exception(f"API error: {response.status}")
53
+ except asyncio.TimeoutError:
54
+ if attempt == config.MAX_RETRIES - 1:
55
+ raise Exception("Request timeout")
56
+ await asyncio.sleep(1)
57
+
58
+ raise Exception("Max retries exceeded")
59
+
60
+ async def get_price(self, coin_ids: str, vs_currencies: str = "usd") -> Dict[str, Any]:
61
+ params = {
62
+ "ids": coin_ids,
63
+ "vs_currencies": vs_currencies,
64
+ "include_24hr_change": "true",
65
+ "include_24hr_vol": "true",
66
+ "include_market_cap": "true"
67
+ }
68
+ return await self._make_request("simple/price", params)
69
+
70
+ async def get_trending(self) -> Dict[str, Any]:
71
+ return await self._make_request("search/trending")
72
+
73
+ async def get_global_data(self) -> Dict[str, Any]:
74
+ return await self._make_request("global")
75
+
76
+ async def get_coin_data(self, coin_id: str) -> Dict[str, Any]:
77
+ params = {"localization": "false", "tickers": "false", "community_data": "false"}
78
+ return await self._make_request(f"coins/{coin_id}", params)
79
+
80
+ async def get_market_data(self, vs_currency: str = "usd", per_page: int = 10) -> Dict[str, Any]:
81
+ params = {
82
+ "vs_currency": vs_currency,
83
+ "order": "market_cap_desc",
84
+ "per_page": per_page,
85
+ "page": 1,
86
+ "sparkline": "false"
87
+ }
88
+ return await self._make_request("coins/markets", params)
89
+
90
+ async def get_price_history(self, coin_id: str, days: int = 7) -> Dict[str, Any]:
91
+ params = {"vs_currency": "usd", "days": days}
92
+ return await self._make_request(f"coins/{coin_id}/market_chart", params)
93
+
94
+ async def close(self):
95
+ if self.session:
96
+ await self.session.close()
97
+
98
+ class CryptoCompareClient:
99
+ def __init__(self):
100
+ self.rate_limiter = RateLimiter(config.RATE_LIMIT_DELAY)
101
+ self.session = None
102
+
103
+ async def get_session(self):
104
+ if self.session is None:
105
+ timeout = aiohttp.ClientTimeout(total=config.REQUEST_TIMEOUT)
106
+ self.session = aiohttp.ClientSession(timeout=timeout)
107
+ return self.session
108
+
109
+ async def _make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
110
+ await self.rate_limiter.acquire()
111
+
112
+ url = f"{config.CRYPTOCOMPARE_BASE_URL}/{endpoint}"
113
+ if params is None:
114
+ params = {}
115
+
116
+ if config.CRYPTOCOMPARE_API_KEY:
117
+ params["api_key"] = config.CRYPTOCOMPARE_API_KEY
118
+
119
+ session = await self.get_session()
120
+
121
+ for attempt in range(config.MAX_RETRIES):
122
+ try:
123
+ async with session.get(url, params=params) as response:
124
+ if response.status == 200:
125
+ data = await response.json()
126
+ if data.get("Response") == "Error":
127
+ raise Exception(data.get("Message", "API error"))
128
+ return data
129
+ elif response.status == 429:
130
+ await asyncio.sleep(2 ** attempt)
131
+ continue
132
+ else:
133
+ raise Exception(f"API error: {response.status}")
134
+ except asyncio.TimeoutError:
135
+ if attempt == config.MAX_RETRIES - 1:
136
+ raise Exception("Request timeout")
137
+ await asyncio.sleep(1)
138
+
139
+ raise Exception("Max retries exceeded")
140
+
141
+ async def get_price_multi(self, fsyms: str, tsyms: str = "USD") -> Dict[str, Any]:
142
+ params = {"fsyms": fsyms, "tsyms": tsyms}
143
+ return await self._make_request("pricemulti", params)
144
+
145
+ async def get_social_data(self, coin_symbol: str) -> Dict[str, Any]:
146
+ params = {"coinSymbol": coin_symbol}
147
+ return await self._make_request("social/coin/latest", params)
148
+
149
+ async def get_news(self, categories: str = "blockchain") -> Dict[str, Any]:
150
+ params = {"categories": categories}
151
+ return await self._make_request("news/", params)
152
+
153
+ async def close(self):
154
+ if self.session:
155
+ await self.session.close()
156
+
157
+ coingecko_client = CoinGeckoClient()
158
+ cryptocompare_client = CryptoCompareClient()
src/cache_manager.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import Any, Optional, Dict
3
+ from src.config import config
4
+
5
+ class CacheManager:
6
+ def __init__(self, default_ttl: Optional[int] = None):
7
+ self.cache: Dict[str, Dict[str, Any]] = {}
8
+ self.default_ttl = default_ttl or config.CACHE_TTL
9
+
10
+ def get(self, key: str) -> Optional[Any]:
11
+ if key not in self.cache:
12
+ return None
13
+
14
+ entry = self.cache[key]
15
+ if time.time() > entry["expires_at"]:
16
+ del self.cache[key]
17
+ return None
18
+
19
+ return entry["data"]
20
+
21
+ def set(self, key: str, data: Any, ttl: Optional[int] = None) -> None:
22
+ expires_at = time.time() + (ttl or self.default_ttl)
23
+ self.cache[key] = {
24
+ "data": data,
25
+ "expires_at": expires_at
26
+ }
27
+
28
+ def delete(self, key: str) -> bool:
29
+ return self.cache.pop(key, None) is not None
30
+
31
+ def clear(self) -> None:
32
+ self.cache.clear()
33
+
34
+ def cleanup_expired(self) -> int:
35
+ current_time = time.time()
36
+ expired_keys = [
37
+ key for key, entry in self.cache.items()
38
+ if current_time > entry["expires_at"]
39
+ ]
40
+
41
+ for key in expired_keys:
42
+ del self.cache[key]
43
+
44
+ return len(expired_keys)
45
+
46
+ def size(self) -> int:
47
+ return len(self.cache)
48
+
49
+ cache_manager = CacheManager()
src/config.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ @dataclass
6
+ class Config:
7
+ GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY", "")
8
+ COINGECKO_API_KEY: Optional[str] = os.getenv("COINGECKO_API_KEY")
9
+ CRYPTOCOMPARE_API_KEY: Optional[str] = os.getenv("CRYPTOCOMPARE_API_KEY")
10
+
11
+ COINGECKO_BASE_URL: str = "https://api.coingecko.com/api/v3"
12
+ CRYPTOCOMPARE_BASE_URL: str = "https://min-api.cryptocompare.com/data"
13
+
14
+ CACHE_TTL: int = 300
15
+ RATE_LIMIT_DELAY: float = 2.0
16
+ MAX_RETRIES: int = 3
17
+ REQUEST_TIMEOUT: int = 30
18
+
19
+ config = Config()
src/defillama_client.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ from typing import Dict, Any, List, Optional
4
+ from src.config import config
5
+
6
+ class DeFiLlamaClient:
7
+ def __init__(self):
8
+ self.base_url = "https://api.llama.fi"
9
+ self.session = None
10
+ self.rate_limiter = None
11
+
12
+ async def get_session(self):
13
+ if self.session is None:
14
+ timeout = aiohttp.ClientTimeout(total=30)
15
+ self.session = aiohttp.ClientSession(timeout=timeout)
16
+ return self.session
17
+
18
+ async def _make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
19
+ url = f"{self.base_url}/{endpoint}"
20
+ session = await self.get_session()
21
+
22
+ for attempt in range(3):
23
+ try:
24
+ async with session.get(url, params=params) as response:
25
+ if response.status == 200:
26
+ return await response.json()
27
+ elif response.status == 429:
28
+ await asyncio.sleep(2 ** attempt)
29
+ continue
30
+ else:
31
+ raise Exception(f"API error: {response.status}")
32
+ except Exception as e:
33
+ if attempt == 2:
34
+ raise e
35
+ await asyncio.sleep(1)
36
+
37
+ async def get_protocols(self) -> List[Dict[str, Any]]:
38
+ return await self._make_request("protocols")
39
+
40
+ async def get_protocol_data(self, protocol: str) -> Dict[str, Any]:
41
+ return await self._make_request(f"protocol/{protocol}")
42
+
43
+ async def get_tvl_data(self) -> Dict[str, Any]:
44
+ return await self._make_request("v2/historicalChainTvl")
45
+
46
+ async def get_chain_tvl(self, chain: str) -> Dict[str, Any]:
47
+ return await self._make_request(f"v2/historicalChainTvl/{chain}")
48
+
49
+ async def get_yields(self) -> List[Dict[str, Any]]:
50
+ return await self._make_request("pools")
51
+
52
+ async def get_bridges(self) -> List[Dict[str, Any]]:
53
+ return await self._make_request("bridges")
54
+
55
+ async def get_dex_volume(self) -> Dict[str, Any]:
56
+ return await self._make_request("overview/dexs")
57
+
58
+ async def close(self):
59
+ if self.session:
60
+ await self.session.close()
61
+
62
+ defillama_client = DeFiLlamaClient()
src/enhanced_agent.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ import json
3
+ import asyncio
4
+ from typing import Dict, Any, List, Optional
5
+ from src.api_clients import coingecko_client, cryptocompare_client
6
+ from src.defillama_client import defillama_client
7
+ from src.news_aggregator import news_aggregator
8
+ from src.cache_manager import cache_manager
9
+ from src.config import config
10
+
11
+ class EnhancedResearchAgent:
12
+ def __init__(self):
13
+ if config.GEMINI_API_KEY:
14
+ genai.configure(api_key=config.GEMINI_API_KEY)
15
+ self.model = genai.GenerativeModel('gemini-1.5-flash')
16
+ else:
17
+ self.model = None
18
+
19
+ self.symbol_map = {
20
+ "btc": "bitcoin", "eth": "ethereum", "sol": "solana", "ada": "cardano",
21
+ "dot": "polkadot", "bnb": "binancecoin", "usdc": "usd-coin",
22
+ "usdt": "tether", "xrp": "ripple", "avax": "avalanche-2",
23
+ "link": "chainlink", "matic": "matic-network", "uni": "uniswap",
24
+ "atom": "cosmos", "near": "near", "icp": "internet-computer",
25
+ "ftm": "fantom", "algo": "algorand", "xlm": "stellar"
26
+ }
27
+
28
+ def _format_coin_id(self, symbol: str) -> str:
29
+ return self.symbol_map.get(symbol.lower(), symbol.lower())
30
+
31
+ async def get_comprehensive_market_data(self) -> Dict[str, Any]:
32
+ cache_key = "comprehensive_market"
33
+ cached = cache_manager.get(cache_key)
34
+ if cached:
35
+ return cached
36
+
37
+ try:
38
+ tasks = [
39
+ coingecko_client.get_market_data(per_page=50),
40
+ coingecko_client.get_global_data(),
41
+ coingecko_client.get_trending(),
42
+ defillama_client.get_protocols(),
43
+ defillama_client.get_tvl_data(),
44
+ news_aggregator.get_crypto_news(5)
45
+ ]
46
+
47
+ results = await asyncio.gather(*tasks, return_exceptions=True)
48
+
49
+ data = {}
50
+ for i, result in enumerate(results):
51
+ if not isinstance(result, Exception):
52
+ if i == 0: data["market_data"] = result
53
+ elif i == 1: data["global_data"] = result
54
+ elif i == 2: data["trending"] = result
55
+ elif i == 3:
56
+ data["defi_protocols"] = result[:20] if isinstance(result, list) and result else []
57
+ elif i == 4: data["tvl_data"] = result
58
+ elif i == 5: data["news"] = result
59
+
60
+ cache_manager.set(cache_key, data, 180)
61
+ return data
62
+
63
+ except Exception as e:
64
+ raise Exception(f"Failed to fetch comprehensive market data: {str(e)}")
65
+
66
+ async def get_defi_analysis(self, protocol: Optional[str] = None) -> Dict[str, Any]:
67
+ cache_key = f"defi_analysis_{protocol or 'overview'}"
68
+ cached = cache_manager.get(cache_key)
69
+ if cached:
70
+ return cached
71
+
72
+ try:
73
+ if protocol:
74
+ data = await defillama_client.get_protocol_data(protocol)
75
+ else:
76
+ protocols = await defillama_client.get_protocols()
77
+ tvl_data = await defillama_client.get_tvl_data()
78
+ yields_data = await defillama_client.get_yields()
79
+
80
+ data = {
81
+ "top_protocols": protocols[:20] if isinstance(protocols, list) and protocols else [],
82
+ "tvl_overview": tvl_data,
83
+ "top_yields": yields_data[:10] if isinstance(yields_data, list) and yields_data else []
84
+ }
85
+
86
+ cache_manager.set(cache_key, data, 300)
87
+ return data
88
+
89
+ except Exception as e:
90
+ raise Exception(f"Failed to get DeFi analysis: {str(e)}")
91
+
92
+ async def get_price_history(self, symbol: str, days: int = 30) -> Dict[str, Any]:
93
+ cache_key = f"price_history_{symbol}_{days}"
94
+ cached = cache_manager.get(cache_key)
95
+ if cached:
96
+ return cached
97
+
98
+ try:
99
+ coin_id = self._format_coin_id(symbol)
100
+ data = await coingecko_client.get_price_history(coin_id, days)
101
+ cache_manager.set(cache_key, data, 900)
102
+ return data
103
+ except Exception as e:
104
+ raise Exception(f"Failed to get price history for {symbol}: {str(e)}")
105
+
106
+ async def get_advanced_coin_analysis(self, symbol: str) -> Dict[str, Any]:
107
+ cache_key = f"advanced_analysis_{symbol.lower()}"
108
+ cached = cache_manager.get(cache_key)
109
+ if cached:
110
+ return cached
111
+
112
+ try:
113
+ coin_id = self._format_coin_id(symbol)
114
+
115
+ tasks = [
116
+ coingecko_client.get_coin_data(coin_id),
117
+ coingecko_client.get_price_history(coin_id, days=30),
118
+ cryptocompare_client.get_social_data(symbol.upper()),
119
+ self._get_defi_involvement(symbol.upper())
120
+ ]
121
+
122
+ results = await asyncio.gather(*tasks, return_exceptions=True)
123
+
124
+ analysis = {}
125
+ for i, result in enumerate(results):
126
+ if not isinstance(result, Exception):
127
+ if i == 0: analysis["coin_data"] = result
128
+ elif i == 1: analysis["price_history"] = result
129
+ elif i == 2: analysis["social_data"] = result
130
+ elif i == 3: analysis["defi_data"] = result
131
+
132
+ cache_manager.set(cache_key, analysis, 300)
133
+ return analysis
134
+
135
+ except Exception as e:
136
+ raise Exception(f"Failed advanced analysis for {symbol}: {str(e)}")
137
+
138
+ async def _get_defi_involvement(self, symbol: str) -> Dict[str, Any]:
139
+ try:
140
+ protocols = await defillama_client.get_protocols()
141
+ if protocols:
142
+ relevant_protocols = [p for p in protocols if symbol.lower() in p.get("name", "").lower()]
143
+ return {"protocols": relevant_protocols[:5]}
144
+ return {"protocols": []}
145
+ except:
146
+ return {"protocols": []}
147
+
148
+ def _format_comprehensive_data(self, data: Dict[str, Any]) -> str:
149
+ formatted = "📊 COMPREHENSIVE CRYPTO MARKET ANALYSIS\n\n"
150
+
151
+ if "global_data" in data and data["global_data"].get("data"):
152
+ global_info = data["global_data"]["data"]
153
+ total_mcap = global_info.get("total_market_cap", {}).get("usd", 0)
154
+ total_volume = global_info.get("total_volume", {}).get("usd", 0)
155
+ btc_dominance = global_info.get("market_cap_percentage", {}).get("btc", 0)
156
+ eth_dominance = global_info.get("market_cap_percentage", {}).get("eth", 0)
157
+
158
+ formatted += f"💰 Total Market Cap: ${total_mcap/1e12:.2f}T\n"
159
+ formatted += f"📈 24h Volume: ${total_volume/1e9:.1f}B\n"
160
+ formatted += f"₿ Bitcoin Dominance: {btc_dominance:.1f}%\n"
161
+ formatted += f"Ξ Ethereum Dominance: {eth_dominance:.1f}%\n\n"
162
+
163
+ if "trending" in data and data["trending"].get("coins"):
164
+ formatted += "🔥 TRENDING CRYPTOCURRENCIES\n"
165
+ for i, coin in enumerate(data["trending"]["coins"][:5], 1):
166
+ name = coin.get("item", {}).get("name", "Unknown")
167
+ symbol = coin.get("item", {}).get("symbol", "")
168
+ score = coin.get("item", {}).get("score", 0)
169
+ formatted += f"{i}. {name} ({symbol.upper()}) - Score: {score}\n"
170
+ formatted += "\n"
171
+
172
+ if "defi_protocols" in data and data["defi_protocols"]:
173
+ formatted += "🏦 TOP DeFi PROTOCOLS\n"
174
+ for i, protocol in enumerate(data["defi_protocols"][:5], 1):
175
+ name = protocol.get("name", "Unknown")
176
+ tvl = protocol.get("tvl", 0)
177
+ chain = protocol.get("chain", "Unknown")
178
+ formatted += f"{i}. {name} ({chain}): ${tvl/1e9:.2f}B TVL\n"
179
+ formatted += "\n"
180
+
181
+ if "news" in data and data["news"]:
182
+ formatted += "📰 LATEST CRYPTO NEWS\n"
183
+ for i, article in enumerate(data["news"][:3], 1):
184
+ title = article.get("title", "No title")[:60] + "..."
185
+ source = article.get("source", "Unknown")
186
+ formatted += f"{i}. {title} - {source}\n"
187
+ formatted += "\n"
188
+
189
+ if "market_data" in data and data["market_data"]:
190
+ formatted += "💎 TOP PERFORMING COINS (24h)\n"
191
+ valid_coins = [coin for coin in data["market_data"][:20] if coin.get("price_change_percentage_24h") is not None]
192
+ sorted_coins = sorted(valid_coins, key=lambda x: x.get("price_change_percentage_24h", 0), reverse=True)
193
+ for i, coin in enumerate(sorted_coins[:5], 1):
194
+ name = coin.get("name", "Unknown")
195
+ symbol = coin.get("symbol", "").upper()
196
+ price = coin.get("current_price", 0)
197
+ change = coin.get("price_change_percentage_24h", 0)
198
+ formatted += f"{i}. {name} ({symbol}): ${price:,.4f} (+{change:.2f}%)\n"
199
+
200
+ return formatted
201
+
202
+ async def research_with_context(self, query: str) -> str:
203
+ try:
204
+ if not config.GEMINI_API_KEY or not self.model:
205
+ return "❌ Gemini API key not configured. Please set GEMINI_API_KEY environment variable."
206
+
207
+ system_prompt = """You are an advanced Web3 and DeFi research analyst with access to real-time market data,
208
+ DeFi protocol information, social sentiment, and breaking news. Provide comprehensive, actionable insights
209
+ that combine multiple data sources for superior analysis.
210
+
211
+ Guidelines:
212
+ - Synthesize data from multiple sources (price, DeFi, social, news)
213
+ - Provide specific recommendations with risk assessments
214
+ - Include both technical and fundamental analysis
215
+ - Reference current market conditions and news events
216
+ - Use clear, professional language with data-driven insights
217
+ - Highlight opportunities and risks clearly
218
+ """
219
+
220
+ market_context = ""
221
+ try:
222
+ if any(keyword in query.lower() for keyword in
223
+ ["market", "overview", "analysis", "trending", "defi", "protocols"]):
224
+ comprehensive_data = await self.get_comprehensive_market_data()
225
+ market_context = f"\n\nCURRENT MARKET ANALYSIS:\n{self._format_comprehensive_data(comprehensive_data)}"
226
+
227
+ for symbol in self.symbol_map.keys():
228
+ if symbol in query.lower() or symbol.upper() in query:
229
+ analysis_data = await self.get_advanced_coin_analysis(symbol)
230
+ if "coin_data" in analysis_data:
231
+ coin_info = analysis_data["coin_data"]
232
+ market_data = coin_info.get("market_data", {})
233
+ current_price = market_data.get("current_price", {}).get("usd", 0)
234
+ price_change = market_data.get("price_change_percentage_24h", 0)
235
+ market_cap = market_data.get("market_cap", {}).get("usd", 0)
236
+ volume = market_data.get("total_volume", {}).get("usd", 0)
237
+ ath = market_data.get("ath", {}).get("usd", 0)
238
+ ath_change = market_data.get("ath_change_percentage", {}).get("usd", 0)
239
+
240
+ market_context += f"\n\n{symbol.upper()} DETAILED ANALYSIS:\n"
241
+ market_context += f"Current Price: ${current_price:,.4f}\n"
242
+ market_context += f"24h Change: {price_change:+.2f}%\n"
243
+ market_context += f"Market Cap: ${market_cap/1e9:.2f}B\n"
244
+ market_context += f"24h Volume: ${volume/1e9:.2f}B\n"
245
+ market_context += f"ATH: ${ath:,.4f} ({ath_change:+.2f}% from ATH)\n"
246
+ break
247
+
248
+ if "defi" in query.lower():
249
+ defi_data = await self.get_defi_analysis()
250
+ if "top_protocols" in defi_data and defi_data["top_protocols"]:
251
+ market_context += "\n\nTOP DeFi PROTOCOLS BY TVL:\n"
252
+ for protocol in defi_data["top_protocols"][:5]:
253
+ name = protocol.get("name", "Unknown")
254
+ tvl = protocol.get("tvl", 0)
255
+ change = protocol.get("change_1d", 0)
256
+ market_context += f"• {name}: ${tvl/1e9:.2f}B TVL ({change:+.2f}%)\n"
257
+
258
+ except Exception as e:
259
+ market_context = f"\n\nNote: Some enhanced data unavailable ({str(e)})"
260
+
261
+ full_prompt = f"{system_prompt}\n\nQuery: {query}\n\nReal-time Market Context:{market_context}"
262
+
263
+ response = self.model.generate_content(full_prompt)
264
+ return response.text if response.text else "❌ No response generated. Please try rephrasing your query."
265
+
266
+ except Exception as e:
267
+ return f"❌ Enhanced research failed: {str(e)}"
268
+
269
+ async def close(self):
270
+ await coingecko_client.close()
271
+ await cryptocompare_client.close()
272
+ await defillama_client.close()
273
+ await news_aggregator.close()
src/news_aggregator.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ from typing import Dict, Any, List, Optional
4
+ from datetime import datetime
5
+ import json
6
+
7
+ class CryptoNewsAggregator:
8
+ def __init__(self):
9
+ self.sources = {
10
+ "cryptonews": "https://cryptonews-api.com/api/v1/category?section=general&items=10",
11
+ "newsapi": "https://newsapi.org/v2/everything?q=cryptocurrency&sortBy=publishedAt&pageSize=10",
12
+ "coindesk": "https://api.coindesk.com/v1/news/articles"
13
+ }
14
+ self.session = None
15
+
16
+ async def get_session(self):
17
+ if self.session is None:
18
+ timeout = aiohttp.ClientTimeout(total=30)
19
+ headers = {"User-Agent": "Web3-Research-CoBot/1.0"}
20
+ self.session = aiohttp.ClientSession(timeout=timeout, headers=headers)
21
+ return self.session
22
+
23
+ async def get_crypto_news(self, limit: int = 10) -> List[Dict[str, Any]]:
24
+ news_items = []
25
+ tasks = []
26
+
27
+ for source, url in self.sources.items():
28
+ tasks.append(self._fetch_news_from_source(source, url))
29
+
30
+ results = await asyncio.gather(*tasks, return_exceptions=True)
31
+
32
+ for result in results:
33
+ if not isinstance(result, Exception) and result:
34
+ news_items.extend(result[:5])
35
+
36
+ news_items.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
37
+ return news_items[:limit]
38
+
39
+ async def _fetch_news_from_source(self, source: str, url: str) -> List[Dict[str, Any]]:
40
+ try:
41
+ session = await self.get_session()
42
+ async with session.get(url) as response:
43
+ if response.status == 200:
44
+ data = await response.json()
45
+ return self._parse_news_data(source, data)
46
+ return []
47
+ except Exception:
48
+ return []
49
+
50
+ def _parse_news_data(self, source: str, data: Dict[str, Any]) -> List[Dict[str, Any]]:
51
+ news_items = []
52
+ current_time = datetime.now().timestamp()
53
+
54
+ try:
55
+ if source == "cryptonews" and "data" in data:
56
+ for item in data["data"][:5]:
57
+ news_items.append({
58
+ "title": item.get("news_title", ""),
59
+ "summary": item.get("text", "")[:200] + "...",
60
+ "url": item.get("news_url", ""),
61
+ "source": "CryptoNews",
62
+ "timestamp": current_time
63
+ })
64
+
65
+ elif source == "newsapi" and "articles" in data:
66
+ for item in data["articles"][:5]:
67
+ news_items.append({
68
+ "title": item.get("title", ""),
69
+ "summary": item.get("description", "")[:200] + "...",
70
+ "url": item.get("url", ""),
71
+ "source": item.get("source", {}).get("name", "NewsAPI"),
72
+ "timestamp": current_time
73
+ })
74
+ except Exception:
75
+ pass
76
+
77
+ return news_items
78
+
79
+ async def close(self):
80
+ if self.session:
81
+ await self.session.close()
82
+
83
+ news_aggregator = CryptoNewsAggregator()
src/portfolio_analyzer.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from typing import Dict, Any, List, Optional
3
+ from src.api_clients import coingecko_client
4
+ from src.cache_manager import cache_manager
5
+ import json
6
+
7
+ class PortfolioAnalyzer:
8
+ def __init__(self):
9
+ self.symbol_map = {
10
+ "btc": "bitcoin", "eth": "ethereum", "sol": "solana", "ada": "cardano",
11
+ "dot": "polkadot", "bnb": "binancecoin", "usdc": "usd-coin",
12
+ "usdt": "tether", "xrp": "ripple", "avax": "avalanche-2",
13
+ "link": "chainlink", "matic": "matic-network", "uni": "uniswap"
14
+ }
15
+
16
+ def _format_coin_id(self, symbol: str) -> str:
17
+ return self.symbol_map.get(symbol.lower(), symbol.lower())
18
+
19
+ async def analyze_portfolio(self, holdings: List[Dict[str, Any]]) -> Dict[str, Any]:
20
+ try:
21
+ coin_ids = [self._format_coin_id(h["symbol"]) for h in holdings]
22
+
23
+ tasks = []
24
+ for coin_id in coin_ids:
25
+ tasks.append(coingecko_client.get_coin_data(coin_id))
26
+ tasks.append(coingecko_client.get_price_history(coin_id, days=30))
27
+
28
+ results = await asyncio.gather(*tasks, return_exceptions=True)
29
+
30
+ portfolio_value = 0
31
+ portfolio_change_24h = 0
32
+ asset_allocation = []
33
+ risk_metrics = []
34
+
35
+ for i, holding in enumerate(holdings):
36
+ coin_data_idx = i * 2
37
+ price_history_idx = i * 2 + 1
38
+
39
+ if not isinstance(results[coin_data_idx], Exception):
40
+ coin_data = results[coin_data_idx]
41
+ market_data = coin_data.get("market_data", {})
42
+ current_price = market_data.get("current_price", {}).get("usd", 0)
43
+ price_change_24h = market_data.get("price_change_percentage_24h", 0)
44
+
45
+ holding_value = current_price * holding["amount"]
46
+ portfolio_value += holding_value
47
+ portfolio_change_24h += holding_value * (price_change_24h / 100)
48
+
49
+ volatility = 0
50
+ if not isinstance(results[price_history_idx], Exception):
51
+ price_history = results[price_history_idx]
52
+ prices = [p[1] for p in price_history.get("prices", [])]
53
+ if len(prices) > 1:
54
+ price_changes = [(prices[i] - prices[i-1]) / prices[i-1] for i in range(1, len(prices))]
55
+ volatility = sum(abs(change) for change in price_changes) / len(price_changes)
56
+
57
+ asset_allocation.append({
58
+ "symbol": holding["symbol"].upper(),
59
+ "name": coin_data.get("name", "Unknown"),
60
+ "value": holding_value,
61
+ "percentage": 0,
62
+ "amount": holding["amount"],
63
+ "price": current_price,
64
+ "change_24h": price_change_24h
65
+ })
66
+
67
+ risk_metrics.append({
68
+ "symbol": holding["symbol"].upper(),
69
+ "volatility": volatility,
70
+ "market_cap_rank": coin_data.get("market_cap_rank", 999)
71
+ })
72
+
73
+ for asset in asset_allocation:
74
+ asset["percentage"] = (asset["value"] / portfolio_value) * 100 if portfolio_value > 0 else 0
75
+
76
+ portfolio_change_percentage = (portfolio_change_24h / portfolio_value) * 100 if portfolio_value > 0 else 0
77
+
78
+ avg_volatility = sum(r["volatility"] for r in risk_metrics) / len(risk_metrics) if risk_metrics else 0
79
+
80
+ diversification_score = len([a for a in asset_allocation if a["percentage"] >= 5])
81
+
82
+ risk_level = "Low" if avg_volatility < 0.05 else "Medium" if avg_volatility < 0.10 else "High"
83
+
84
+ return {
85
+ "total_value": portfolio_value,
86
+ "change_24h": portfolio_change_24h,
87
+ "change_24h_percentage": portfolio_change_percentage,
88
+ "asset_allocation": sorted(asset_allocation, key=lambda x: x["value"], reverse=True),
89
+ "risk_metrics": {
90
+ "overall_risk": risk_level,
91
+ "avg_volatility": avg_volatility,
92
+ "diversification_score": diversification_score,
93
+ "largest_holding_percentage": max([a["percentage"] for a in asset_allocation]) if asset_allocation else 0
94
+ },
95
+ "recommendations": self._generate_recommendations(asset_allocation, risk_metrics)
96
+ }
97
+
98
+ except Exception as e:
99
+ raise Exception(f"Portfolio analysis failed: {str(e)}")
100
+
101
+ def _generate_recommendations(self, allocation: List[Dict[str, Any]], risk_metrics: List[Dict[str, Any]]) -> List[str]:
102
+ recommendations = []
103
+
104
+ if not allocation:
105
+ return ["Unable to generate recommendations - no valid portfolio data"]
106
+
107
+ largest_holding = max(allocation, key=lambda x: x["percentage"])
108
+ if largest_holding["percentage"] > 50:
109
+ recommendations.append(f"Consider reducing {largest_holding['symbol']} position (currently {largest_holding['percentage']:.1f}%) to improve diversification")
110
+
111
+ high_risk_assets = [r for r in risk_metrics if r["volatility"] > 0.15]
112
+ if len(high_risk_assets) > len(allocation) * 0.6:
113
+ recommendations.append("Portfolio has high volatility exposure - consider adding stable assets like BTC or ETH")
114
+
115
+ small_cap_heavy = len([r for r in risk_metrics if r["market_cap_rank"] > 100])
116
+ if small_cap_heavy > len(allocation) * 0.4:
117
+ recommendations.append("High small-cap exposure detected - consider balancing with top 20 cryptocurrencies")
118
+
119
+ if len(allocation) < 5:
120
+ recommendations.append("Consider diversifying into 5-10 different cryptocurrencies to reduce risk")
121
+
122
+ stablecoin_exposure = sum(a["percentage"] for a in allocation if a["symbol"] in ["USDC", "USDT", "DAI"])
123
+ if stablecoin_exposure < 10:
124
+ recommendations.append("Consider allocating 10-20% to stablecoins for portfolio stability")
125
+
126
+ return recommendations[:5]
127
+
128
+ async def compare_portfolios(self, portfolio1: List[Dict[str, Any]], portfolio2: List[Dict[str, Any]]) -> Dict[str, Any]:
129
+ analysis1 = await self.analyze_portfolio(portfolio1)
130
+ analysis2 = await self.analyze_portfolio(portfolio2)
131
+
132
+ return {
133
+ "portfolio_1": analysis1,
134
+ "portfolio_2": analysis2,
135
+ "comparison": {
136
+ "value_difference": analysis2["total_value"] - analysis1["total_value"],
137
+ "performance_difference": analysis2["change_24h_percentage"] - analysis1["change_24h_percentage"],
138
+ "risk_comparison": f"Portfolio 2 is {'higher' if analysis2['risk_metrics']['avg_volatility'] > analysis1['risk_metrics']['avg_volatility'] else 'lower'} risk",
139
+ "diversification_comparison": f"Portfolio 2 is {'more' if analysis2['risk_metrics']['diversification_score'] > analysis1['risk_metrics']['diversification_score'] else 'less'} diversified"
140
+ }
141
+ }
142
+
143
+ portfolio_analyzer = PortfolioAnalyzer()
src/research_agent.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from google import genai
2
+ from google.genai import types
3
+ import json
4
+ from typing import Dict, Any, List
5
+ from src.api_clients import coingecko_client, cryptocompare_client
6
+ from src.cache_manager import cache_manager
7
+ from src.config import config
8
+ import asyncio
9
+
10
+ class ResearchAgent:
11
+ def __init__(self):
12
+ self.client = genai.Client(api_key=config.GEMINI_API_KEY)
13
+ self.symbol_map = {
14
+ "btc": "bitcoin", "eth": "ethereum", "sol": "solana",
15
+ "ada": "cardano", "dot": "polkadot", "bnb": "binancecoin",
16
+ "usdc": "usd-coin", "usdt": "tether", "xrp": "ripple",
17
+ "avax": "avalanche-2", "link": "chainlink", "matic": "matic-network"
18
+ }
19
+
20
+ def _format_coin_id(self, symbol: str) -> str:
21
+ return self.symbol_map.get(symbol.lower(), symbol.lower())
22
+
23
+ async def get_market_overview(self) -> Dict[str, Any]:
24
+ cache_key = "market_overview"
25
+ cached = cache_manager.get(cache_key)
26
+ if cached:
27
+ return cached
28
+
29
+ try:
30
+ market_data = await coingecko_client.get_market_data(per_page=20)
31
+ global_data = await coingecko_client.get_global_data()
32
+ trending = await coingecko_client.get_trending()
33
+
34
+ result = {
35
+ "market_data": market_data,
36
+ "global_data": global_data,
37
+ "trending": trending
38
+ }
39
+
40
+ cache_manager.set(cache_key, result)
41
+ return result
42
+
43
+ except Exception as e:
44
+ raise Exception(f"Failed to fetch market overview: {str(e)}")
45
+
46
+ async def get_price_history(self, symbol: str) -> Dict[str, Any]:
47
+ cache_key = f"price_history_{symbol.lower()}"
48
+ cached = cache_manager.get(cache_key)
49
+ if cached:
50
+ return cached
51
+
52
+ try:
53
+ coin_id = self._format_coin_id(symbol)
54
+ data = await coingecko_client.get_price_history(coin_id, days=30)
55
+
56
+ cache_manager.set(cache_key, data)
57
+ return data
58
+
59
+ except Exception as e:
60
+ raise Exception(f"Failed to fetch price history for {symbol}: {str(e)}")
61
+
62
+ async def get_coin_analysis(self, symbol: str) -> Dict[str, Any]:
63
+ cache_key = f"coin_analysis_{symbol.lower()}"
64
+ cached = cache_manager.get(cache_key)
65
+ if cached:
66
+ return cached
67
+
68
+ try:
69
+ coin_id = self._format_coin_id(symbol)
70
+
71
+ tasks = [
72
+ coingecko_client.get_coin_data(coin_id),
73
+ coingecko_client.get_price_history(coin_id, days=7),
74
+ cryptocompare_client.get_social_data(symbol.upper())
75
+ ]
76
+
77
+ coin_data, price_history, social_data = await asyncio.gather(*tasks, return_exceptions=True)
78
+
79
+ result = {}
80
+ if not isinstance(coin_data, Exception):
81
+ result["coin_data"] = coin_data
82
+ if not isinstance(price_history, Exception):
83
+ result["price_history"] = price_history
84
+ if not isinstance(social_data, Exception):
85
+ result["social_data"] = social_data
86
+
87
+ cache_manager.set(cache_key, result)
88
+ return result
89
+
90
+ except Exception as e:
91
+ raise Exception(f"Failed to analyze {symbol}: {str(e)}")
92
+
93
+ def _format_market_data(self, data: Dict[str, Any]) -> str:
94
+ if not data:
95
+ return "No market data available"
96
+
97
+ formatted = "📊 MARKET OVERVIEW\n\n"
98
+
99
+ if "global_data" in data and "data" in data["global_data"]:
100
+ global_info = data["global_data"]["data"]
101
+ total_mcap = global_info.get("total_market_cap", {}).get("usd", 0)
102
+ total_volume = global_info.get("total_volume", {}).get("usd", 0)
103
+ btc_dominance = global_info.get("market_cap_percentage", {}).get("btc", 0)
104
+
105
+ formatted += f"Total Market Cap: ${total_mcap:,.0f}\n"
106
+ formatted += f"24h Volume: ${total_volume:,.0f}\n"
107
+ formatted += f"Bitcoin Dominance: {btc_dominance:.1f}%\n\n"
108
+
109
+ if "trending" in data and "coins" in data["trending"]:
110
+ formatted += "🔥 TRENDING COINS\n"
111
+ for i, coin in enumerate(data["trending"]["coins"][:5], 1):
112
+ name = coin.get("item", {}).get("name", "Unknown")
113
+ symbol = coin.get("item", {}).get("symbol", "")
114
+ formatted += f"{i}. {name} ({symbol.upper()})\n"
115
+ formatted += "\n"
116
+
117
+ if "market_data" in data:
118
+ formatted += "💰 TOP CRYPTOCURRENCIES\n"
119
+ for i, coin in enumerate(data["market_data"][:10], 1):
120
+ name = coin.get("name", "Unknown")
121
+ symbol = coin.get("symbol", "").upper()
122
+ price = coin.get("current_price", 0)
123
+ change = coin.get("price_change_percentage_24h", 0)
124
+ change_symbol = "📈" if change >= 0 else "📉"
125
+
126
+ formatted += f"{i:2d}. {name} ({symbol}): ${price:,.4f} {change_symbol} {change:+.2f}%\n"
127
+
128
+ return formatted
129
+
130
+ async def research(self, query: str) -> str:
131
+ try:
132
+ if not config.GEMINI_API_KEY:
133
+ return "❌ Gemini API key not configured. Please set GEMINI_API_KEY environment variable."
134
+
135
+ system_prompt = """You are an expert Web3 and cryptocurrency research analyst.
136
+ Provide comprehensive, accurate, and actionable insights based on real market data.
137
+
138
+ Guidelines:
139
+ - Give specific, data-driven analysis
140
+ - Include price targets and risk assessments when relevant
141
+ - Explain technical concepts clearly
142
+ - Provide actionable recommendations
143
+ - Use emojis for better readability
144
+ - Be concise but thorough
145
+ """
146
+
147
+ market_context = ""
148
+ try:
149
+ if any(keyword in query.lower() for keyword in ["market", "overview", "trending", "top"]):
150
+ market_data = await self.get_market_overview()
151
+ market_context = f"\n\nCURRENT MARKET DATA:\n{self._format_market_data(market_data)}"
152
+
153
+ for symbol in ["btc", "eth", "sol", "ada", "dot", "bnb", "avax", "link"]:
154
+ if symbol in query.lower() or symbol.upper() in query:
155
+ analysis_data = await self.get_coin_analysis(symbol)
156
+ if "coin_data" in analysis_data:
157
+ coin_info = analysis_data["coin_data"]
158
+ market_data = coin_info.get("market_data", {})
159
+ current_price = market_data.get("current_price", {}).get("usd", 0)
160
+ price_change = market_data.get("price_change_percentage_24h", 0)
161
+ market_cap = market_data.get("market_cap", {}).get("usd", 0)
162
+ volume = market_data.get("total_volume", {}).get("usd", 0)
163
+
164
+ market_context += f"\n\n{symbol.upper()} DATA:\n"
165
+ market_context += f"Price: ${current_price:,.4f}\n"
166
+ market_context += f"24h Change: {price_change:+.2f}%\n"
167
+ market_context += f"Market Cap: ${market_cap:,.0f}\n"
168
+ market_context += f"Volume: ${volume:,.0f}\n"
169
+ break
170
+
171
+ except Exception as e:
172
+ market_context = f"\n\nNote: Some market data unavailable ({str(e)})"
173
+
174
+ full_prompt = f"{query}{market_context}"
175
+
176
+ response = self.client.models.generate_content(
177
+ model="gemini-2.5-flash",
178
+ contents=[
179
+ types.Content(
180
+ role="user",
181
+ parts=[types.Part(text=full_prompt)]
182
+ )
183
+ ],
184
+ config=types.GenerateContentConfig(
185
+ system_instruction=system_prompt,
186
+ temperature=0.3,
187
+ max_output_tokens=2000
188
+ )
189
+ )
190
+
191
+ if response.text:
192
+ return response.text
193
+ else:
194
+ return "❌ No response generated. Please try rephrasing your query."
195
+
196
+ except Exception as e:
197
+ return f"❌ Research failed: {str(e)}"
198
+
199
+ async def close(self):
200
+ await coingecko_client.close()
201
+ await cryptocompare_client.close()
src/visualizations.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.graph_objects as go
2
+ import plotly.express as px
3
+ from plotly.subplots import make_subplots
4
+ from typing import Dict, Any, List
5
+ import pandas as pd
6
+ from datetime import datetime
7
+
8
+ def create_price_chart(data: Dict[str, Any], symbol: str) -> str:
9
+ try:
10
+ if not data or "prices" not in data:
11
+ return f"<div style='padding: 20px; text-align: center;'>No price data available for {symbol.upper()}</div>"
12
+
13
+ prices = data["prices"]
14
+ volumes = data.get("total_volumes", [])
15
+
16
+ if not prices:
17
+ return f"<div style='padding: 20px; text-align: center;'>No price history found for {symbol.upper()}</div>"
18
+
19
+ df = pd.DataFrame(prices, columns=["timestamp", "price"])
20
+ df["datetime"] = pd.to_datetime(df["timestamp"], unit="ms")
21
+
22
+ fig = make_subplots(
23
+ rows=2, cols=1,
24
+ shared_xaxes=True,
25
+ vertical_spacing=0.05,
26
+ subplot_titles=[f"{symbol.upper()} Price Chart", "Volume"],
27
+ row_width=[0.7, 0.3]
28
+ )
29
+
30
+ fig.add_trace(
31
+ go.Scatter(
32
+ x=df["datetime"],
33
+ y=df["price"],
34
+ mode="lines",
35
+ name="Price",
36
+ line=dict(color="#00D4AA", width=2),
37
+ hovertemplate="<b>%{y:$,.2f}</b><br>%{x}<extra></extra>"
38
+ ),
39
+ row=1, col=1
40
+ )
41
+
42
+ if volumes:
43
+ vol_df = pd.DataFrame(volumes, columns=["timestamp", "volume"])
44
+ vol_df["datetime"] = pd.to_datetime(vol_df["timestamp"], unit="ms")
45
+
46
+ fig.add_trace(
47
+ go.Bar(
48
+ x=vol_df["datetime"],
49
+ y=vol_df["volume"],
50
+ name="Volume",
51
+ marker_color="#FF6B6B",
52
+ opacity=0.7,
53
+ hovertemplate="<b>$%{y:,.0f}</b><br>%{x}<extra></extra>"
54
+ ),
55
+ row=2, col=1
56
+ )
57
+
58
+ current_price = df["price"].iloc[-1]
59
+ price_change = ((df["price"].iloc[-1] - df["price"].iloc[0]) / df["price"].iloc[0]) * 100
60
+
61
+ fig.update_layout(
62
+ title=dict(
63
+ text=f"{symbol.upper()} - ${current_price:,.4f} ({price_change:+.2f}%)",
64
+ x=0.5,
65
+ font=dict(size=20, color="#FFFFFF")
66
+ ),
67
+ xaxis_title="Date",
68
+ yaxis_title="Price (USD)",
69
+ template="plotly_dark",
70
+ showlegend=False,
71
+ height=600,
72
+ margin=dict(l=60, r=60, t=80, b=60),
73
+ plot_bgcolor="rgba(0,0,0,0)",
74
+ paper_bgcolor="rgba(0,0,0,0)"
75
+ )
76
+
77
+ fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
78
+ fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
79
+
80
+ return fig.to_html(include_plotlyjs="cdn", div_id=f"chart_{symbol}")
81
+
82
+ except Exception as e:
83
+ return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Chart generation failed: {str(e)}</div>"
84
+
85
+ def create_market_overview(data: Dict[str, Any]) -> str:
86
+ try:
87
+ if not data or "market_data" not in data:
88
+ return "<div style='padding: 20px; text-align: center;'>Market data unavailable</div>"
89
+
90
+ market_data = data["market_data"]
91
+ if not market_data:
92
+ return "<div style='padding: 20px; text-align: center;'>No market data found</div>"
93
+
94
+ df = pd.DataFrame(market_data)
95
+ df = df.head(20)
96
+
97
+ fig = make_subplots(
98
+ rows=2, cols=2,
99
+ subplot_titles=[
100
+ "Market Cap Distribution",
101
+ "24h Price Changes",
102
+ "Trading Volume",
103
+ "Price vs Volume"
104
+ ],
105
+ specs=[[{"type": "pie"}, {"type": "bar"}],
106
+ [{"type": "bar"}, {"type": "scatter"}]]
107
+ )
108
+
109
+ fig.add_trace(
110
+ go.Pie(
111
+ labels=df["symbol"].str.upper(),
112
+ values=df["market_cap"],
113
+ textinfo="label+percent",
114
+ textposition="inside",
115
+ marker=dict(colors=px.colors.qualitative.Set3),
116
+ hovertemplate="<b>%{label}</b><br>Market Cap: $%{value:,.0f}<extra></extra>"
117
+ ),
118
+ row=1, col=1
119
+ )
120
+
121
+ colors = ["#00D4AA" if x >= 0 else "#FF6B6B" for x in df["price_change_percentage_24h"]]
122
+ fig.add_trace(
123
+ go.Bar(
124
+ x=df["symbol"].str.upper(),
125
+ y=df["price_change_percentage_24h"],
126
+ marker_color=colors,
127
+ hovertemplate="<b>%{x}</b><br>24h Change: %{y:+.2f}%<extra></extra>"
128
+ ),
129
+ row=1, col=2
130
+ )
131
+
132
+ fig.add_trace(
133
+ go.Bar(
134
+ x=df["symbol"].str.upper(),
135
+ y=df["total_volume"],
136
+ marker_color="#4ECDC4",
137
+ hovertemplate="<b>%{x}</b><br>Volume: $%{y:,.0f}<extra></extra>"
138
+ ),
139
+ row=2, col=1
140
+ )
141
+
142
+ fig.add_trace(
143
+ go.Scatter(
144
+ x=df["current_price"],
145
+ y=df["total_volume"],
146
+ mode="markers+text",
147
+ text=df["symbol"].str.upper(),
148
+ textposition="top center",
149
+ marker=dict(
150
+ size=df["market_cap"] / df["market_cap"].max() * 50 + 10,
151
+ color=df["price_change_percentage_24h"],
152
+ colorscale="RdYlGn",
153
+ colorbar=dict(title="24h Change %"),
154
+ line=dict(width=1, color="white")
155
+ ),
156
+ hovertemplate="<b>%{text}</b><br>Price: $%{x:,.4f}<br>Volume: $%{y:,.0f}<extra></extra>"
157
+ ),
158
+ row=2, col=2
159
+ )
160
+
161
+ global_info = data.get("global_data", {}).get("data", {})
162
+ total_mcap = global_info.get("total_market_cap", {}).get("usd", 0)
163
+ total_volume = global_info.get("total_volume", {}).get("usd", 0)
164
+ btc_dominance = global_info.get("market_cap_percentage", {}).get("btc", 0)
165
+
166
+ title_text = f"Crypto Market Overview - Total MCap: ${total_mcap/1e12:.2f}T | 24h Vol: ${total_volume/1e9:.0f}B | BTC Dom: {btc_dominance:.1f}%"
167
+
168
+ fig.update_layout(
169
+ title=dict(
170
+ text=title_text,
171
+ x=0.5,
172
+ font=dict(size=16, color="#FFFFFF")
173
+ ),
174
+ template="plotly_dark",
175
+ showlegend=False,
176
+ height=800,
177
+ margin=dict(l=60, r=60, t=100, b=60),
178
+ plot_bgcolor="rgba(0,0,0,0)",
179
+ paper_bgcolor="rgba(0,0,0,0)"
180
+ )
181
+
182
+ fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
183
+ fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
184
+
185
+ return fig.to_html(include_plotlyjs="cdn", div_id="market_overview")
186
+
187
+ except Exception as e:
188
+ return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Market overview failed: {str(e)}</div>"
189
+
190
+ def create_comparison_chart(coins_data: List[Dict[str, Any]]) -> str:
191
+ try:
192
+ if not coins_data:
193
+ return "<div style='padding: 20px; text-align: center;'>No comparison data available</div>"
194
+
195
+ df = pd.DataFrame(coins_data)
196
+
197
+ fig = make_subplots(
198
+ rows=1, cols=2,
199
+ subplot_titles=["Price Comparison", "Market Cap Comparison"]
200
+ )
201
+
202
+ colors = px.colors.qualitative.Set1[:len(df)]
203
+
204
+ for i, (_, coin) in enumerate(df.iterrows()):
205
+ fig.add_trace(
206
+ go.Bar(
207
+ name=coin["symbol"].upper(),
208
+ x=[coin["symbol"].upper()],
209
+ y=[coin["current_price"]],
210
+ marker_color=colors[i],
211
+ hovertemplate=f"<b>{coin['name']}</b><br>Price: $%{{y:,.4f}}<extra></extra>"
212
+ ),
213
+ row=1, col=1
214
+ )
215
+
216
+ fig.add_trace(
217
+ go.Bar(
218
+ name=coin["symbol"].upper(),
219
+ x=[coin["symbol"].upper()],
220
+ y=[coin["market_cap"]],
221
+ marker_color=colors[i],
222
+ showlegend=False,
223
+ hovertemplate=f"<b>{coin['name']}</b><br>Market Cap: $%{{y:,.0f}}<extra></extra>"
224
+ ),
225
+ row=1, col=2
226
+ )
227
+
228
+ fig.update_layout(
229
+ title="Cryptocurrency Comparison",
230
+ template="plotly_dark",
231
+ height=500,
232
+ showlegend=True
233
+ )
234
+
235
+ return fig.to_html(include_plotlyjs="cdn", div_id="comparison_chart")
236
+
237
+ except Exception as e:
238
+ return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Comparison chart failed: {str(e)}</div>"
uv.lock ADDED
The diff for this file is too large to render. See raw diff