tomhflau commited on
Commit
5fc6337
Β·
verified Β·
1 Parent(s): 530e642

Upload 4 files

Browse files
Files changed (4) hide show
  1. .gitattributes +35 -35
  2. .gitignore +3 -0
  3. app.py +262 -0
  4. requirements.txt +0 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ venv
2
+ __pycache__
3
+ .env
app.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, requests, gradio as gr
2
+ from smolagents import CodeAgent, GoogleSearchTool, OpenAIServerModel
3
+ from smolagents.tools import Tool
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+ ETHERSCAN_API_KEY = os.getenv("ETHERSCAN_API_KEY")
8
+ MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
9
+ SERPAPI_API_KEY = os.getenv("SERPAPI_API_KEY")
10
+
11
+ ##Smolagent
12
+ model = OpenAIServerModel(
13
+ model_id="mistral-medium-latest",
14
+ api_base="https://api.mistral.ai/v1",
15
+ api_key=MISTRAL_API_KEY
16
+ )
17
+
18
+ search_tool = GoogleSearchTool(provider="serpapi")
19
+
20
+ agent = CodeAgent(
21
+ tools=[search_tool],
22
+ model=model,
23
+ add_base_tools=False
24
+ )
25
+
26
+ ##################
27
+ ###Multi Agent####
28
+ ##################
29
+
30
+ ###Survey Agent
31
+ def classify_risk(*answers):
32
+ score_map = {
33
+ # Q1
34
+ "Short-term (0–2 yrs)": 1, "Medium-term (2–5 yrs)": 2, "Long-term (5+ yrs)": 3,
35
+ # Q2
36
+ "Very uncomfortable": 1, "Somewhat uncomfortable": 2, "Comfortable": 3,
37
+ # Q3
38
+ "Capital preservation": 1, "Moderate growth": 2, "High growth": 3,
39
+ # Q4
40
+ "Beginner": 1, "Intermediate": 2, "Advanced": 3,
41
+ # Q5
42
+ "No": 1, "Maybe": 2, "Yes": 3,
43
+ # Q6
44
+ "<10%": 1, "10–30%": 2, ">30%": 3,
45
+ # Q7
46
+ "Sell immediately": 1, "Hold": 2, "Buy more": 3,
47
+ # Q8
48
+ "Stablecoins": 1, "BTC/ETH": 2, "Meme/Altcoins": 3,
49
+ # Q9
50
+ "Daily": 3, "Weekly": 2, "Rarely": 1,
51
+ # Q10
52
+ "Safe & slow": 1, "Balanced": 2, "Fast & risky": 3
53
+ }
54
+
55
+ total = sum(score_map.get(ans, 0) for ans in answers)
56
+ avg = total / 10
57
+
58
+ if avg <= 1.6:
59
+ profile = "Conservative"
60
+ elif avg <= 2.3:
61
+ profile = "Moderate"
62
+ else:
63
+ profile = "Aggressive"
64
+
65
+ return profile, f"🧠 Your Risk Profile: **{profile}**\n\nScore: {total}/30"
66
+
67
+ ###Portfolio Import Agent
68
+ ERC20_TOKENS = {
69
+ "USDC": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
70
+ "DAI": "0x6b175474e89094c44da98b954eedeac495271d0f",
71
+ "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7",
72
+ "LINK": "0x514910771af9ca656af840dff83e8264ecf986ca"
73
+ }
74
+
75
+ def import_portfolio(address):
76
+ if not address.startswith("0x") or len(address) != 42:
77
+ return [], "❌ Invalid Ethereum address format.", None
78
+
79
+ portfolio = []
80
+
81
+ eth_url = f"https://api.etherscan.io/api?module=account&action=balance&address={address}&tag=latest&apikey={ETHERSCAN_API_KEY}"
82
+ try:
83
+ eth_resp = requests.get(eth_url).json()
84
+ eth_balance = int(eth_resp["result"]) / 1e18
85
+ portfolio.append({"asset": "ETH", "balance": eth_balance, "type": "Large-cap"})
86
+ except Exception as e:
87
+ return [], f"Failed to fetch ETH balance: {e}", None
88
+
89
+ for token_name, contract in ERC20_TOKENS.items():
90
+ token_url = f"https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress={contract}&address={address}&tag=latest&apikey={ETHERSCAN_API_KEY}"
91
+ try:
92
+ token_resp = requests.get(token_url).json()
93
+ token_balance = int(token_resp["result"]) / 1e6
94
+ if token_balance > 0:
95
+ token_type = "Stablecoin" if token_name in ["USDC", "DAI"] else "Altcoin"
96
+ portfolio.append({"asset": token_name, "balance": token_balance, "type": token_type})
97
+ except Exception:
98
+ continue
99
+
100
+ if not portfolio:
101
+ return [], "No assets found.", None
102
+
103
+ # For display and downstream
104
+ display = [[p["asset"], p["balance"]] for p in portfolio]
105
+ return display, "βœ… Portfolio fetched.", portfolio
106
+
107
+ ###Analysis Agent
108
+ def analyze_portfolio(portfolio, risk_profile):
109
+ if not portfolio:
110
+ return "❌ No portfolio data to analyze."
111
+
112
+ total_balance = sum(p["balance"] for p in portfolio)
113
+ if total_balance == 0:
114
+ return "❌ Portfolio is empty."
115
+
116
+ # Volatility estimate
117
+ vol_score = 0
118
+ for p in portfolio:
119
+ if p["type"] == "Altcoin":
120
+ vol_score += 3 * (p["balance"] / total_balance)
121
+ elif p["type"] == "Large-cap":
122
+ vol_score += 2 * (p["balance"] / total_balance)
123
+ elif p["type"] == "Stablecoin":
124
+ vol_score += 1 * (p["balance"] / total_balance)
125
+
126
+ if vol_score <= 1.5:
127
+ vol_level = "Low"
128
+ elif vol_score <= 2.2:
129
+ vol_level = "Moderate"
130
+ else:
131
+ vol_level = "High"
132
+
133
+ # Concentration check
134
+ top_asset_pct = max(p["balance"] / total_balance for p in portfolio)
135
+ concentration = "High" if top_asset_pct > 0.5 else "Moderate" if top_asset_pct > 0.3 else "Low"
136
+
137
+ # Diversification
138
+ unique_types = set(p["type"] for p in portfolio)
139
+ diversification = "High" if len(unique_types) >= 3 else "Moderate" if len(unique_types) == 2 else "Low"
140
+
141
+ # Risk alignment
142
+ alignment = "Aligned"
143
+ if risk_profile == "Conservative" and vol_level == "High":
144
+ alignment = "Not Aligned"
145
+ elif risk_profile == "Aggressive" and vol_level == "Low":
146
+ alignment = "Under Risked"
147
+
148
+ report = f"""
149
+ πŸ“Š **Portfolio Risk Analysis**
150
+ - **Volatility Level:** {vol_level}
151
+ - **Top Asset Concentration:** {concentration}
152
+ - **Diversification Level:** {diversification}
153
+ - **Risk Alignment with Profile ({risk_profile}):** {alignment}
154
+ """
155
+
156
+ return report.strip(), report.strip()
157
+
158
+ ##Expert Consulting Agent
159
+ def chat_with_advisor(user_message, chat_history, risk_profile, analysis_report):
160
+ if not risk_profile or not analysis_report:
161
+ return chat_history, chat_history + [["⚠️", "Complete survey & analysis first."]]
162
+
163
+ prompt = (
164
+ f"Risk Profile:\n{risk_profile}\n\n"
165
+ f"Portfolio Analysis:\n{analysis_report}\n\n"
166
+ f"User: {user_message}\n"
167
+ f"Advisor:"
168
+ )
169
+
170
+ try:
171
+ response = agent.run(prompt)
172
+ except Exception as e:
173
+ response = f"❌ Error during agent processing: {e}"
174
+
175
+ chat_history.append([user_message, response])
176
+ return chat_history, chat_history
177
+
178
+ #################
179
+ ### GRADIO UI ###
180
+ #################
181
+
182
+ ###Risk Survey
183
+ with gr.Blocks() as demo:
184
+ # ✨ New Title Added Here
185
+ gr.Markdown("# Web3WealthManagement")
186
+ gr.Markdown("---") # Optional: Adds a horizontal line for separation
187
+
188
+ risk_profile_state = gr.State() # 🧠 persist profile here
189
+ portfolio_state = gr.State()
190
+ chat_state = gr.State([]) # stores message history
191
+ analysis_state = gr.State() # stores the analysis string
192
+
193
+ gr.Markdown("## πŸ§ͺ Step 1: Crypto Risk Survey") # Changed to Step 1 for clarity
194
+ with gr.Row():
195
+ q1 = gr.Radio(["Short-term (0–2 yrs)", "Medium-term (2–5 yrs)", "Long-term (5+ yrs)"], label="1. What's your investment horizon?")
196
+ q2 = gr.Radio(["Very uncomfortable", "Somewhat uncomfortable", "Comfortable"], label="2. How do you feel about a 20% drop?")
197
+ q3 = gr.Radio(["Capital preservation", "Moderate growth", "High growth"], label="3. What's your goal?")
198
+ q4 = gr.Radio(["Beginner", "Intermediate", "Advanced"], label="4. Experience in crypto investing?")
199
+ q5 = gr.Radio(["No", "Maybe", "Yes"], label="5. Would you buy more after a 30% dip?")
200
+
201
+ with gr.Row():
202
+ q6 = gr.Radio(["<10%", "10–30%", ">30%"], label="6. How much of your savings would you invest in crypto?")
203
+ q7 = gr.Radio(["Sell immediately", "Hold", "Buy more"], label="7. What would you do in a crash?")
204
+ q8 = gr.Radio(["Stablecoins", "BTC/ETH", "Meme/Altcoins"], label="8. Preferred investment type?")
205
+ q9 = gr.Radio(["Daily", "Weekly", "Rarely"], label="9. How often do you check your portfolio?")
206
+ q10 = gr.Radio(["Safe & slow", "Balanced", "Fast & risky"], label="10. Preferred return strategy?")
207
+
208
+
209
+ submit_btn = gr.Button("Submit Survey")
210
+ result = gr.Markdown()
211
+
212
+ submit_btn.click(
213
+ classify_risk,
214
+ inputs=[q1, q2, q3, q4, q5, q6, q7, q8, q9, q10],
215
+ outputs=[risk_profile_state, result]
216
+ )
217
+
218
+ ### Wallet Import
219
+ gr.Markdown("## πŸ“₯ Step 2: Import Your Portfolio")
220
+ gr.Markdown("You may try with a sample wallet or enter your own Ethereum address below:")
221
+ wallet_input = gr.Textbox(label="Enter Ethereum Wallet Address")
222
+ with gr.Row():
223
+ vitalik_btn = gr.Button("Use Vitalik's Wallet")
224
+ cuban_btn = gr.Button("Use Mark Cuban's Wallet")
225
+ import_btn = gr.Button("Import Portfolio")
226
+ portfolio_output = gr.Dataframe(headers=["Asset", "Balance"], row_count=5)
227
+ import_status = gr.Markdown()
228
+ import_btn.click(
229
+ import_portfolio,
230
+ inputs=[wallet_input],
231
+ outputs=[portfolio_output, import_status, portfolio_state]
232
+ )
233
+
234
+ ### Risk Analysis
235
+ gr.Markdown("## πŸ” Step 3: Analyze Portfolio Risk")
236
+ analyze_btn = gr.Button("Run Risk Analysis")
237
+ analysis_output = gr.Markdown()
238
+
239
+ analyze_btn.click(
240
+ analyze_portfolio,
241
+ inputs=[portfolio_state, risk_profile_state],
242
+ outputs=[analysis_output, analysis_state]
243
+ )
244
+
245
+ ### Expert Followup
246
+ gr.Markdown("## πŸ’¬ Step 4: Expert Consultation Chat")
247
+
248
+
249
+ chatbot = gr.Chatbot()
250
+ user_input = gr.Textbox(label="Ask the advisor", placeholder="e.g. How do I reduce my portfolio risk?")
251
+ send_btn = gr.Button("Send")
252
+
253
+ send_btn.click(
254
+ chat_with_advisor,
255
+ inputs=[user_input, chat_state, risk_profile_state, analysis_state],
256
+ outputs=[chatbot, chat_state]
257
+ )
258
+
259
+ vitalik_btn.click(lambda: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", outputs=wallet_input)
260
+ cuban_btn.click(lambda: "0xa679c6154b8d4619Af9F83f0bF9a13A680e01eCf", outputs=wallet_input)
261
+
262
+ demo.launch()
requirements.txt ADDED
Binary file (592 Bytes). View file