@intentsolutionsio/flash-loan-simulator 1.0.0 → 1.0.8

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.
@@ -9,13 +9,12 @@ Handles all output formatting:
9
9
  """
10
10
 
11
11
  import json
12
- from dataclasses import asdict
13
12
  from decimal import Decimal
14
- from typing import Any, Dict, List, Optional
13
+ from typing import List, Optional
15
14
 
16
- from strategy_engine import StrategyResult, TransactionStep, StrategyType
17
- from profit_calculator import ProfitBreakdown, GasEstimate
18
- from risk_assessor import RiskAssessment, RiskFactor, RiskLevel
15
+ from strategy_engine import StrategyResult, StrategyType
16
+ from profit_calculator import ProfitBreakdown
17
+ from risk_assessor import RiskAssessment, RiskLevel
19
18
  from protocol_adapters import ProviderInfo
20
19
 
21
20
 
@@ -79,8 +78,7 @@ class ConsoleFormatter:
79
78
  border_left = (inner_width - len(title_part)) // 2
80
79
  border_right = inner_width - len(title_part) - border_left
81
80
  result.append(
82
- f"{self.BOX_TL}{self.BOX_H * border_left}{title_part}"
83
- f"{self.BOX_H * border_right}{self.BOX_TR}"
81
+ f"{self.BOX_TL}{self.BOX_H * border_left}{title_part}{self.BOX_H * border_right}{self.BOX_TR}"
84
82
  )
85
83
  else:
86
84
  result.append(f"{self.BOX_TL}{self.BOX_H * (inner_width + 2)}{self.BOX_TR}")
@@ -126,14 +124,9 @@ class ConsoleFormatter:
126
124
  lines.append(self._subheader("PROFIT BREAKDOWN"))
127
125
  lines.append(f" Gross Profit: {result.gross_profit:+.6f} {result.loan_asset}")
128
126
  lines.append(f" Flash Loan Fee: -{result.loan_fee:.6f} {result.loan_asset}")
129
- lines.append(
130
- f" Gas Cost: -{result.gas_cost_eth:.6f} ETH (${result.gas_cost_usd:.2f})"
131
- )
127
+ lines.append(f" Gas Cost: -{result.gas_cost_eth:.6f} ETH (${result.gas_cost_usd:.2f})")
132
128
  lines.append(f" {'-' * 40}")
133
- lines.append(
134
- f" Net Profit: {result.net_profit:+.6f} {result.loan_asset} "
135
- f"(${result.net_profit_usd:+.2f})"
136
- )
129
+ lines.append(f" Net Profit: {result.net_profit:+.6f} {result.loan_asset} (${result.net_profit_usd:+.2f})")
137
130
  lines.append(f" ROI: {result.roi_percent:.4f}%")
138
131
 
139
132
  return "\n".join(lines)
@@ -150,20 +143,14 @@ class ConsoleFormatter:
150
143
  # Costs
151
144
  lines.append("\n Costs:")
152
145
  lines.append(f" Flash Loan Fee: -{breakdown.flash_loan_fee:.6f} ETH")
153
- lines.append(
154
- f" Gas Cost: -{breakdown.gas_cost_eth:.6f} ETH "
155
- f"(${breakdown.gas_cost_usd:.2f})"
156
- )
146
+ lines.append(f" Gas Cost: -{breakdown.gas_cost_eth:.6f} ETH (${breakdown.gas_cost_usd:.2f})")
157
147
  lines.append(f" Est. Slippage: -{breakdown.slippage_cost:.6f} ETH")
158
148
  lines.append(f" DEX Fees: ~{breakdown.dex_fees:.6f} ETH (in price)")
159
149
  lines.append(f" {'-' * 45}")
160
150
  lines.append(f" Total Costs: -{breakdown.total_costs:.6f} ETH")
161
151
 
162
152
  # Net
163
- lines.append(
164
- f"\n Net Profit: {breakdown.net_profit:+.6f} ETH "
165
- f"(${breakdown.net_profit_usd:+.2f})"
166
- )
153
+ lines.append(f"\n Net Profit: {breakdown.net_profit:+.6f} ETH (${breakdown.net_profit_usd:+.2f})")
167
154
  lines.append(f" ROI: {breakdown.roi_percent:.4f}%")
168
155
  lines.append(f" Breakeven Gas: {breakdown.breakeven_gas_price:.1f} gwei")
169
156
 
@@ -182,9 +169,7 @@ class ConsoleFormatter:
182
169
  lines.append(self._header("RISK ASSESSMENT"))
183
170
 
184
171
  # Overall rating
185
- indicator, label = self.RISK_INDICATORS.get(
186
- assessment.overall_level, ("⚪", "UNKNOWN")
187
- )
172
+ indicator, label = self.RISK_INDICATORS.get(assessment.overall_level, ("⚪", "UNKNOWN"))
188
173
  lines.append(f"\n Overall Risk: {indicator} {label}")
189
174
  lines.append(f" Risk Score: {assessment.overall_score:.0f}/100")
190
175
  lines.append(f" Viability: {assessment.viability}")
@@ -211,9 +196,7 @@ class ConsoleFormatter:
211
196
 
212
197
  return "\n".join(lines)
213
198
 
214
- def format_provider_comparison(
215
- self, providers: List[ProviderInfo], asset: str, amount: Decimal
216
- ) -> str:
199
+ def format_provider_comparison(self, providers: List[ProviderInfo], asset: str, amount: Decimal) -> str:
217
200
  """Format provider comparison table."""
218
201
  lines = []
219
202
 
@@ -221,10 +204,7 @@ class ConsoleFormatter:
221
204
  lines.append(f"\n Comparing {amount} {asset} flash loan:\n")
222
205
 
223
206
  # Table header
224
- lines.append(
225
- f" {'Provider':<14} {'Fee %':<8} {'Fee Amount':<14} "
226
- f"{'Gas OH':<10} {'Chains':<20}"
227
- )
207
+ lines.append(f" {'Provider':<14} {'Fee %':<8} {'Fee Amount':<14} {'Gas OH':<10} {'Chains':<20}")
228
208
  lines.append(f" {'-' * 66}")
229
209
 
230
210
  # Table rows
@@ -236,10 +216,7 @@ class ConsoleFormatter:
236
216
  if len(info.supported_chains) > 2:
237
217
  chains += "..."
238
218
 
239
- lines.append(
240
- f" {info.name:<14} {fee_pct:<8} {fee_amt:<14} "
241
- f"{gas_oh:<10} {chains:<20}"
242
- )
219
+ lines.append(f" {info.name:<14} {fee_pct:<8} {fee_amt:<14} {gas_oh:<10} {chains:<20}")
243
220
 
244
221
  # Recommendation
245
222
  if providers:
@@ -252,17 +229,14 @@ class ConsoleFormatter:
252
229
 
253
230
  return "\n".join(lines)
254
231
 
255
- def format_quick_summary(
256
- self, result: StrategyResult, assessment: Optional[RiskAssessment] = None
257
- ) -> str:
232
+ def format_quick_summary(self, result: StrategyResult, assessment: Optional[RiskAssessment] = None) -> str:
258
233
  """Format a quick one-box summary."""
259
234
  lines = []
260
235
 
261
236
  # Profit line
262
237
  profit_emoji = "✓" if result.is_profitable else "✗"
263
238
  lines.append(
264
- f"Net Profit: {result.net_profit:+.6f} {result.loan_asset} "
265
- f"(${result.net_profit_usd:+.2f}) {profit_emoji}"
239
+ f"Net Profit: {result.net_profit:+.6f} {result.loan_asset} (${result.net_profit_usd:+.2f}) {profit_emoji}"
266
240
  )
267
241
 
268
242
  # Provider
@@ -270,15 +244,11 @@ class ConsoleFormatter:
270
244
 
271
245
  # Risk if available
272
246
  if assessment:
273
- ind, lbl = self.RISK_INDICATORS.get(
274
- assessment.overall_level, ("⚪", "?")
275
- )
247
+ ind, lbl = self.RISK_INDICATORS.get(assessment.overall_level, ("⚪", "?"))
276
248
  lines.append(f"Risk: {ind} {lbl} | Viability: {assessment.viability}")
277
249
 
278
250
  # Verdict
279
- if result.is_profitable and (
280
- assessment is None or assessment.viability != "NO-GO"
281
- ):
251
+ if result.is_profitable and (assessment is None or assessment.viability != "NO-GO"):
282
252
  lines.append("Verdict: PROCEED WITH CAUTION")
283
253
  elif result.is_profitable:
284
254
  lines.append("Verdict: HIGH RISK - RECONSIDER")
@@ -400,9 +370,7 @@ class MarkdownFormatter:
400
370
  lines.append(f"- **Strategy**: {result.strategy_type.value}")
401
371
  lines.append(f"- **Loan**: {result.loan_amount} {result.loan_asset}")
402
372
  lines.append(f"- **Provider**: {result.provider}")
403
- lines.append(
404
- f"- **Profitable**: {'Yes ✓' if result.is_profitable else 'No ✗'}"
405
- )
373
+ lines.append(f"- **Profitable**: {'Yes ✓' if result.is_profitable else 'No ✗'}")
406
374
  lines.append("")
407
375
 
408
376
  # Profit
@@ -461,7 +429,7 @@ class MarkdownFormatter:
461
429
 
462
430
  def demo():
463
431
  """Demonstrate formatters."""
464
- from strategy_engine import StrategyFactory, StrategyType, ArbitrageParams
432
+ from strategy_engine import StrategyFactory, ArbitrageParams
465
433
  from profit_calculator import ProfitCalculator
466
434
  from risk_assessor import RiskAssessor
467
435
  from protocol_adapters import ProviderManager
@@ -11,7 +11,7 @@ Calculates net profit after all costs including:
11
11
 
12
12
  from dataclasses import dataclass
13
13
  from decimal import Decimal
14
- from typing import List, Optional
14
+ from typing import List
15
15
 
16
16
  from strategy_engine import StrategyResult, TransactionStep
17
17
 
@@ -120,9 +120,7 @@ class ProfitCalculator:
120
120
  if total_gas_units > 0 and profit_before_gas > 0:
121
121
  # profit_before_gas = gas_units * gas_price * eth_price
122
122
  # gas_price = profit_before_gas / (gas_units * eth_price)
123
- breakeven_gas = float(
124
- profit_before_gas * Decimal("1e9") / (total_gas_units * self.eth_price_usd)
125
- )
123
+ breakeven_gas = float(profit_before_gas * Decimal("1e9") / (total_gas_units * self.eth_price_usd))
126
124
  else:
127
125
  breakeven_gas = 0.0
128
126
 
@@ -223,15 +221,17 @@ class ProfitCalculator:
223
221
 
224
222
  total_cost = fee + gas_cost
225
223
 
226
- results.append({
227
- "provider": name,
228
- "fee_rate": float(fee_rate) * 100,
229
- "fee_amount": float(fee),
230
- "gas_overhead": overhead,
231
- "gas_cost_eth": float(gas_cost),
232
- "total_cost_eth": float(total_cost),
233
- "total_cost_usd": float(total_cost * self.eth_price_usd),
234
- })
224
+ results.append(
225
+ {
226
+ "provider": name,
227
+ "fee_rate": float(fee_rate) * 100,
228
+ "fee_amount": float(fee),
229
+ "gas_overhead": overhead,
230
+ "gas_cost_eth": float(gas_cost),
231
+ "total_cost_eth": float(total_cost),
232
+ "total_cost_usd": float(total_cost * self.eth_price_usd),
233
+ }
234
+ )
235
235
 
236
236
  # Sort by total cost
237
237
  results.sort(key=lambda x: x["total_cost_eth"])
@@ -267,11 +267,11 @@ def demo():
267
267
  print("=" * 60)
268
268
 
269
269
  print(f"\nGross Revenue: {breakdown.gross_revenue:.6f} ETH")
270
- print(f"\nCosts:")
270
+ print("\nCosts:")
271
271
  print(f" Flash Loan Fee: -{breakdown.flash_loan_fee:.6f} ETH")
272
272
  print(f" Gas Cost: -{breakdown.gas_cost_eth:.6f} ETH (${breakdown.gas_cost_usd:.2f})")
273
273
  print(f" Est. Slippage: -{breakdown.slippage_cost:.6f} ETH")
274
- print(f" ────────────────────────────────")
274
+ print(" ────────────────────────────────")
275
275
  print(f" Total Costs: -{breakdown.total_costs:.6f} ETH")
276
276
 
277
277
  print(f"\nNet Profit: {breakdown.net_profit:.6f} ETH (${breakdown.net_profit_usd:.2f})")
@@ -297,7 +297,7 @@ def demo():
297
297
  providers=providers,
298
298
  )
299
299
 
300
- print(f"\nFor 100 ETH flash loan:")
300
+ print("\nFor 100 ETH flash loan:")
301
301
  print(f"{'Provider':<12} {'Fee %':<8} {'Fee ETH':<12} {'Gas ETH':<12} {'Total':<12}")
302
302
  print("-" * 56)
303
303
 
@@ -11,8 +11,6 @@ from dataclasses import dataclass
11
11
  from decimal import Decimal
12
12
  from typing import Dict, List, Optional
13
13
 
14
- import httpx
15
-
16
14
 
17
15
  @dataclass
18
16
  class LoanParams:
@@ -343,9 +341,7 @@ class ProviderManager:
343
341
  """List all available providers."""
344
342
  return list(self.providers.keys())
345
343
 
346
- def compare_providers(
347
- self, asset: str, amount: Decimal, chain: str = "ethereum"
348
- ) -> List[ProviderInfo]:
344
+ def compare_providers(self, asset: str, amount: Decimal, chain: str = "ethereum") -> List[ProviderInfo]:
349
345
  """
