@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.
- package/.claude-plugin/plugin.json +22 -0
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/agents/flashloan-agent.md +261 -0
- package/package.json +43 -0
- package/skills/simulating-flash-loans/ARD.md +454 -0
- package/skills/simulating-flash-loans/PRD.md +239 -0
- package/skills/simulating-flash-loans/SKILL.md +109 -0
- package/skills/simulating-flash-loans/config/settings.yaml +241 -0
- package/skills/simulating-flash-loans/references/errors.md +297 -0
- package/skills/simulating-flash-loans/references/examples.md +532 -0
- package/skills/simulating-flash-loans/references/implementation.md +73 -0
- package/skills/simulating-flash-loans/scripts/flash_simulator.py +492 -0
- package/skills/simulating-flash-loans/scripts/formatters.py +512 -0
- package/skills/simulating-flash-loans/scripts/profit_calculator.py +315 -0
- package/skills/simulating-flash-loans/scripts/protocol_adapters.py +416 -0
- package/skills/simulating-flash-loans/scripts/risk_assessor.py +439 -0
- package/skills/simulating-flash-loans/scripts/strategy_engine.py +596 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Flash loan strategy simulation engine.
|
|
4
|
+
|
|
5
|
+
Implements various flash loan strategies:
|
|
6
|
+
- Simple arbitrage (2 DEX)
|
|
7
|
+
- Triangular arbitrage (3+ DEX)
|
|
8
|
+
- Liquidation
|
|
9
|
+
- Collateral swap
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from decimal import Decimal
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import List, Optional, Dict, Any
|
|
17
|
+
|
|
18
|
+
from protocol_adapters import ProviderManager, FlashLoanProvider
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StrategyType(Enum):
|
|
22
|
+
"""Types of flash loan strategies."""
|
|
23
|
+
|
|
24
|
+
SIMPLE_ARBITRAGE = "arbitrage"
|
|
25
|
+
TRIANGULAR_ARBITRAGE = "triangular"
|
|
26
|
+
LIQUIDATION = "liquidation"
|
|
27
|
+
COLLATERAL_SWAP = "collateral_swap"
|
|
28
|
+
SELF_LIQUIDATION = "self_liquidation"
|
|
29
|
+
DEBT_REFINANCING = "refinancing"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DEXPrice:
|
|
34
|
+
"""Price quote from a DEX."""
|
|
35
|
+
|
|
36
|
+
dex_name: str
|
|
37
|
+
input_token: str
|
|
38
|
+
output_token: str
|
|
39
|
+
input_amount: Decimal
|
|
40
|
+
output_amount: Decimal
|
|
41
|
+
price: Decimal # output per input
|
|
42
|
+
price_impact: float # percentage
|
|
43
|
+
liquidity: Decimal # available liquidity
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class TransactionStep:
|
|
48
|
+
"""Single step in a flash loan transaction."""
|
|
49
|
+
|
|
50
|
+
step_number: int
|
|
51
|
+
action: str # "borrow", "swap", "repay", etc.
|
|
52
|
+
protocol: str
|
|
53
|
+
input_token: str
|
|
54
|
+
input_amount: Decimal
|
|
55
|
+
output_token: str
|
|
56
|
+
output_amount: Decimal
|
|
57
|
+
fee: Decimal
|
|
58
|
+
gas_estimate: int
|
|
59
|
+
description: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class StrategyResult:
|
|
64
|
+
"""Result of a strategy simulation."""
|
|
65
|
+
|
|
66
|
+
strategy_type: StrategyType
|
|
67
|
+
success: bool
|
|
68
|
+
steps: List[TransactionStep]
|
|
69
|
+
loan_amount: Decimal
|
|
70
|
+
loan_asset: str
|
|
71
|
+
loan_provider: str
|
|
72
|
+
loan_fee: Decimal
|
|
73
|
+
gross_profit: Decimal
|
|
74
|
+
total_fees: Decimal
|
|
75
|
+
gas_cost_eth: Decimal
|
|
76
|
+
gas_cost_usd: Decimal
|
|
77
|
+
net_profit: Decimal
|
|
78
|
+
net_profit_usd: Decimal
|
|
79
|
+
roi_percent: float
|
|
80
|
+
execution_path: str
|
|
81
|
+
warnings: List[str] = field(default_factory=list)
|
|
82
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ArbitrageParams:
|
|
87
|
+
"""Parameters for arbitrage simulation."""
|
|
88
|
+
|
|
89
|
+
input_token: str
|
|
90
|
+
output_token: str
|
|
91
|
+
amount: Decimal
|
|
92
|
+
dex_buy: str # DEX with lower price (buy here)
|
|
93
|
+
dex_sell: str # DEX with higher price (sell here)
|
|
94
|
+
provider: str = "aave"
|
|
95
|
+
chain: str = "ethereum"
|
|
96
|
+
slippage: float = 0.5 # percentage
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class LiquidationParams:
|
|
101
|
+
"""Parameters for liquidation simulation."""
|
|
102
|
+
|
|
103
|
+
protocol: str # "aave", "compound"
|
|
104
|
+
borrower: Optional[str] = None
|
|
105
|
+
health_factor_threshold: float = 1.0
|
|
106
|
+
debt_asset: str = "USDC"
|
|
107
|
+
collateral_asset: str = "ETH"
|
|
108
|
+
provider: str = "aave"
|
|
109
|
+
chain: str = "ethereum"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class FlashLoanStrategy(ABC):
|
|
113
|
+
"""Abstract base class for flash loan strategies."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, provider_manager: ProviderManager):
|
|
116
|
+
"""Initialize with provider manager."""
|
|
117
|
+
self.provider_manager = provider_manager
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def strategy_type(self) -> StrategyType:
|
|
122
|
+
"""Get strategy type."""
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def simulate(self, params: Any) -> StrategyResult:
|
|
127
|
+
"""Run strategy simulation."""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
def get_provider(self, name: str) -> Optional[FlashLoanProvider]:
|
|
131
|
+
"""Get flash loan provider by name."""
|
|
132
|
+
return self.provider_manager.get_provider(name)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class SimpleArbitrageStrategy(FlashLoanStrategy):
|
|
136
|
+
"""
|
|
137
|
+
Simple two-DEX arbitrage strategy.
|
|
138
|
+
|
|
139
|
+
1. Flash borrow asset A
|
|
140
|
+
2. Sell A for B on DEX with high A price
|
|
141
|
+
3. Buy A with B on DEX with low A price
|
|
142
|
+
4. Repay flash loan + fee
|
|
143
|
+
5. Keep profit
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
# Mock DEX prices for simulation
|
|
147
|
+
# In production, these would be fetched from actual DEXs
|
|
148
|
+
MOCK_PRICES = {
|
|
149
|
+
"uniswap": {
|
|
150
|
+
("ETH", "USDC"): Decimal("2543.22"),
|
|
151
|
+
("USDC", "ETH"): Decimal("1") / Decimal("2543.22"),
|
|
152
|
+
("WBTC", "ETH"): Decimal("15.5"),
|
|
153
|
+
("ETH", "WBTC"): Decimal("1") / Decimal("15.5"),
|
|
154
|
+
},
|
|
155
|
+
"sushiswap": {
|
|
156
|
+
("ETH", "USDC"): Decimal("2538.50"),
|
|
157
|
+
("USDC", "ETH"): Decimal("1") / Decimal("2538.50"),
|
|
158
|
+
("WBTC", "ETH"): Decimal("15.48"),
|
|
159
|
+
("ETH", "WBTC"): Decimal("1") / Decimal("15.48"),
|
|
160
|
+
},
|
|
161
|
+
"curve": {
|
|
162
|
+
("ETH", "USDC"): Decimal("2540.00"),
|
|
163
|
+
("USDC", "ETH"): Decimal("1") / Decimal("2540.00"),
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Gas costs per DEX
|
|
168
|
+
DEX_GAS = {
|
|
169
|
+
"uniswap": 150000,
|
|
170
|
+
"sushiswap": 140000,
|
|
171
|
+
"curve": 200000,
|
|
172
|
+
"balancer": 180000,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def strategy_type(self) -> StrategyType:
|
|
177
|
+
return StrategyType.SIMPLE_ARBITRAGE
|
|
178
|
+
|
|
179
|
+
def get_dex_price(
|
|
180
|
+
self, dex: str, from_token: str, to_token: str
|
|
181
|
+
) -> Optional[Decimal]:
|
|
182
|
+
"""Get price from DEX (mock data)."""
|
|
183
|
+
dex_prices = self.MOCK_PRICES.get(dex.lower(), {})
|
|
184
|
+
return dex_prices.get((from_token.upper(), to_token.upper()))
|
|
185
|
+
|
|
186
|
+
def simulate(self, params: ArbitrageParams) -> StrategyResult:
|
|
187
|
+
"""
|
|
188
|
+
Simulate simple arbitrage.
|
|
189
|
+
|
|
190
|
+
Flow:
|
|
191
|
+
1. Borrow {amount} {input_token} from flash loan
|
|
192
|
+
2. Sell on {dex_sell} → get {output_token}
|
|
193
|
+
3. Buy on {dex_buy} → get back {input_token}
|
|
194
|
+
4. Repay loan + fee
|
|
195
|
+
"""
|
|
196
|
+
steps = []
|
|
197
|
+
warnings = []
|
|
198
|
+
|
|
199
|
+
# Get provider
|
|
200
|
+
provider = self.get_provider(params.provider)
|
|
201
|
+
if not provider:
|
|
202
|
+
return self._error_result(f"Unknown provider: {params.provider}")
|
|
203
|
+
|
|
204
|
+
# Step 1: Flash borrow
|
|
205
|
+
loan_fee = provider.get_fee(params.input_token, params.amount)
|
|
206
|
+
steps.append(
|
|
207
|
+
TransactionStep(
|
|
208
|
+
step_number=1,
|
|
209
|
+
action="flash_borrow",
|
|
210
|
+
protocol=provider.name,
|
|
211
|
+
input_token=params.input_token,
|
|
212
|
+
input_amount=Decimal("0"),
|
|
213
|
+
output_token=params.input_token,
|
|
214
|
+
output_amount=params.amount,
|
|
215
|
+
fee=loan_fee,
|
|
216
|
+
gas_estimate=provider.get_gas_overhead(),
|
|
217
|
+
description=f"Flash borrow {params.amount} {params.input_token} from {provider.name}",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Step 2: Sell on high-price DEX
|
|
222
|
+
sell_price = self.get_dex_price(
|
|
223
|
+
params.dex_sell, params.input_token, params.output_token
|
|
224
|
+
)
|
|
225
|
+
if not sell_price:
|
|
226
|
+
warnings.append(f"No price data for {params.dex_sell}")
|
|
227
|
+
sell_price = Decimal("2540") # Fallback
|
|
228
|
+
|
|
229
|
+
sell_output = params.amount * sell_price
|
|
230
|
+
sell_gas = self.DEX_GAS.get(params.dex_sell.lower(), 150000)
|
|
231
|
+
|
|
232
|
+
steps.append(
|
|
233
|
+
TransactionStep(
|
|
234
|
+
step_number=2,
|
|
235
|
+
action="swap",
|
|
236
|
+
protocol=params.dex_sell,
|
|
237
|
+
input_token=params.input_token,
|
|
238
|
+
input_amount=params.amount,
|
|
239
|
+
output_token=params.output_token,
|
|
240
|
+
output_amount=sell_output,
|
|
241
|
+
fee=Decimal("0"), # DEX fee included in price
|
|
242
|
+
gas_estimate=sell_gas,
|
|
243
|
+
description=f"Sell {params.amount} {params.input_token} on {params.dex_sell}",
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Step 3: Buy on low-price DEX
|
|
248
|
+
buy_price = self.get_dex_price(
|
|
249
|
+
params.dex_buy, params.output_token, params.input_token
|
|
250
|
+
)
|
|
251
|
+
if not buy_price:
|
|
252
|
+
warnings.append(f"No price data for {params.dex_buy}")
|
|
253
|
+
buy_price = Decimal("1") / Decimal("2538") # Fallback
|
|
254
|
+
|
|
255
|
+
buy_output = sell_output * buy_price
|
|
256
|
+
buy_gas = self.DEX_GAS.get(params.dex_buy.lower(), 150000)
|
|
257
|
+
|
|
258
|
+
steps.append(
|
|
259
|
+
TransactionStep(
|
|
260
|
+
step_number=3,
|
|
261
|
+
action="swap",
|
|
262
|
+
protocol=params.dex_buy,
|
|
263
|
+
input_token=params.output_token,
|
|
264
|
+
input_amount=sell_output,
|
|
265
|
+
output_token=params.input_token,
|
|
266
|
+
output_amount=buy_output,
|
|
267
|
+
fee=Decimal("0"),
|
|
268
|
+
gas_estimate=buy_gas,
|
|
269
|
+
description=f"Buy {params.input_token} on {params.dex_buy}",
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Step 4: Repay flash loan
|
|
274
|
+
repay_amount = params.amount + loan_fee
|
|
275
|
+
steps.append(
|
|
276
|
+
TransactionStep(
|
|
277
|
+
step_number=4,
|
|
278
|
+
action="flash_repay",
|
|
279
|
+
protocol=provider.name,
|
|
280
|
+
input_token=params.input_token,
|
|
281
|
+
input_amount=repay_amount,
|
|
282
|
+
output_token=params.input_token,
|
|
283
|
+
output_amount=Decimal("0"),
|
|
284
|
+
fee=Decimal("0"),
|
|
285
|
+
gas_estimate=50000,
|
|
286
|
+
description=f"Repay {repay_amount} {params.input_token} to {provider.name}",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Calculate profit
|
|
291
|
+
gross_profit = buy_output - params.amount
|
|
292
|
+
total_gas = sum(s.gas_estimate for s in steps)
|
|
293
|
+
|
|
294
|
+
# Assume 30 gwei gas price and $2500 ETH
|
|
295
|
+
gas_price_gwei = 30
|
|
296
|
+
eth_price = Decimal("2500")
|
|
297
|
+
gas_cost_eth = Decimal(total_gas * gas_price_gwei) / Decimal("1e9")
|
|
298
|
+
gas_cost_usd = gas_cost_eth * eth_price
|
|
299
|
+
|
|
300
|
+
net_profit = gross_profit - loan_fee - gas_cost_eth
|
|
301
|
+
net_profit_usd = net_profit * eth_price
|
|
302
|
+
|
|
303
|
+
# Check profitability
|
|
304
|
+
if net_profit < 0:
|
|
305
|
+
warnings.append("Strategy is NOT profitable after costs")
|
|
306
|
+
|
|
307
|
+
roi = float(net_profit / params.amount * 100) if params.amount > 0 else 0
|
|
308
|
+
|
|
309
|
+
return StrategyResult(
|
|
310
|
+
strategy_type=self.strategy_type,
|
|
311
|
+
success=net_profit > 0,
|
|
312
|
+
steps=steps,
|
|
313
|
+
loan_amount=params.amount,
|
|
314
|
+
loan_asset=params.input_token,
|
|
315
|
+
loan_provider=provider.name,
|
|
316
|
+
loan_fee=loan_fee,
|
|
317
|
+
gross_profit=gross_profit,
|
|
318
|
+
total_fees=loan_fee,
|
|
319
|
+
gas_cost_eth=gas_cost_eth,
|
|
320
|
+
gas_cost_usd=gas_cost_usd,
|
|
321
|
+
net_profit=net_profit,
|
|
322
|
+
net_profit_usd=net_profit_usd,
|
|
323
|
+
roi_percent=roi,
|
|
324
|
+
execution_path=f"{params.dex_sell} → {params.dex_buy}",
|
|
325
|
+
warnings=warnings,
|
|
326
|
+
metadata={
|
|
327
|
+
"sell_price": float(sell_price),
|
|
328
|
+
"buy_price": float(buy_price),
|
|
329
|
+
"price_spread": float((sell_price - Decimal("1") / buy_price) / sell_price * 100),
|
|
330
|
+
},
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def _error_result(self, message: str) -> StrategyResult:
|
|
334
|
+
"""Create error result."""
|
|
335
|
+
return StrategyResult(
|
|
336
|
+
strategy_type=self.strategy_type,
|
|
337
|
+
success=False,
|
|
338
|
+
steps=[],
|
|
339
|
+
loan_amount=Decimal("0"),
|
|
340
|
+
loan_asset="",
|
|
341
|
+
loan_provider="",
|
|
342
|
+
loan_fee=Decimal("0"),
|
|
343
|
+
gross_profit=Decimal("0"),
|
|
344
|
+
total_fees=Decimal("0"),
|
|
345
|
+
gas_cost_eth=Decimal("0"),
|
|
346
|
+
gas_cost_usd=Decimal("0"),
|
|
347
|
+
net_profit=Decimal("0"),
|
|
348
|
+
net_profit_usd=Decimal("0"),
|
|
349
|
+
roi_percent=0,
|
|
350
|
+
execution_path="",
|
|
351
|
+
warnings=[message],
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class LiquidationStrategy(FlashLoanStrategy):
|
|
356
|
+
"""
|
|
357
|
+
Liquidation strategy using flash loans.
|
|
358
|
+
|
|
359
|
+
1. Flash borrow debt asset
|
|
360
|
+
2. Repay borrower's debt on lending protocol
|
|
361
|
+
3. Receive collateral + liquidation bonus
|
|
362
|
+
4. Swap collateral back to debt asset
|
|
363
|
+
5. Repay flash loan
|
|
364
|
+
6. Keep liquidation bonus
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
# Liquidation bonuses by protocol
|
|
368
|
+
LIQUIDATION_BONUSES = {
|
|
369
|
+
"aave": Decimal("0.05"), # 5% bonus
|
|
370
|
+
"compound": Decimal("0.08"), # 8% bonus
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def strategy_type(self) -> StrategyType:
|
|
375
|
+
return StrategyType.LIQUIDATION
|
|
376
|
+
|
|
377
|
+
def simulate(self, params: LiquidationParams) -> StrategyResult:
|
|
378
|
+
"""Simulate liquidation strategy."""
|
|
379
|
+
steps = []
|
|
380
|
+
warnings = []
|
|
381
|
+
|
|
382
|
+
# Get provider
|
|
383
|
+
provider = self.get_provider(params.provider)
|
|
384
|
+
if not provider:
|
|
385
|
+
return self._error_result(f"Unknown provider: {params.provider}")
|
|
386
|
+
|
|
387
|
+
# Mock position data
|
|
388
|
+
# In production, this would be fetched from the lending protocol
|
|
389
|
+
debt_amount = Decimal("10000") # 10K USDC debt
|
|
390
|
+
collateral_amount = Decimal("5") # 5 ETH collateral
|
|
391
|
+
collateral_price = Decimal("2500") # $2500/ETH
|
|
392
|
+
collateral_value = collateral_amount * collateral_price # $12,500
|
|
393
|
+
|
|
394
|
+
liquidation_bonus = self.LIQUIDATION_BONUSES.get(
|
|
395
|
+
params.protocol.lower(), Decimal("0.05")
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Step 1: Flash borrow debt asset
|
|
399
|
+
loan_fee = provider.get_fee(params.debt_asset, debt_amount)
|
|
400
|
+
steps.append(
|
|
401
|
+
TransactionStep(
|
|
402
|
+
step_number=1,
|
|
403
|
+
action="flash_borrow",
|
|
404
|
+
protocol=provider.name,
|
|
405
|
+
input_token=params.debt_asset,
|
|
406
|
+
input_amount=Decimal("0"),
|
|
407
|
+
output_token=params.debt_asset,
|
|
408
|
+
output_amount=debt_amount,
|
|
409
|
+
fee=loan_fee,
|
|
410
|
+
gas_estimate=provider.get_gas_overhead(),
|
|
411
|
+
description=f"Flash borrow {debt_amount} {params.debt_asset}",
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Step 2: Liquidate position
|
|
416
|
+
collateral_received = collateral_amount * (1 + liquidation_bonus)
|
|
417
|
+
steps.append(
|
|
418
|
+
TransactionStep(
|
|
419
|
+
step_number=2,
|
|
420
|
+
action="liquidate",
|
|
421
|
+
protocol=params.protocol,
|
|
422
|
+
input_token=params.debt_asset,
|
|
423
|
+
input_amount=debt_amount,
|
|
424
|
+
output_token=params.collateral_asset,
|
|
425
|
+
output_amount=collateral_received,
|
|
426
|
+
fee=Decimal("0"),
|
|
427
|
+
gas_estimate=300000,
|
|
428
|
+
description=f"Liquidate position, receive {collateral_received} {params.collateral_asset}",
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Step 3: Swap collateral to debt asset
|
|
433
|
+
swap_output = collateral_received * collateral_price
|
|
434
|
+
steps.append(
|
|
435
|
+
TransactionStep(
|
|
436
|
+
step_number=3,
|
|
437
|
+
action="swap",
|
|
438
|
+
protocol="Uniswap",
|
|
439
|
+
input_token=params.collateral_asset,
|
|
440
|
+
input_amount=collateral_received,
|
|
441
|
+
output_token=params.debt_asset,
|
|
442
|
+
output_amount=swap_output,
|
|
443
|
+
fee=Decimal("0"),
|
|
444
|
+
gas_estimate=150000,
|
|
445
|
+
description=f"Swap {params.collateral_asset} to {params.debt_asset}",
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Step 4: Repay flash loan
|
|
450
|
+
repay_amount = debt_amount + loan_fee
|
|
451
|
+
steps.append(
|
|
452
|
+
TransactionStep(
|
|
453
|
+
step_number=4,
|
|
454
|
+
action="flash_repay",
|
|
455
|
+
protocol=provider.name,
|
|
456
|
+
input_token=params.debt_asset,
|
|
457
|
+
input_amount=repay_amount,
|
|
458
|
+
output_token=params.debt_asset,
|
|
459
|
+
output_amount=Decimal("0"),
|
|
460
|
+
fee=Decimal("0"),
|
|
461
|
+
gas_estimate=50000,
|
|
462
|
+
description=f"Repay flash loan",
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Calculate profit
|
|
467
|
+
gross_profit = swap_output - debt_amount
|
|
468
|
+
total_gas = sum(s.gas_estimate for s in steps)
|
|
469
|
+
|
|
470
|
+
gas_price_gwei = 30
|
|
471
|
+
eth_price = Decimal("2500")
|
|
472
|
+
gas_cost_eth = Decimal(total_gas * gas_price_gwei) / Decimal("1e9")
|
|
473
|
+
gas_cost_usd = gas_cost_eth * eth_price
|
|
474
|
+
|
|
475
|
+
# For USDC-denominated profit
|
|
476
|
+
gas_cost_in_debt = gas_cost_usd
|
|
477
|
+
net_profit = gross_profit - loan_fee - gas_cost_in_debt
|
|
478
|
+
net_profit_usd = net_profit # Already in USD
|
|
479
|
+
|
|
480
|
+
roi = float(net_profit / debt_amount * 100) if debt_amount > 0 else 0
|
|
481
|
+
|
|
482
|
+
if net_profit < 0:
|
|
483
|
+
warnings.append("Liquidation not profitable after costs")
|
|
484
|
+
|
|
485
|
+
return StrategyResult(
|
|
486
|
+
strategy_type=self.strategy_type,
|
|
487
|
+
success=net_profit > 0,
|
|
488
|
+
steps=steps,
|
|
489
|
+
loan_amount=debt_amount,
|
|
490
|
+
loan_asset=params.debt_asset,
|
|
491
|
+
loan_provider=provider.name,
|
|
492
|
+
loan_fee=loan_fee,
|
|
493
|
+
gross_profit=gross_profit,
|
|
494
|
+
total_fees=loan_fee,
|
|
495
|
+
gas_cost_eth=gas_cost_eth,
|
|
496
|
+
gas_cost_usd=gas_cost_usd,
|
|
497
|
+
net_profit=net_profit,
|
|
498
|
+
net_profit_usd=net_profit_usd,
|
|
499
|
+
roi_percent=roi,
|
|
500
|
+
execution_path=f"{params.protocol} liquidation",
|
|
501
|
+
warnings=warnings,
|
|
502
|
+
metadata={
|
|
503
|
+
"debt_amount": float(debt_amount),
|
|
504
|
+
"collateral_received": float(collateral_received),
|
|
505
|
+
"liquidation_bonus": float(liquidation_bonus * 100),
|
|
506
|
+
},
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
def _error_result(self, message: str) -> StrategyResult:
|
|
510
|
+
"""Create error result."""
|
|
511
|
+
return StrategyResult(
|
|
512
|
+
strategy_type=self.strategy_type,
|
|
513
|
+
success=False,
|
|
514
|
+
steps=[],
|
|
515
|
+
loan_amount=Decimal("0"),
|
|
516
|
+
loan_asset="",
|
|
517
|
+
loan_provider="",
|
|
518
|
+
loan_fee=Decimal("0"),
|
|
519
|
+
gross_profit=Decimal("0"),
|
|
520
|
+
total_fees=Decimal("0"),
|
|
521
|
+
gas_cost_eth=Decimal("0"),
|
|
522
|
+
gas_cost_usd=Decimal("0"),
|
|
523
|
+
net_profit=Decimal("0"),
|
|
524
|
+
net_profit_usd=Decimal("0"),
|
|
525
|
+
roi_percent=0,
|
|
526
|
+
execution_path="",
|
|
527
|
+
warnings=[message],
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class StrategyFactory:
|
|
532
|
+
"""Factory for creating strategy instances."""
|
|
533
|
+
|
|
534
|
+
def __init__(self):
|
|
535
|
+
"""Initialize with provider manager."""
|
|
536
|
+
self.provider_manager = ProviderManager()
|
|
537
|
+
self._strategies = {
|
|
538
|
+
StrategyType.SIMPLE_ARBITRAGE: SimpleArbitrageStrategy,
|
|
539
|
+
StrategyType.LIQUIDATION: LiquidationStrategy,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
def create(self, strategy_type: StrategyType) -> Optional[FlashLoanStrategy]:
|
|
543
|
+
"""Create strategy instance."""
|
|
544
|
+
strategy_class = self._strategies.get(strategy_type)
|
|
545
|
+
if strategy_class:
|
|
546
|
+
return strategy_class(self.provider_manager)
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
def list_strategies(self) -> List[StrategyType]:
|
|
550
|
+
"""List available strategies."""
|
|
551
|
+
return list(self._strategies.keys())
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def demo():
|
|
555
|
+
"""Demonstrate strategy simulation."""
|
|
556
|
+
factory = StrategyFactory()
|
|
557
|
+
|
|
558
|
+
print("=" * 60)
|
|
559
|
+
print("FLASH LOAN STRATEGY SIMULATION")
|
|
560
|
+
print("=" * 60)
|
|
561
|
+
|
|
562
|
+
# Simulate simple arbitrage
|
|
563
|
+
arbitrage = factory.create(StrategyType.SIMPLE_ARBITRAGE)
|
|
564
|
+
if arbitrage:
|
|
565
|
+
params = ArbitrageParams(
|
|
566
|
+
input_token="ETH",
|
|
567
|
+
output_token="USDC",
|
|
568
|
+
amount=Decimal("100"),
|
|
569
|
+
dex_buy="sushiswap",
|
|
570
|
+
dex_sell="uniswap",
|
|
571
|
+
provider="aave",
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
result = arbitrage.simulate(params)
|
|
575
|
+
|
|
576
|
+
print(f"\nStrategy: {result.strategy_type.value}")
|
|
577
|
+
print(f"Loan: {result.loan_amount} {result.loan_asset} from {result.loan_provider}")
|
|
578
|
+
print(f"Path: {result.execution_path}")
|
|
579
|
+
print("-" * 40)
|
|
580
|
+
print(f"Gross Profit: {result.gross_profit:.6f} {result.loan_asset}")
|
|
581
|
+
print(f"Loan Fee: -{result.loan_fee:.6f} {result.loan_asset}")
|
|
582
|
+
print(f"Gas Cost: -{result.gas_cost_eth:.6f} ETH (${result.gas_cost_usd:.2f})")
|
|
583
|
+
print("-" * 40)
|
|
584
|
+
print(f"Net Profit: {result.net_profit:.6f} {result.loan_asset}")
|
|
585
|
+
print(f" (${result.net_profit_usd:.2f})")
|
|
586
|
+
print(f"ROI: {result.roi_percent:.4f}%")
|
|
587
|
+
print(f"Profitable: {'YES' if result.success else 'NO'}")
|
|
588
|
+
|
|
589
|
+
if result.warnings:
|
|
590
|
+
print("\nWarnings:")
|
|
591
|
+
for w in result.warnings:
|
|
592
|
+
print(f" ⚠️ {w}")
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
if __name__ == "__main__":
|
|
596
|
+
demo()
|