@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,512 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Flash loan simulation output formatters.
|
|
4
|
+
|
|
5
|
+
Handles all output formatting:
|
|
6
|
+
- Console tables and reports
|
|
7
|
+
- JSON export
|
|
8
|
+
- Summary cards
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import asdict
|
|
13
|
+
from decimal import Decimal
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from strategy_engine import StrategyResult, TransactionStep, StrategyType
|
|
17
|
+
from profit_calculator import ProfitBreakdown, GasEstimate
|
|
18
|
+
from risk_assessor import RiskAssessment, RiskFactor, RiskLevel
|
|
19
|
+
from protocol_adapters import ProviderInfo
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DecimalEncoder(json.JSONEncoder):
|
|
23
|
+
"""JSON encoder that handles Decimal types."""
|
|
24
|
+
|
|
25
|
+
def default(self, obj):
|
|
26
|
+
if isinstance(obj, Decimal):
|
|
27
|
+
return float(obj)
|
|
28
|
+
if hasattr(obj, "value"): # Enum
|
|
29
|
+
return obj.value
|
|
30
|
+
return super().default(obj)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConsoleFormatter:
|
|
34
|
+
"""Format output for console display."""
|
|
35
|
+
|
|
36
|
+
# Box drawing characters
|
|
37
|
+
BOX_H = "─"
|
|
38
|
+
BOX_V = "│"
|
|
39
|
+
BOX_TL = "┌"
|
|
40
|
+
BOX_TR = "┐"
|
|
41
|
+
BOX_BL = "└"
|
|
42
|
+
BOX_BR = "┘"
|
|
43
|
+
BOX_LT = "├"
|
|
44
|
+
BOX_RT = "┤"
|
|
45
|
+
BOX_TB = "┬"
|
|
46
|
+
BOX_BT = "┴"
|
|
47
|
+
BOX_X = "┼"
|
|
48
|
+
|
|
49
|
+
# Risk level indicators
|
|
50
|
+
RISK_INDICATORS = {
|
|
51
|
+
RiskLevel.LOW: ("🟢", "LOW"),
|
|
52
|
+
RiskLevel.MEDIUM: ("🟡", "MEDIUM"),
|
|
53
|
+
RiskLevel.HIGH: ("🟠", "HIGH"),
|
|
54
|
+
RiskLevel.CRITICAL: ("🔴", "CRITICAL"),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def __init__(self, width: int = 70):
|
|
58
|
+
"""Initialize formatter with display width."""
|
|
59
|
+
self.width = width
|
|
60
|
+
|
|
61
|
+
def _header(self, title: str) -> str:
|
|
62
|
+
"""Create a section header."""
|
|
63
|
+
padding = (self.width - len(title) - 2) // 2
|
|
64
|
+
return f"\n{'=' * padding} {title} {'=' * padding}\n"
|
|
65
|
+
|
|
66
|
+
def _subheader(self, title: str) -> str:
|
|
67
|
+
"""Create a subsection header."""
|
|
68
|
+
return f"\n{'-' * self.width}\n{title}\n{'-' * self.width}\n"
|
|
69
|
+
|
|
70
|
+
def _box(self, lines: List[str], title: str = "") -> str:
|
|
71
|
+
"""Create a box around content."""
|
|
72
|
+
inner_width = self.width - 4
|
|
73
|
+
|
|
74
|
+
result = []
|
|
75
|
+
|
|
76
|
+
# Top border with title
|
|
77
|
+
if title:
|
|
78
|
+
title_part = f" {title} "
|
|
79
|
+
border_left = (inner_width - len(title_part)) // 2
|
|
80
|
+
border_right = inner_width - len(title_part) - border_left
|
|
81
|
+
result.append(
|
|
82
|
+
f"{self.BOX_TL}{self.BOX_H * border_left}{title_part}"
|
|
83
|
+
f"{self.BOX_H * border_right}{self.BOX_TR}"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
result.append(f"{self.BOX_TL}{self.BOX_H * (inner_width + 2)}{self.BOX_TR}")
|
|
87
|
+
|
|
88
|
+
# Content
|
|
89
|
+
for line in lines:
|
|
90
|
+
# Truncate if too long
|
|
91
|
+
if len(line) > inner_width:
|
|
92
|
+
line = line[: inner_width - 3] + "..."
|
|
93
|
+
result.append(f"{self.BOX_V} {line:<{inner_width}} {self.BOX_V}")
|
|
94
|
+
|
|
95
|
+
# Bottom border
|
|
96
|
+
result.append(f"{self.BOX_BL}{self.BOX_H * (inner_width + 2)}{self.BOX_BR}")
|
|
97
|
+
|
|
98
|
+
return "\n".join(result)
|
|
99
|
+
|
|
100
|
+
def format_strategy_result(self, result: StrategyResult) -> str:
|
|
101
|
+
"""Format strategy simulation result."""
|
|
102
|
+
lines = []
|
|
103
|
+
|
|
104
|
+
# Header
|
|
105
|
+
lines.append(self._header("FLASH LOAN SIMULATION"))
|
|
106
|
+
|
|
107
|
+
# Strategy summary box
|
|
108
|
+
summary = [
|
|
109
|
+
f"Strategy: {result.strategy_type.value}",
|
|
110
|
+
f"Loan: {result.loan_amount} {result.loan_asset}",
|
|
111
|
+
f"Provider: {result.provider}",
|
|
112
|
+
f"Profitable: {'YES ✓' if result.is_profitable else 'NO ✗'}",
|
|
113
|
+
]
|
|
114
|
+
lines.append(self._box(summary, "SUMMARY"))
|
|
115
|
+
|
|
116
|
+
# Transaction steps
|
|
117
|
+
lines.append(self._subheader("TRANSACTION STEPS"))
|
|
118
|
+
for i, step in enumerate(result.steps, 1):
|
|
119
|
+
lines.append(f" {i}. [{step.protocol}] {step.action}")
|
|
120
|
+
lines.append(f" {step.asset_in} → {step.asset_out}")
|
|
121
|
+
lines.append(f" {step.amount_in:.6f} → {step.amount_out:.6f}")
|
|
122
|
+
lines.append(f" Gas: ~{step.gas_estimate:,} units")
|
|
123
|
+
lines.append("")
|
|
124
|
+
|
|
125
|
+
# Profit summary
|
|
126
|
+
lines.append(self._subheader("PROFIT BREAKDOWN"))
|
|
127
|
+
lines.append(f" Gross Profit: {result.gross_profit:+.6f} {result.loan_asset}")
|
|
128
|
+
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
|
+
)
|
|
132
|
+
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
|
+
)
|
|
137
|
+
lines.append(f" ROI: {result.roi_percent:.4f}%")
|
|
138
|
+
|
|
139
|
+
return "\n".join(lines)
|
|
140
|
+
|
|
141
|
+
def format_profit_breakdown(self, breakdown: ProfitBreakdown) -> str:
|
|
142
|
+
"""Format detailed profit breakdown."""
|
|
143
|
+
lines = []
|
|
144
|
+
|
|
145
|
+
lines.append(self._header("PROFIT BREAKDOWN"))
|
|
146
|
+
|
|
147
|
+
# Revenue
|
|
148
|
+
lines.append(f"\n Gross Revenue: {breakdown.gross_revenue:.6f} ETH")
|
|
149
|
+
|
|
150
|
+
# Costs
|
|
151
|
+
lines.append("\n Costs:")
|
|
152
|
+
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
|
+
)
|
|
157
|
+
lines.append(f" Est. Slippage: -{breakdown.slippage_cost:.6f} ETH")
|
|
158
|
+
lines.append(f" DEX Fees: ~{breakdown.dex_fees:.6f} ETH (in price)")
|
|
159
|
+
lines.append(f" {'-' * 45}")
|
|
160
|
+
lines.append(f" Total Costs: -{breakdown.total_costs:.6f} ETH")
|
|
161
|
+
|
|
162
|
+
# Net
|
|
163
|
+
lines.append(
|
|
164
|
+
f"\n Net Profit: {breakdown.net_profit:+.6f} ETH "
|
|
165
|
+
f"(${breakdown.net_profit_usd:+.2f})"
|
|
166
|
+
)
|
|
167
|
+
lines.append(f" ROI: {breakdown.roi_percent:.4f}%")
|
|
168
|
+
lines.append(f" Breakeven Gas: {breakdown.breakeven_gas_price:.1f} gwei")
|
|
169
|
+
|
|
170
|
+
# Verdict
|
|
171
|
+
if breakdown.is_profitable:
|
|
172
|
+
lines.append("\n ✓ PROFITABLE")
|
|
173
|
+
else:
|
|
174
|
+
lines.append("\n ✗ NOT PROFITABLE")
|
|
175
|
+
|
|
176
|
+
return "\n".join(lines)
|
|
177
|
+
|
|
178
|
+
def format_risk_assessment(self, assessment: RiskAssessment) -> str:
|
|
179
|
+
"""Format risk assessment report."""
|
|
180
|
+
lines = []
|
|
181
|
+
|
|
182
|
+
lines.append(self._header("RISK ASSESSMENT"))
|
|
183
|
+
|
|
184
|
+
# Overall rating
|
|
185
|
+
indicator, label = self.RISK_INDICATORS.get(
|
|
186
|
+
assessment.overall_level, ("⚪", "UNKNOWN")
|
|
187
|
+
)
|
|
188
|
+
lines.append(f"\n Overall Risk: {indicator} {label}")
|
|
189
|
+
lines.append(f" Risk Score: {assessment.overall_score:.0f}/100")
|
|
190
|
+
lines.append(f" Viability: {assessment.viability}")
|
|
191
|
+
|
|
192
|
+
# Individual factors
|
|
193
|
+
lines.append(self._subheader("RISK FACTORS"))
|
|
194
|
+
|
|
195
|
+
for factor in assessment.factors:
|
|
196
|
+
ind, lbl = self.RISK_INDICATORS.get(factor.level, ("⚪", "?"))
|
|
197
|
+
lines.append(f"\n {ind} {factor.name}: {lbl} ({factor.score:.0f})")
|
|
198
|
+
lines.append(f" {factor.description}")
|
|
199
|
+
lines.append(f" → {factor.mitigation}")
|
|
200
|
+
|
|
201
|
+
# Warnings
|
|
202
|
+
if assessment.warnings:
|
|
203
|
+
lines.append(self._subheader("WARNINGS"))
|
|
204
|
+
for warning in assessment.warnings:
|
|
205
|
+
lines.append(f" ⚠️ {warning}")
|
|
206
|
+
|
|
207
|
+
# Recommendations
|
|
208
|
+
lines.append(self._subheader("RECOMMENDATIONS"))
|
|
209
|
+
for rec in assessment.recommendations:
|
|
210
|
+
lines.append(f" • {rec}")
|
|
211
|
+
|
|
212
|
+
return "\n".join(lines)
|
|
213
|
+
|
|
214
|
+
def format_provider_comparison(
|
|
215
|
+
self, providers: List[ProviderInfo], asset: str, amount: Decimal
|
|
216
|
+
) -> str:
|
|
217
|
+
"""Format provider comparison table."""
|
|
218
|
+
lines = []
|
|
219
|
+
|
|
220
|
+
lines.append(self._header("PROVIDER COMPARISON"))
|
|
221
|
+
lines.append(f"\n Comparing {amount} {asset} flash loan:\n")
|
|
222
|
+
|
|
223
|
+
# Table header
|
|
224
|
+
lines.append(
|
|
225
|
+
f" {'Provider':<14} {'Fee %':<8} {'Fee Amount':<14} "
|
|
226
|
+
f"{'Gas OH':<10} {'Chains':<20}"
|
|
227
|
+
)
|
|
228
|
+
lines.append(f" {'-' * 66}")
|
|
229
|
+
|
|
230
|
+
# Table rows
|
|
231
|
+
for info in providers:
|
|
232
|
+
fee_pct = f"{float(info.fee_rate) * 100:.2f}%"
|
|
233
|
+
fee_amt = f"{info.fee_amount:.4f} {asset}"
|
|
234
|
+
gas_oh = f"{info.gas_overhead:,}"
|
|
235
|
+
chains = ", ".join(info.supported_chains[:2])
|
|
236
|
+
if len(info.supported_chains) > 2:
|
|
237
|
+
chains += "..."
|
|
238
|
+
|
|
239
|
+
lines.append(
|
|
240
|
+
f" {info.name:<14} {fee_pct:<8} {fee_amt:<14} "
|
|
241
|
+
f"{gas_oh:<10} {chains:<20}"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Recommendation
|
|
245
|
+
if providers:
|
|
246
|
+
best = providers[0]
|
|
247
|
+
lines.append(f"\n Recommended: {best.name}")
|
|
248
|
+
if best.fee_amount == 0:
|
|
249
|
+
lines.append(" (FREE flash loan!)")
|
|
250
|
+
else:
|
|
251
|
+
lines.append(f" (Lowest fee: {best.fee_amount:.4f} {asset})")
|
|
252
|
+
|
|
253
|
+
return "\n".join(lines)
|
|
254
|
+
|
|
255
|
+
def format_quick_summary(
|
|
256
|
+
self, result: StrategyResult, assessment: Optional[RiskAssessment] = None
|
|
257
|
+
) -> str:
|
|
258
|
+
"""Format a quick one-box summary."""
|
|
259
|
+
lines = []
|
|
260
|
+
|
|
261
|
+
# Profit line
|
|
262
|
+
profit_emoji = "✓" if result.is_profitable else "✗"
|
|
263
|
+
lines.append(
|
|
264
|
+
f"Net Profit: {result.net_profit:+.6f} {result.loan_asset} "
|
|
265
|
+
f"(${result.net_profit_usd:+.2f}) {profit_emoji}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Provider
|
|
269
|
+
lines.append(f"Provider: {result.provider} (fee: {result.loan_fee:.6f})")
|
|
270
|
+
|
|
271
|
+
# Risk if available
|
|
272
|
+
if assessment:
|
|
273
|
+
ind, lbl = self.RISK_INDICATORS.get(
|
|
274
|
+
assessment.overall_level, ("⚪", "?")
|
|
275
|
+
)
|
|
276
|
+
lines.append(f"Risk: {ind} {lbl} | Viability: {assessment.viability}")
|
|
277
|
+
|
|
278
|
+
# Verdict
|
|
279
|
+
if result.is_profitable and (
|
|
280
|
+
assessment is None or assessment.viability != "NO-GO"
|
|
281
|
+
):
|
|
282
|
+
lines.append("Verdict: PROCEED WITH CAUTION")
|
|
283
|
+
elif result.is_profitable:
|
|
284
|
+
lines.append("Verdict: HIGH RISK - RECONSIDER")
|
|
285
|
+
else:
|
|
286
|
+
lines.append("Verdict: DO NOT EXECUTE")
|
|
287
|
+
|
|
288
|
+
return self._box(lines, "QUICK SUMMARY")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class JSONFormatter:
|
|
292
|
+
"""Format output as JSON."""
|
|
293
|
+
|
|
294
|
+
def format_full_report(
|
|
295
|
+
self,
|
|
296
|
+
result: StrategyResult,
|
|
297
|
+
breakdown: Optional[ProfitBreakdown] = None,
|
|
298
|
+
assessment: Optional[RiskAssessment] = None,
|
|
299
|
+
providers: Optional[List[ProviderInfo]] = None,
|
|
300
|
+
) -> str:
|
|
301
|
+
"""Format complete simulation report as JSON."""
|
|
302
|
+
report = {
|
|
303
|
+
"simulation": {
|
|
304
|
+
"strategy_type": result.strategy_type.value,
|
|
305
|
+
"loan_asset": result.loan_asset,
|
|
306
|
+
"loan_amount": float(result.loan_amount),
|
|
307
|
+
"provider": result.provider,
|
|
308
|
+
"is_profitable": result.is_profitable,
|
|
309
|
+
},
|
|
310
|
+
"profit": {
|
|
311
|
+
"gross_profit": float(result.gross_profit),
|
|
312
|
+
"loan_fee": float(result.loan_fee),
|
|
313
|
+
"gas_cost_eth": float(result.gas_cost_eth),
|
|
314
|
+
"gas_cost_usd": float(result.gas_cost_usd),
|
|
315
|
+
"net_profit": float(result.net_profit),
|
|
316
|
+
"net_profit_usd": float(result.net_profit_usd),
|
|
317
|
+
"roi_percent": result.roi_percent,
|
|
318
|
+
},
|
|
319
|
+
"steps": [
|
|
320
|
+
{
|
|
321
|
+
"protocol": s.protocol,
|
|
322
|
+
"action": s.action,
|
|
323
|
+
"asset_in": s.asset_in,
|
|
324
|
+
"asset_out": s.asset_out,
|
|
325
|
+
"amount_in": float(s.amount_in),
|
|
326
|
+
"amount_out": float(s.amount_out),
|
|
327
|
+
"gas_estimate": s.gas_estimate,
|
|
328
|
+
}
|
|
329
|
+
for s in result.steps
|
|
330
|
+
],
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if breakdown:
|
|
334
|
+
report["breakdown"] = {
|
|
335
|
+
"gross_revenue": float(breakdown.gross_revenue),
|
|
336
|
+
"flash_loan_fee": float(breakdown.flash_loan_fee),
|
|
337
|
+
"gas_cost_eth": float(breakdown.gas_cost_eth),
|
|
338
|
+
"gas_cost_usd": float(breakdown.gas_cost_usd),
|
|
339
|
+
"dex_fees": float(breakdown.dex_fees),
|
|
340
|
+
"slippage_cost": float(breakdown.slippage_cost),
|
|
341
|
+
"total_costs": float(breakdown.total_costs),
|
|
342
|
+
"breakeven_gas_price": breakdown.breakeven_gas_price,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if assessment:
|
|
346
|
+
report["risk"] = {
|
|
347
|
+
"overall_level": assessment.overall_level.value,
|
|
348
|
+
"overall_score": assessment.overall_score,
|
|
349
|
+
"viability": assessment.viability,
|
|
350
|
+
"factors": [
|
|
351
|
+
{
|
|
352
|
+
"name": f.name,
|
|
353
|
+
"level": f.level.value,
|
|
354
|
+
"score": f.score,
|
|
355
|
+
"description": f.description,
|
|
356
|
+
"mitigation": f.mitigation,
|
|
357
|
+
}
|
|
358
|
+
for f in assessment.factors
|
|
359
|
+
],
|
|
360
|
+
"warnings": assessment.warnings,
|
|
361
|
+
"recommendations": assessment.recommendations,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if providers:
|
|
365
|
+
report["providers"] = [
|
|
366
|
+
{
|
|
367
|
+
"name": p.name,
|
|
368
|
+
"fee_rate": float(p.fee_rate),
|
|
369
|
+
"fee_amount": float(p.fee_amount),
|
|
370
|
+
"max_available": float(p.max_available),
|
|
371
|
+
"gas_overhead": p.gas_overhead,
|
|
372
|
+
"supported_chains": p.supported_chains,
|
|
373
|
+
}
|
|
374
|
+
for p in providers
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
return json.dumps(report, indent=2, cls=DecimalEncoder)
|
|
378
|
+
|
|
379
|
+
def format_strategy_result(self, result: StrategyResult) -> str:
|
|
380
|
+
"""Format just the strategy result as JSON."""
|
|
381
|
+
return self.format_full_report(result)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class MarkdownFormatter:
|
|
385
|
+
"""Format output as Markdown (for reports/docs)."""
|
|
386
|
+
|
|
387
|
+
def format_simulation_report(
|
|
388
|
+
self,
|
|
389
|
+
result: StrategyResult,
|
|
390
|
+
breakdown: Optional[ProfitBreakdown] = None,
|
|
391
|
+
assessment: Optional[RiskAssessment] = None,
|
|
392
|
+
) -> str:
|
|
393
|
+
"""Format as Markdown report."""
|
|
394
|
+
lines = []
|
|
395
|
+
|
|
396
|
+
lines.append("# Flash Loan Simulation Report\n")
|
|
397
|
+
|
|
398
|
+
# Summary
|
|
399
|
+
lines.append("## Summary\n")
|
|
400
|
+
lines.append(f"- **Strategy**: {result.strategy_type.value}")
|
|
401
|
+
lines.append(f"- **Loan**: {result.loan_amount} {result.loan_asset}")
|
|
402
|
+
lines.append(f"- **Provider**: {result.provider}")
|
|
403
|
+
lines.append(
|
|
404
|
+
f"- **Profitable**: {'Yes ✓' if result.is_profitable else 'No ✗'}"
|
|
405
|
+
)
|
|
406
|
+
lines.append("")
|
|
407
|
+
|
|
408
|
+
# Profit
|
|
409
|
+
lines.append("## Profit Analysis\n")
|
|
410
|
+
lines.append("| Metric | Value |")
|
|
411
|
+
lines.append("|--------|-------|")
|
|
412
|
+
lines.append(f"| Gross Profit | {result.gross_profit:+.6f} {result.loan_asset} |")
|
|
413
|
+
lines.append(f"| Flash Loan Fee | -{result.loan_fee:.6f} {result.loan_asset} |")
|
|
414
|
+
lines.append(f"| Gas Cost | -{result.gas_cost_eth:.6f} ETH (${result.gas_cost_usd:.2f}) |")
|
|
415
|
+
lines.append(f"| **Net Profit** | **{result.net_profit:+.6f} {result.loan_asset}** |")
|
|
416
|
+
lines.append(f"| ROI | {result.roi_percent:.4f}% |")
|
|
417
|
+
lines.append("")
|
|
418
|
+
|
|
419
|
+
# Steps
|
|
420
|
+
lines.append("## Transaction Steps\n")
|
|
421
|
+
for i, step in enumerate(result.steps, 1):
|
|
422
|
+
lines.append(f"### Step {i}: {step.action} on {step.protocol}\n")
|
|
423
|
+
lines.append(f"- Input: {step.amount_in:.6f} {step.asset_in}")
|
|
424
|
+
lines.append(f"- Output: {step.amount_out:.6f} {step.asset_out}")
|
|
425
|
+
lines.append(f"- Gas: ~{step.gas_estimate:,} units")
|
|
426
|
+
lines.append("")
|
|
427
|
+
|
|
428
|
+
# Risk if available
|
|
429
|
+
if assessment:
|
|
430
|
+
lines.append("## Risk Assessment\n")
|
|
431
|
+
lines.append(f"- **Overall Level**: {assessment.overall_level.value}")
|
|
432
|
+
lines.append(f"- **Risk Score**: {assessment.overall_score:.0f}/100")
|
|
433
|
+
lines.append(f"- **Viability**: {assessment.viability}")
|
|
434
|
+
lines.append("")
|
|
435
|
+
|
|
436
|
+
lines.append("### Risk Factors\n")
|
|
437
|
+
lines.append("| Factor | Level | Score | Description |")
|
|
438
|
+
lines.append("|--------|-------|-------|-------------|")
|
|
439
|
+
for f in assessment.factors:
|
|
440
|
+
lines.append(f"| {f.name} | {f.level.value} | {f.score:.0f} | {f.description} |")
|
|
441
|
+
lines.append("")
|
|
442
|
+
|
|
443
|
+
if assessment.warnings:
|
|
444
|
+
lines.append("### Warnings\n")
|
|
445
|
+
for w in assessment.warnings:
|
|
446
|
+
lines.append(f"- ⚠️ {w}")
|
|
447
|
+
lines.append("")
|
|
448
|
+
|
|
449
|
+
lines.append("### Recommendations\n")
|
|
450
|
+
for r in assessment.recommendations:
|
|
451
|
+
lines.append(f"- {r}")
|
|
452
|
+
lines.append("")
|
|
453
|
+
|
|
454
|
+
# Disclaimer
|
|
455
|
+
lines.append("---\n")
|
|
456
|
+
lines.append("*This simulation is for educational purposes only. ")
|
|
457
|
+
lines.append("Do not execute without proper testing and risk assessment.*")
|
|
458
|
+
|
|
459
|
+
return "\n".join(lines)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def demo():
|
|
463
|
+
"""Demonstrate formatters."""
|
|
464
|
+
from strategy_engine import StrategyFactory, StrategyType, ArbitrageParams
|
|
465
|
+
from profit_calculator import ProfitCalculator
|
|
466
|
+
from risk_assessor import RiskAssessor
|
|
467
|
+
from protocol_adapters import ProviderManager
|
|
468
|
+
|
|
469
|
+
# Run simulation
|
|
470
|
+
factory = StrategyFactory()
|
|
471
|
+
strategy = factory.create(StrategyType.SIMPLE_ARBITRAGE)
|
|
472
|
+
|
|
473
|
+
params = ArbitrageParams(
|
|
474
|
+
input_token="ETH",
|
|
475
|
+
output_token="USDC",
|
|
476
|
+
amount=Decimal("100"),
|
|
477
|
+
dex_buy="sushiswap",
|
|
478
|
+
dex_sell="uniswap",
|
|
479
|
+
provider="aave",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
result = strategy.simulate(params)
|
|
483
|
+
|
|
484
|
+
# Calculate breakdown
|
|
485
|
+
calculator = ProfitCalculator(eth_price_usd=2500.0, gas_price_gwei=30.0)
|
|
486
|
+
breakdown = calculator.calculate_breakdown(result)
|
|
487
|
+
|
|
488
|
+
# Assess risk
|
|
489
|
+
assessor = RiskAssessor(eth_price_usd=2500.0)
|
|
490
|
+
assessment = assessor.assess(result)
|
|
491
|
+
|
|
492
|
+
# Get providers
|
|
493
|
+
manager = ProviderManager()
|
|
494
|
+
providers = manager.compare_providers("ETH", Decimal("100"))
|
|
495
|
+
|
|
496
|
+
# Console format
|
|
497
|
+
console = ConsoleFormatter()
|
|
498
|
+
print(console.format_strategy_result(result))
|
|
499
|
+
print(console.format_risk_assessment(assessment))
|
|
500
|
+
print(console.format_provider_comparison(providers, "ETH", Decimal("100")))
|
|
501
|
+
print(console.format_quick_summary(result, assessment))
|
|
502
|
+
|
|
503
|
+
# JSON format
|
|
504
|
+
print("\n" + "=" * 70)
|
|
505
|
+
print("JSON OUTPUT")
|
|
506
|
+
print("=" * 70)
|
|
507
|
+
json_fmt = JSONFormatter()
|
|
508
|
+
print(json_fmt.format_full_report(result, breakdown, assessment, providers))
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
if __name__ == "__main__":
|
|
512
|
+
demo()
|