350
346
  Compare all providers for a specific loan.
351
347
 
@@ -368,9 +364,7 @@ class ProviderManager:
368
364
  results.sort(key=lambda x: x.fee_amount)
369
365
  return results
370
366
 
371
- def find_cheapest(
372
- self, asset: str, amount: Decimal, chain: str = "ethereum"
373
- ) -> Optional[ProviderInfo]:
367
+ def find_cheapest(self, asset: str, amount: Decimal, chain: str = "ethereum") -> Optional[ProviderInfo]:
374
368
  """Find the cheapest provider for a loan."""
375
369
  providers = self.compare_providers(asset, amount, chain)
376
370
  return providers[0] if providers else None
@@ -12,7 +12,7 @@ Evaluates risks for flash loan strategies:
12
12
  from dataclasses import dataclass
13
13
  from decimal import Decimal
14
14
  from enum import Enum
15
- from typing import List, Optional
15
+ from typing import List
16
16
 
17
17
  from strategy_engine import StrategyResult, StrategyType
18
18
 
@@ -128,10 +128,7 @@ class RiskAssessor:
128
128
  "Profit Margin": 0.15,
129
129
  }
130
130
 
131
- overall_score = sum(
132
- f.score * weights.get(f.name, 0.2)
133
- for f in factors
134
- )
131
+ overall_score = sum(f.score * weights.get(f.name, 0.2) for f in factors)
135
132
 
