@intentsolutionsio/flash-loan-simulator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,439 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Flash loan risk assessment engine.
4
+
5
+ Evaluates risks for flash loan strategies:
6
+ - MEV competition
7
+ - Execution risk
8
+ - Protocol risk
9
+ - Liquidity risk
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from decimal import Decimal
14
+ from enum import Enum
15
+ from typing import List, Optional
16
+
17
+ from strategy_engine import StrategyResult, StrategyType
18
+
19
+
20
+ class RiskLevel(Enum):
21
+ """Risk level classification."""
22
+
23
+ LOW = "LOW"
24
+ MEDIUM = "MEDIUM"
25
+ HIGH = "HIGH"
26
+ CRITICAL = "CRITICAL"
27
+
28
+
29
+ @dataclass
30
+ class RiskFactor:
31
+ """Individual risk factor assessment."""
32
+
33
+ name: str
34
+ level: RiskLevel
35
+ score: float # 0-100, higher = more risky
36
+ description: str
37
+ mitigation: str
38
+
39
+
40
+ @dataclass
41
+ class RiskAssessment:
42
+ """Complete risk assessment for a strategy."""
43
+
44
+ overall_level: RiskLevel
45
+ overall_score: float
46
+ viability: str # "GO", "CAUTION", "NO-GO"
47
+ factors: List[RiskFactor]
48
+ recommendations: List[str]
49
+ warnings: List[str]
50
+
51
+
52
+ class RiskAssessor:
53
+ """
54
+ Assesses risks for flash loan strategies.
55
+
56
+ Factors evaluated:
57
+ - MEV Competition: Bot activity on the pair/strategy
58
+ - Execution Risk: Slippage, timing, gas volatility
59
+ - Protocol Risk: Smart contract and oracle risks
60
+ - Liquidity Risk: Pool depth and availability
61
+ """
62
+
63
+ # MEV risk by token pair (common pairs = more competition)
64
+ MEV_COMPETITION = {
65
+ ("ETH", "USDC"): 90,
66
+ ("ETH", "USDT"): 85,
67
+ ("WBTC", "ETH"): 80,
68
+ ("ETH", "DAI"): 75,
69
+ "default": 50,
70
+ }
71
+
72
+ # Protocol risk scores (higher = riskier)
73
+ PROTOCOL_RISK = {
74
+ "aave": 15, # Well-audited, proven
75
+ "compound": 20,
76
+ "dydx": 25,
77
+ "balancer": 30,
78
+ "uniswap": 20,
79
+ "sushiswap": 35,
80
+ "curve": 25,
81
+ "default": 50,
82
+ }
83
+
84
+ def __init__(self, eth_price_usd: float = 2500.0):
85
+ """Initialize assessor."""
86
+ self.eth_price_usd = eth_price_usd
87
+
88
+ def assess(self, result: StrategyResult) -> RiskAssessment:
89
+ """
90
+ Perform complete risk assessment.
91
+
92
+ Args:
93
+ result: Strategy simulation result
94
+
95
+ Returns:
96
+ Complete risk assessment
97
+ """
98
+ factors = []
99
+ recommendations = []
100
+ warnings = []
101
+
102
+ # 1. MEV Competition Risk
103
+ mev_factor = self._assess_mev_risk(result)
104
+ factors.append(mev_factor)
105
+
106
+ # 2. Execution Risk
107
+ exec_factor = self._assess_execution_risk(result)
108
+ factors.append(exec_factor)
109
+
110
+ # 3. Protocol Risk
111
+ protocol_factor = self._assess_protocol_risk(result)
112
+ factors.append(protocol_factor)
113
+
114
+ # 4. Liquidity Risk
115
+ liquidity_factor = self._assess_liquidity_risk(result)
116
+ factors.append(liquidity_factor)
117
+
118
+ # 5. Profitability Risk
119
+ profit_factor = self._assess_profit_risk(result)
120
+ factors.append(profit_factor)
121
+
122
+ # Calculate overall score (weighted average)
123
+ weights = {
124
+ "MEV Competition": 0.30,
125
+ "Execution Risk": 0.25,
126
+ "Protocol Risk": 0.15,
127
+ "Liquidity Risk": 0.15,
128
+ "Profit Margin": 0.15,
129
+ }
130
+
131
+ overall_score = sum(
132
+ f.score * weights.get(f.name, 0.2)
133
+ for f in factors
134
+ )
135
+
136
+ # Determine overall level
137
+ if overall_score < 30:
138
+ overall_level = RiskLevel.LOW
139
+ viability = "GO"
140
+ elif overall_score < 50:
141
+ overall_level = RiskLevel.MEDIUM
142
+ viability = "CAUTION"
143
+ elif overall_score < 70:
144
+ overall_level = RiskLevel.HIGH
145
+ viability = "CAUTION"
146
+ else:
147
+ overall_level = RiskLevel.CRITICAL
148
+ viability = "NO-GO"
149
+
150
+ # Generate recommendations
151
+ recommendations = self._generate_recommendations(factors, result)
152
+
153
+ # Generate warnings
154
+ for factor in factors:
155
+ if factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
156
+ warnings.append(f"{factor.name}: {factor.description}")
157
+
158
+ return RiskAssessment(
159
+ overall_level=overall_level,
160
+ overall_score=overall_score,
161
+ viability=viability,
162
+ factors=factors,
163
+ recommendations=recommendations,
164
+ warnings=warnings,
165
+ )
166
+
167
+ def _assess_mev_risk(self, result: StrategyResult) -> RiskFactor:
168
+ """Assess MEV competition risk."""
169
+ # Higher score = more competition
170
+ pair_key = (result.loan_asset, "USDC") # Simplified
171
+
172
+ score = self.MEV_COMPETITION.get(
173
+ pair_key,
174
+ self.MEV_COMPETITION.get("default", 50)
175
+ )
176
+
177
+ # Adjust for trade size (larger = more attractive to MEV)
178
+ if result.loan_amount > 100:
179
+ score = min(100, score + 15)
180
+ elif result.loan_amount > 50:
181
+ score = min(100, score + 10)
182
+
183
+ # Adjust for profit (more profit = more competition)
184
+ if result.net_profit_usd > 500:
185
+ score = min(100, score + 10)
186
+
187
+ if score < 30:
188
+ level = RiskLevel.LOW
189
+ elif score < 50:
190
+ level = RiskLevel.MEDIUM
191
+ elif score < 70:
192
+ level = RiskLevel.HIGH
193
+ else:
194
+ level = RiskLevel.CRITICAL
195
+
196
+ return RiskFactor(
197
+ name="MEV Competition",
198
+ level=level,
199
+ score=score,
200
+ description=f"High bot activity on {result.loan_asset} pairs",
201
+ mitigation="Use Flashbots Protect or private transactions",
202
+ )
203
+
204
+ def _assess_execution_risk(self, result: StrategyResult) -> RiskFactor:
205
+ """Assess execution risk (slippage, timing)."""
206
+ # Base score from number of steps
207
+ num_steps = len(result.steps)
208
+ score = min(100, num_steps * 15)
209
+
210
+ # Adjust for gas cost relative to profit
211
+ if result.net_profit > 0:
212
+ gas_ratio = float(result.gas_cost_eth / result.net_profit)
213
+ if gas_ratio > 0.5:
214
+ score = min(100, score + 20)
215
+ elif gas_ratio > 0.3:
216
+ score = min(100, score + 10)
217
+
218
+ if score < 30:
219
+ level = RiskLevel.LOW
220
+ elif score < 50:
221
+ level = RiskLevel.MEDIUM
222
+ elif score < 70:
223
+ level = RiskLevel.HIGH
224
+ else:
225
+ level = RiskLevel.CRITICAL
226
+
227
+ return RiskFactor(
228
+ name="Execution Risk",
229
+ level=level,
230
+ score=score,
231
+ description=f"{num_steps} steps with gas-sensitive timing",
232
+ mitigation="Set tight slippage tolerance; use gas price oracles",
233
+ )
234
+
235
+ def _assess_protocol_risk(self, result: StrategyResult) -> RiskFactor:
236
+ """Assess protocol/smart contract risk."""
237
+ # Get protocols involved
238
+ protocols = set()
239
+ for step in result.steps:
240
+ protocols.add(step.protocol.lower())
241
+
242
+ # Sum risk scores
243
+ total_risk = sum(
244
+ self.PROTOCOL_RISK.get(p, self.PROTOCOL_RISK["default"])
245
+ for p in protocols
246
+ )
247
+
248
+ # Average across protocols
249
+ score = total_risk / len(protocols) if protocols else 50
250
+
251
+ if score < 25:
252
+ level = RiskLevel.LOW
253
+ elif score < 40:
254
+ level = RiskLevel.MEDIUM
255
+ elif score < 60:
256
+ level = RiskLevel.HIGH
257
+ else:
258
+ level = RiskLevel.CRITICAL
259
+
260
+ return RiskFactor(
261
+ name="Protocol Risk",
262
+ level=level,
263
+ score=score,
264
+ description=f"Using {len(protocols)} protocol(s): {', '.join(protocols)}",
265
+ mitigation="Verify protocol audits; monitor for exploits",
266
+ )
267
+
268
+ def _assess_liquidity_risk(self, result: StrategyResult) -> RiskFactor:
269
+ """Assess liquidity/slippage risk."""
270
+ # Simplified: larger trades = higher liquidity risk
271
+ trade_usd = float(result.loan_amount * Decimal(str(self.eth_price_usd)))
272
+
273
+ if trade_usd < 10000:
274
+ score = 20
275
+ elif trade_usd < 50000:
276
+ score = 35
277
+ elif trade_usd < 100000:
278
+ score = 50
279
+ elif trade_usd < 500000:
280
+ score = 70
281
+ else:
282
+ score = 90
283
+
284
+ if score < 30:
285
+ level = RiskLevel.LOW
286
+ elif score < 50:
287
+ level = RiskLevel.MEDIUM
288
+ elif score < 70:
289
+ level = RiskLevel.HIGH
290
+ else:
291
+ level = RiskLevel.CRITICAL
292
+
293
+ return RiskFactor(
294
+ name="Liquidity Risk",
295
+ level=level,
296
+ score=score,
297
+ description=f"${trade_usd:,.0f} trade may cause significant slippage",
298
+ mitigation="Check pool liquidity; consider splitting order",
299
+ )
300
+
301
+ def _assess_profit_risk(self, result: StrategyResult) -> RiskFactor:
302
+ """Assess profit margin risk."""
303
+ # Thin margins = high risk
304
+ if result.loan_amount > 0:
305
+ margin_pct = float(result.net_profit / result.loan_amount * 100)
306
+ else:
307
+ margin_pct = 0
308
+
309
+ if margin_pct < 0:
310
+ score = 100 # Negative profit = maximum risk
311
+ elif margin_pct < 0.1:
312
+ score = 80
313
+ elif margin_pct < 0.5:
314
+ score = 50
315
+ elif margin_pct < 1.0:
316
+ score = 30
317
+ else:
318
+ score = 15
319
+
320
+ if score < 30:
321
+ level = RiskLevel.LOW
322
+ elif score < 50:
323
+ level = RiskLevel.MEDIUM
324
+ elif score < 70:
325
+ level = RiskLevel.HIGH
326
+ else:
327
+ level = RiskLevel.CRITICAL
328
+
329
+ return RiskFactor(
330
+ name="Profit Margin",
331
+ level=level,
332
+ score=score,
333
+ description=f"{margin_pct:.3f}% profit margin {'(NEGATIVE!)' if margin_pct < 0 else ''}",
334
+ mitigation="Increase trade size or wait for better opportunity",
335
+ )
336
+
337
+ def _generate_recommendations(
338
+ self, factors: List[RiskFactor], result: StrategyResult
339
+ ) -> List[str]:
340
+ """Generate actionable recommendations."""
341
+ recs = []
342
+
343
+ # Check MEV risk
344
+ mev_factor = next((f for f in factors if f.name == "MEV Competition"), None)
345
+ if mev_factor and mev_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
346
+ recs.append("Use Flashbots Protect to avoid sandwich attacks")
347
+ recs.append("Consider private transaction submission")
348
+
349
+ # Check execution risk
350
+ exec_factor = next((f for f in factors if f.name == "Execution Risk"), None)
351
+ if exec_factor and exec_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
352
+ recs.append("Set tight slippage tolerance (0.5% or less)")
353
+ recs.append("Monitor gas prices before execution")
354
+
355
+ # Check profit margin
356
+ profit_factor = next((f for f in factors if f.name == "Profit Margin"), None)
357
+ if profit_factor and profit_factor.level == RiskLevel.CRITICAL:
358
+ if result.net_profit < 0:
359
+ recs.append("Strategy is UNPROFITABLE - do not execute")
360
+ else:
361
+ recs.append("Profit margin too thin - wait for better opportunity")
362
+
363
+ # Check liquidity
364
+ liq_factor = next((f for f in factors if f.name == "Liquidity Risk"), None)
365
+ if liq_factor and liq_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
366
+ recs.append("Consider splitting into smaller trades")
367
+ recs.append("Check DEX liquidity depth before execution")
368
+
369
+ # General recommendations
370
+ if not recs:
371
+ recs.append("Conditions appear favorable - proceed with caution")
372
+ recs.append("Always test on testnet first")
373
+
374
+ return recs
375
+
376
+
377
+ def demo():
378
+ """Demonstrate risk assessment."""
379
+ from strategy_engine import StrategyFactory, StrategyType, ArbitrageParams
380
+
381
+ # Run a simulation first
382
+ factory = StrategyFactory()
383
+ strategy = factory.create(StrategyType.SIMPLE_ARBITRAGE)
384
+
385
+ params = ArbitrageParams(
386
+ input_token="ETH",
387
+ output_token="USDC",
388
+ amount=Decimal("100"),
389
+ dex_buy="sushiswap",
390
+ dex_sell="uniswap",
391
+ provider="aave",
392
+ )
393
+
394
+ result = strategy.simulate(params)
395
+
396
+ # Assess risk
397
+ assessor = RiskAssessor(eth_price_usd=2500.0)
398
+ assessment = assessor.assess(result)
399
+
400
+ print("=" * 60)
401
+ print("RISK ASSESSMENT")
402
+ print("=" * 60)
403
+
404
+ print(f"\nOverall Risk: {assessment.overall_level.value}")
405
+ print(f"Risk Score: {assessment.overall_score:.0f}/100")
406
+ print(f"Viability: {assessment.viability}")
407
+
408
+ print("\n" + "-" * 60)
409
+ print("RISK FACTORS")
410
+ print("-" * 60)
411
+
412
+ for factor in assessment.factors:
413
+ indicator = {
414
+ RiskLevel.LOW: "🟢",
415
+ RiskLevel.MEDIUM: "🟡",
416
+ RiskLevel.HIGH: "🟠",
417
+ RiskLevel.CRITICAL: "🔴",
418
+ }.get(factor.level, "⚪")
419
+
420
+ print(f"\n{indicator} {factor.name}: {factor.level.value} ({factor.score:.0f})")
421
+ print(f" {factor.description}")
422
+ print(f" → {factor.mitigation}")
423
+
424
+ if assessment.warnings:
425
+ print("\n" + "-" * 60)
426
+ print("WARNINGS")
427
+ print("-" * 60)
428
+ for warning in assessment.warnings:
429
+ print(f" ⚠️ {warning}")
430
+
431
+ print("\n" + "-" * 60)
432
+ print("RECOMMENDATIONS")
433
+ print("-" * 60)
434
+ for rec in assessment.recommendations:
435
+ print(f" • {rec}")
436
+
437
+
438
+ if __name__ == "__main__":
439
+ demo()