@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,492 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Flash Loan Simulator - Main CLI Entry Point.
4
+
5
+ Simulates flash loan strategies across DeFi protocols with:
6
+ - Profitability analysis
7
+ - Gas cost estimation
8
+ - Risk assessment
9
+ - Provider comparison
10
+
11
+ Usage:
12
+ python flash_simulator.py arbitrage ETH USDC 100 --dex-buy sushiswap --dex-sell uniswap
13
+ python flash_simulator.py liquidation --protocol aave --health-factor 0.95
14
+ python flash_simulator.py triangular ETH USDC WBTC ETH --amount 50
15
+ python flash_simulator.py compare ETH 100
16
+ """
17
+
18
+ import argparse
19
+ import sys
20
+ from decimal import Decimal, InvalidOperation
21
+ from typing import Optional
22
+
23
+ from strategy_engine import (
24
+ StrategyFactory,
25
+ StrategyType,
26
+ ArbitrageParams,
27
+ TriangularArbitrageParams,
28
+ LiquidationParams,
29
+ )
30
+ from profit_calculator import ProfitCalculator
31
+ from risk_assessor import RiskAssessor
32
+ from protocol_adapters import ProviderManager
33
+ from formatters import ConsoleFormatter, JSONFormatter, MarkdownFormatter
34
+
35
+
36
+ def parse_args() -> argparse.Namespace:
37
+ """Parse command line arguments."""
38
+ parser = argparse.ArgumentParser(
39
+ description="Flash Loan Strategy Simulator",
40
+ formatter_class=argparse.RawDescriptionHelpFormatter,
41
+ epilog="""
42
+ Examples:
43
+ # Simple arbitrage simulation
44
+ %(prog)s arbitrage ETH USDC 100 --dex-buy sushiswap --dex-sell uniswap
45
+
46
+ # Triangular arbitrage
47
+ %(prog)s triangular ETH USDC WBTC ETH --amount 50
48
+
49
+ # Liquidation profitability
50
+ %(prog)s liquidation --protocol aave --health-factor 0.95
51
+
52
+ # Compare providers
53
+ %(prog)s compare ETH 100
54
+
55
+ # Full analysis with risk assessment
56
+ %(prog)s arbitrage ETH USDC 100 --full --risk-analysis
57
+
58
+ # JSON output for integration
59
+ %(prog)s arbitrage ETH USDC 100 --output json > result.json
60
+
61
+ Providers:
62
+ aave - Aave V3 (0.09% fee, multi-chain)
63
+ dydx - dYdX (0% fee, Ethereum only)
64
+ balancer - Balancer (0.01% fee, multi-chain)
65
+ uniswap - Uniswap V3 (~0.3% implicit, flash swap)
66
+
67
+ DEXes:
68
+ uniswap, sushiswap, curve, balancer, 1inch
69
+
70
+ EDUCATIONAL DISCLAIMER:
71
+ This tool is for simulation and learning only.
72
+ Flash loans carry significant risks. Never deploy
73
+ untested code. Start with testnets.
74
+ """,
75
+ )
76
+
77
+ subparsers = parser.add_subparsers(dest="command", help="Simulation type")
78
+
79
+ # Arbitrage subcommand
80
+ arb_parser = subparsers.add_parser(
81
+ "arbitrage", help="Simple two-DEX arbitrage simulation"
82
+ )
83
+ arb_parser.add_argument(
84
+ "input_token", help="Input token (e.g., ETH)"
85
+ )
86
+ arb_parser.add_argument(
87
+ "output_token", help="Output token (e.g., USDC)"
88
+ )
89
+ arb_parser.add_argument(
90
+ "amount", type=str, help="Loan amount"
91
+ )
92
+ arb_parser.add_argument(
93
+ "--dex-buy", default="sushiswap", help="DEX to buy on (default: sushiswap)"
94
+ )
95
+ arb_parser.add_argument(
96
+ "--dex-sell", default="uniswap", help="DEX to sell on (default: uniswap)"
97
+ )
98
+ arb_parser.add_argument(
99
+ "--provider", default="aave", help="Flash loan provider (default: aave)"
100
+ )
101
+
102
+ # Triangular arbitrage subcommand
103
+ tri_parser = subparsers.add_parser(
104
+ "triangular", help="Multi-hop circular arbitrage simulation"
105
+ )
106
+ tri_parser.add_argument(
107
+ "tokens", nargs="+", help="Token path (e.g., ETH USDC WBTC ETH)"
108
+ )
109
+ tri_parser.add_argument(
110
+ "--amount", type=str, required=True, help="Loan amount"
111
+ )
112
+ tri_parser.add_argument(
113
+ "--provider", default="aave", help="Flash loan provider (default: aave)"
114
+ )
115
+
116
+ # Liquidation subcommand
117
+ liq_parser = subparsers.add_parser(
118
+ "liquidation", help="Liquidation opportunity analysis"
119
+ )
120
+ liq_parser.add_argument(
121
+ "--protocol", default="aave", help="Lending protocol (default: aave)"
122
+ )
123
+ liq_parser.add_argument(
124
+ "--health-factor",
125
+ type=float,
126
+ default=0.95,
127
+ help="Health factor threshold (default: 0.95)",
128
+ )
129
+ liq_parser.add_argument(
130
+ "--collateral", default="ETH", help="Collateral asset (default: ETH)"
131
+ )
132
+ liq_parser.add_argument(
133
+ "--debt", default="USDC", help="Debt asset (default: USDC)"
134
+ )
135
+ liq_parser.add_argument(
136
+ "--amount", type=str, default="10", help="Debt amount to liquidate"
137
+ )
138
+ liq_parser.add_argument(
139
+ "--provider", default="aave", help="Flash loan provider (default: aave)"
140
+ )
141
+
142
+ # Compare providers subcommand
143
+ cmp_parser = subparsers.add_parser(
144
+ "compare", help="Compare flash loan providers"
145
+ )
146
+ cmp_parser.add_argument("asset", help="Asset to borrow (e.g., ETH)")
147
+ cmp_parser.add_argument("amount", type=str, help="Amount to borrow")
148
+ cmp_parser.add_argument(
149
+ "--chain", default="ethereum", help="Chain (default: ethereum)"
150
+ )
151
+
152
+ # Global options
153
+ for sub in [arb_parser, tri_parser, liq_parser]:
154
+ sub.add_argument(
155
+ "--compare-providers",
156
+ action="store_true",
157
+ help="Compare all providers for this strategy",
158
+ )
159
+ sub.add_argument(
160
+ "--risk-analysis",
161
+ action="store_true",
162
+ help="Include risk assessment",
163
+ )
164
+ sub.add_argument(
165
+ "--full",
166
+ action="store_true",
167
+ help="Full analysis (breakdown + risk + providers)",
168
+ )
169
+
170
+ # Output options for all subcommands
171
+ for sub in [arb_parser, tri_parser, liq_parser, cmp_parser]:
172
+ sub.add_argument(
173
+ "--output",
174
+ choices=["console", "json", "markdown"],
175
+ default="console",
176
+ help="Output format (default: console)",
177
+ )
178
+ sub.add_argument(
179
+ "--eth-price",
180
+ type=float,
181
+ default=2500.0,
182
+ help="ETH price in USD (default: 2500)",
183
+ )
184
+ sub.add_argument(
185
+ "--gas-price",
186
+ type=float,
187
+ default=30.0,
188
+ help="Gas price in gwei (default: 30)",
189
+ )
190
+
191
+ return parser.parse_args()
192
+
193
+
194
+ def run_arbitrage(args: argparse.Namespace) -> int:
195
+ """Run simple arbitrage simulation."""
196
+ try:
197
+ amount = Decimal(args.amount)
198
+ except InvalidOperation:
199
+ print(f"Error: Invalid amount '{args.amount}'", file=sys.stderr)
200
+ return 1
201
+
202
+ # Create strategy
203
+ factory = StrategyFactory()
204
+ strategy = factory.create(StrategyType.SIMPLE_ARBITRAGE)
205
+
206
+ params = ArbitrageParams(
207
+ input_token=args.input_token.upper(),
208
+ output_token=args.output_token.upper(),
209
+ amount=amount,
210
+ dex_buy=args.dex_buy.lower(),
211
+ dex_sell=args.dex_sell.lower(),
212
+ provider=args.provider.lower(),
213
+ )
214
+
215
+ # Run simulation
216
+ result = strategy.simulate(params)
217
+
218
+ # Calculate extras if needed
219
+ calculator = None
220
+ breakdown = None
221
+ assessor = None
222
+ assessment = None
223
+ providers = None
224
+
225
+ if args.full or hasattr(args, "compare_providers") and args.compare_providers:
226
+ calculator = ProfitCalculator(
227
+ eth_price_usd=args.eth_price, gas_price_gwei=args.gas_price
228
+ )
229
+ breakdown = calculator.calculate_breakdown(result)
230
+
231
+ if args.full or (hasattr(args, "risk_analysis") and args.risk_analysis):
232
+ assessor = RiskAssessor(eth_price_usd=args.eth_price)
233
+ assessment = assessor.assess(result)
234
+
235
+ if args.full or (hasattr(args, "compare_providers") and args.compare_providers):
236
+ manager = ProviderManager()
237
+ providers = manager.compare_providers(
238
+ args.input_token.upper(), amount, "ethereum"
239
+ )
240
+
241
+ # Format output
242
+ output_result(args, result, breakdown, assessment, providers)
243
+
244
+ return 0 if result.is_profitable else 1
245
+
246
+
247
+ def run_triangular(args: argparse.Namespace) -> int:
248
+ """Run triangular arbitrage simulation."""
249
+ if len(args.tokens) < 3:
250
+ print("Error: Triangular arbitrage requires at least 3 tokens", file=sys.stderr)
251
+ return 1
252
+
253
+ try:
254
+ amount = Decimal(args.amount)
255
+ except InvalidOperation:
256
+ print(f"Error: Invalid amount '{args.amount}'", file=sys.stderr)
257
+ return 1
258
+
259
+ # Create strategy
260
+ factory = StrategyFactory()
261
+ strategy = factory.create(StrategyType.TRIANGULAR_ARBITRAGE)
262
+
263
+ params = TriangularArbitrageParams(
264
+ tokens=[t.upper() for t in args.tokens],
265
+ amount=amount,
266
+ provider=args.provider.lower(),
267
+ )
268
+
269
+ # Run simulation
270
+ result = strategy.simulate(params)
271
+
272
+ # Calculate extras
273
+ calculator = None
274
+ breakdown = None
275
+ assessment = None
276
+ providers = None
277
+
278
+ if args.full:
279
+ calculator = ProfitCalculator(
280
+ eth_price_usd=args.eth_price, gas_price_gwei=args.gas_price
281
+ )
282
+ breakdown = calculator.calculate_breakdown(result)
283
+
284
+ assessor = RiskAssessor(eth_price_usd=args.eth_price)
285
+ assessment = assessor.assess(result)
286
+
287
+ manager = ProviderManager()
288
+ providers = manager.compare_providers(
289
+ args.tokens[0].upper(), amount, "ethereum"
290
+ )
291
+
292
+ if hasattr(args, "risk_analysis") and args.risk_analysis:
293
+ assessor = RiskAssessor(eth_price_usd=args.eth_price)
294
+ assessment = assessor.assess(result)
295
+
296
+ # Format output
297
+ output_result(args, result, breakdown, assessment, providers)
298
+
299
+ return 0 if result.is_profitable else 1
300
+
301
+
302
+ def run_liquidation(args: argparse.Namespace) -> int:
303
+ """Run liquidation simulation."""
304
+ try:
305
+ amount = Decimal(args.amount)
306
+ except InvalidOperation:
307
+ print(f"Error: Invalid amount '{args.amount}'", file=sys.stderr)
308
+ return 1
309
+
310
+ # Create strategy
311
+ factory = StrategyFactory()
312
+ strategy = factory.create(StrategyType.LIQUIDATION)
313
+
314
+ params = LiquidationParams(
315
+ collateral_asset=args.collateral.upper(),
316
+ debt_asset=args.debt.upper(),
317
+ debt_amount=amount,
318
+ health_factor=args.health_factor,
319
+ lending_protocol=args.protocol.lower(),
320
+ provider=args.provider.lower(),
321
+ )
322
+
323
+ # Run simulation
324
+ result = strategy.simulate(params)
325
+
326
+ # Calculate extras
327
+ calculator = None
328
+ breakdown = None
329
+ assessment = None
330
+ providers = None
331
+
332
+ if args.full:
333
+ calculator = ProfitCalculator(
334
+ eth_price_usd=args.eth_price, gas_price_gwei=args.gas_price
335
+ )
336
+ breakdown = calculator.calculate_breakdown(result)
337
+
338
+ assessor = RiskAssessor(eth_price_usd=args.eth_price)
339
+ assessment = assessor.assess(result)
340
+
341
+ manager = ProviderManager()
342
+ providers = manager.compare_providers(
343
+ args.debt.upper(), amount, "ethereum"
344
+ )
345
+
346
+ if hasattr(args, "risk_analysis") and args.risk_analysis:
347
+ assessor = RiskAssessor(eth_price_usd=args.eth_price)
348
+ assessment = assessor.assess(result)
349
+
350
+ # Format output
351
+ output_result(args, result, breakdown, assessment, providers)
352
+
353
+ return 0 if result.is_profitable else 1
354
+
355
+
356
+ def run_compare(args: argparse.Namespace) -> int:
357
+ """Run provider comparison."""
358
+ try:
359
+ amount = Decimal(args.amount)
360
+ except InvalidOperation:
361
+ print(f"Error: Invalid amount '{args.amount}'", file=sys.stderr)
362
+ return 1
363
+
364
+ manager = ProviderManager()
365
+ providers = manager.compare_providers(
366
+ args.asset.upper(), amount, args.chain.lower()
367
+ )
368
+
369
+ if not providers:
370
+ print(
371
+ f"No providers support {amount} {args.asset} on {args.chain}",
372
+ file=sys.stderr,
373
+ )
374
+ return 1
375
+
376
+ # Format output
377
+ if args.output == "json":
378
+ json_fmt = JSONFormatter()
379
+ # Create a minimal result for JSON output
380
+ output = {
381
+ "comparison": {
382
+ "asset": args.asset.upper(),
383
+ "amount": float(amount),
384
+ "chain": args.chain,
385
+ },
386
+ "providers": [
387
+ {
388
+ "name": p.name,
389
+ "fee_rate": float(p.fee_rate),
390
+ "fee_amount": float(p.fee_amount),
391
+ "max_available": float(p.max_available),
392
+ "gas_overhead": p.gas_overhead,
393
+ "supported_chains": p.supported_chains,
394
+ }
395
+ for p in providers
396
+ ],
397
+ }
398
+ import json
399
+ print(json.dumps(output, indent=2))
400
+ else:
401
+ console = ConsoleFormatter()
402
+ print(console.format_provider_comparison(providers, args.asset.upper(), amount))
403
+
404
+ return 0
405
+
406
+
407
+ def output_result(
408
+ args: argparse.Namespace,
409
+ result,
410
+ breakdown=None,
411
+ assessment=None,
412
+ providers=None,
413
+ ) -> None:
414
+ """Output simulation result in requested format."""
415
+ if args.output == "json":
416
+ json_fmt = JSONFormatter()
417
+ print(json_fmt.format_full_report(result, breakdown, assessment, providers))
418
+
419
+ elif args.output == "markdown":
420
+ md_fmt = MarkdownFormatter()
421
+ print(md_fmt.format_simulation_report(result, breakdown, assessment))
422
+
423
+ else: # console (default)
424
+ console = ConsoleFormatter()
425
+
426
+ # Always show strategy result
427
+ print(console.format_strategy_result(result))
428
+
429
+ # Show breakdown if calculated
430
+ if breakdown:
431
+ print(console.format_profit_breakdown(breakdown))
432
+
433
+ # Show risk assessment if calculated
434
+ if assessment:
435
+ print(console.format_risk_assessment(assessment))
436
+
437
+ # Show provider comparison if calculated
438
+ if providers:
439
+ print(
440
+ console.format_provider_comparison(
441
+ providers, result.loan_asset, result.loan_amount
442
+ )
443
+ )
444
+
445
+ # Always show quick summary at end
446
+ print(console.format_quick_summary(result, assessment))
447
+
448
+
449
+ def print_banner():
450
+ """Print startup banner."""
451
+ banner = """
452
+ ╔══════════════════════════════════════════════════════════════════╗
453
+ ║ FLASH LOAN SIMULATOR v1.0.0 ║
454
+ ║ ║
455
+ ║ Simulate flash loan strategies across DeFi protocols ║
456
+ ║ Providers: Aave V3 | dYdX | Balancer | Uniswap V3 ║
457
+ ║ ║
458
+ ║ ⚠️ EDUCATIONAL PURPOSES ONLY - Not financial advice ║
459
+ ╚══════════════════════════════════════════════════════════════════╝
460
+ """
461
+ print(banner, file=sys.stderr)
462
+
463
+
464
+ def main() -> int:
465
+ """Main entry point."""
466
+ args = parse_args()
467
+
468
+ if not args.command:
469
+ print_banner()
470
+ print("Use --help for usage information", file=sys.stderr)
471
+ print("\nQuick start:")
472
+ print(" flash_simulator.py arbitrage ETH USDC 100")
473
+ print(" flash_simulator.py compare ETH 100")
474
+ print(" flash_simulator.py liquidation --protocol aave")
475
+ return 1
476
+
477
+ # Run appropriate command
478
+ if args.command == "arbitrage":
479
+ return run_arbitrage(args)
480
+ elif args.command == "triangular":
481
+ return run_triangular(args)
482
+ elif args.command == "liquidation":
483
+ return run_liquidation(args)
484
+ elif args.command == "compare":
485
+ return run_compare(args)
486
+ else:
487
+ print(f"Unknown command: {args.command}", file=sys.stderr)
488
+ return 1
489
+
490
+
491
+ if __name__ == "__main__":
492
+ sys.exit(main())