136
133
  # Determine overall level
137
134
  if overall_score < 30:
@@ -169,10 +166,7 @@ class RiskAssessor:
169
166
  # Higher score = more competition
170
167
  pair_key = (result.loan_asset, "USDC") # Simplified
171
168
 
172
- score = self.MEV_COMPETITION.get(
173
- pair_key,
174
- self.MEV_COMPETITION.get("default", 50)
175
- )
169
+ score = self.MEV_COMPETITION.get(pair_key, self.MEV_COMPETITION.get("default", 50))
176
170
 
177
171
  # Adjust for trade size (larger = more attractive to MEV)
178
172
  if result.loan_amount > 100:
@@ -240,10 +234,7 @@ class RiskAssessor:
240
234
  protocols.add(step.protocol.lower())
241
235
 
242
236
  # Sum risk scores
243
- total_risk = sum(
244
- self.PROTOCOL_RISK.get(p, self.PROTOCOL_RISK["default"])
245
- for p in protocols
246
- )
237
+ total_risk = sum(self.PROTOCOL_RISK.get(p, self.PROTOCOL_RISK["default"]) for p in protocols)
247
238
 
248
239
  # Average across protocols
249
240
  score = total_risk / len(protocols) if protocols else 50
@@ -334,9 +325,7 @@ class RiskAssessor:
334
325
  mitigation="Increase trade size or wait for better opportunity",
335
326
  )
336
327
 
337
- def _generate_recommendations(
338
- self, factors: List[RiskFactor], result: StrategyResult
339
- ) -> List[str]:
328
+ def _generate_recommendations(self, factors: List[RiskFactor], result: StrategyResult) -> List[str]:
340
329
  """Generate actionable recommendations."""
341
330
  recs = []
342
331
 
@@ -376,7 +365,7 @@ class RiskAssessor:
376
365
 
377
366
  def demo():
378
367
  """Demonstrate risk assessment."""
379
- from strategy_engine import StrategyFactory, StrategyType, ArbitrageParams
368
+ from strategy_engine import StrategyFactory, ArbitrageParams
380
369
 
381
370
  # Run a simulation first
382
371
  factory = StrategyFactory()
@@ -176,9 +176,7 @@ class SimpleArbitrageStrategy(FlashLoanStrategy):
176
176
  def strategy_type(self) -> StrategyType:
177
177
  return StrategyType.SIMPLE_ARBITRAGE
178
178
 
179
- def get_dex_price(
180
- self, dex: str, from_token: str, to_token: str
181
- ) -> Optional[Decimal]:
179
+ def get_dex_price(self, dex: str, from_token: str, to_token: str) -> Optional[Decimal]:
182
180
  """Get price from DEX (mock data)."""
183
181
  dex_prices = self.MOCK_PRICES.get(dex.lower(), {})
184
182
  return dex_prices.get((from_token.upper(), to_token.upper()))
@@ -219,9 +217,7 @@ class SimpleArbitrageStrategy(FlashLoanStrategy):
219
217
  )
220
218
 
221
219
  # 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
- )
220
+ sell_price = self.get_dex_price(params.dex_sell, params.input_token, params.output_token)
225
221
  if not sell_price:
226
222
  warnings.append(f"No price data for {params.dex_sell}")
227
223
  sell_price = Decimal("2540") # Fallback
@@ -245,9 +241,7 @@ class SimpleArbitrageStrategy(FlashLoanStrategy):
245
241
  )
246
242
 
247
243
  # 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
- )
244
+ buy_price = self.get_dex_price(params.dex_buy, params.output_token, params.input_token)
251
245
  if not buy_price:
252
246
  warnings.append(f"No price data for {params.dex_buy}")
253
247
  buy_price = Decimal("1") / Decimal("2538") # Fallback
@@ -389,11 +383,9 @@ class LiquidationStrategy(FlashLoanStrategy):
389
383
  debt_amount = Decimal("10000") # 10K USDC debt
390
384
  collateral_amount = Decimal("5") # 5 ETH collateral
391
385
  collateral_price = Decimal("2500") # $2500/ETH
392
- collateral_value = collateral_amount * collateral_price # $12,500
386
+ collateral_amount * collateral_price # $12,500
393
387
 
394
- liquidation_bonus = self.LIQUIDATION_BONUSES.get(
395
- params.protocol.lower(), Decimal("0.05")
396
- )
388
+ liquidation_bonus = self.LIQUIDATION_BONUSES.get(params.protocol.lower(), Decimal("0.05"))
397
389
 
398
390
  # Step 1: Flash borrow debt asset
399
391
  loan_fee = provider.get_fee(params.debt_asset, debt_amount)
@@ -459,7 +451,7 @@ class LiquidationStrategy(FlashLoanStrategy):
459
451
  output_amount=Decimal("0"),
460
452
  fee=Decimal("0"),
461
453
  gas_estimate=50000,
462
- description=f"Repay flash loan",
454
+ description="Repay flash loan",
463
455
  )
464
456
  )
465
457