408 lines
No EOL
16 KiB
Bash
Executable file
408 lines
No EOL
16 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# Change to the analysis directory (where this script is located)
|
|
cd "$(dirname "$0")"
|
|
|
|
# Function to cleanup on exit
|
|
cleanup() {
|
|
if [ -n "$VIEWER_PID" ]; then
|
|
echo -e "\n${YELLOW}Stopping viewer...${NC}"
|
|
# Kill the entire process group
|
|
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
|
sleep 1
|
|
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
|
fi
|
|
rm -f profitable_scenario.csv 2>/dev/null || true
|
|
}
|
|
|
|
# Set trap to cleanup on script exit
|
|
trap cleanup EXIT
|
|
|
|
# Default values
|
|
OPTIMIZER_CLASS=""
|
|
TOTAL_RUNS=50
|
|
TRADES_PER_RUN=20
|
|
DEBUG_CSV=false
|
|
WHALE_MODE=false
|
|
|
|
# Function to show usage
|
|
show_usage() {
|
|
echo "Usage: $0 <optimizer_class> [runs=N] [trades=N] [debugCSV] [whaleMode]"
|
|
echo ""
|
|
echo "Parameters:"
|
|
echo " optimizer_class Required. The optimizer class to use"
|
|
echo " runs=N Optional. Number of fuzzing runs (default: 50)"
|
|
echo " trades=N Optional. Trades per run (default: 20, actual will be ±5)"
|
|
echo " debugCSV Optional. Enable debug mode with position tracking CSV (forces runs=1)"
|
|
echo " whaleMode Optional. Enable whale-sized trades (20-80% of balance, 50-500 ETH funding)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 BullMarketOptimizer"
|
|
echo " $0 WhaleOptimizer runs=100"
|
|
echo " $0 BearMarketOptimizer runs=10 trades=50"
|
|
echo " $0 NeutralMarketOptimizer trades=30 runs=25"
|
|
echo " $0 BullMarketOptimizer debugCSV"
|
|
echo " $0 WhaleOptimizer whaleMode runs=20"
|
|
echo ""
|
|
echo "Available optimizers:"
|
|
echo " - BullMarketOptimizer"
|
|
echo " - NeutralMarketOptimizer"
|
|
echo " - BearMarketOptimizer"
|
|
echo " - WhaleOptimizer"
|
|
echo " - MockOptimizer"
|
|
echo " - RandomScenarioOptimizer"
|
|
}
|
|
|
|
# Parse arguments
|
|
if [ $# -eq 0 ]; then
|
|
echo "Error: No optimizer class specified"
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
|
|
# First argument is always the optimizer class
|
|
OPTIMIZER_CLASS=$1
|
|
shift
|
|
|
|
# Parse named parameters
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
runs=*)
|
|
TOTAL_RUNS="${arg#*=}"
|
|
if ! [[ "$TOTAL_RUNS" =~ ^[0-9]+$ ]] || [ "$TOTAL_RUNS" -eq 0 ]; then
|
|
echo "Error: Invalid value for runs. Must be a positive integer."
|
|
exit 1
|
|
fi
|
|
;;
|
|
trades=*)
|
|
TRADES_PER_RUN="${arg#*=}"
|
|
if ! [[ "$TRADES_PER_RUN" =~ ^[0-9]+$ ]] || [ "$TRADES_PER_RUN" -eq 0 ]; then
|
|
echo "Error: Invalid value for trades. Must be a positive integer."
|
|
exit 1
|
|
fi
|
|
;;
|
|
debugCSV)
|
|
DEBUG_CSV=true
|
|
TOTAL_RUNS=1
|
|
;;
|
|
whaleMode)
|
|
WHALE_MODE=true
|
|
;;
|
|
*)
|
|
echo "Error: Unknown parameter '$arg'"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Colors for output
|
|
GREEN='\033[0;32m'
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
OUTPUT_DIR="fuzzing_results_${OPTIMIZER_CLASS}_$(date +%Y%m%d_%H%M%S)"
|
|
MERGED_CSV="$OUTPUT_DIR/merged_profitable_scenarios.csv"
|
|
|
|
echo -e "${GREEN}=== Fuzzing Campaign ===${NC}"
|
|
echo "Optimizer: $OPTIMIZER_CLASS"
|
|
echo "Total runs: $TOTAL_RUNS"
|
|
echo "Trades per run: $TRADES_PER_RUN (±5)"
|
|
if [ "$DEBUG_CSV" = true ]; then
|
|
echo -e "${YELLOW}Debug mode: ENABLED (position tracking CSV will be generated)${NC}"
|
|
fi
|
|
if [ "$WHALE_MODE" = true ]; then
|
|
echo -e "${YELLOW}Whale mode: ENABLED (20-80% trades, 50-500 ETH funding)${NC}"
|
|
fi
|
|
echo "Output directory: $OUTPUT_DIR"
|
|
echo ""
|
|
|
|
# Validate that the optimizer class exists by doing a dry run
|
|
echo "Validating optimizer class..."
|
|
OPTIMIZER_CLASS="$OPTIMIZER_CLASS" FUZZING_RUNS=1 TRADES_PER_RUN=1 forge script FuzzingAnalysis.s.sol --ffi --via-ir --gas-limit 200000000 > /tmp/optimizer_check.log 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
# Check specifically for unknown optimizer error
|
|
if grep -q "Unknown optimizer class" /tmp/optimizer_check.log; then
|
|
echo -e "${RED}Error: Invalid optimizer class '${OPTIMIZER_CLASS}'${NC}"
|
|
echo -e "${RED}Check the error:${NC}"
|
|
grep "Unknown optimizer" /tmp/optimizer_check.log
|
|
echo ""
|
|
show_usage
|
|
exit 1
|
|
else
|
|
# Other errors are ok during validation, we just want to check the optimizer exists
|
|
echo "Optimizer validation passed (non-optimizer errors ignored)"
|
|
fi
|
|
else
|
|
echo "Optimizer validation passed"
|
|
fi
|
|
|
|
# Create output directory
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
# Initialize merged CSV with header
|
|
echo "Scenario,Seed,Initial Balance,Final Balance,Profit,Profit %" > "$MERGED_CSV"
|
|
|
|
# Track statistics
|
|
TOTAL_PROFITABLE=0
|
|
FAILED_RUNS=0
|
|
CUMULATIVE_PNL=0
|
|
CSV_GENERATED=false
|
|
LATEST_CSV=""
|
|
|
|
# Save configuration
|
|
CONFIG_FILE="$OUTPUT_DIR/config.txt"
|
|
{
|
|
echo "Fuzzing Configuration"
|
|
echo "===================="
|
|
echo "Optimizer: $OPTIMIZER_CLASS"
|
|
echo "Total runs: $TOTAL_RUNS"
|
|
echo "Trades per run: $TRADES_PER_RUN (±5)"
|
|
echo "Start time: $(date)"
|
|
} > "$CONFIG_FILE"
|
|
|
|
# Run fuzzing analysis multiple times
|
|
for i in $(seq 1 $TOTAL_RUNS); do
|
|
echo -e "${YELLOW}Running fuzzing iteration $i/$TOTAL_RUNS...${NC}"
|
|
|
|
# Run single fuzzing iteration with specified optimizer and trades
|
|
# Use iteration number as seed offset to ensure different scenarios
|
|
# Enable position tracking if debugCSV is set
|
|
if [ "$DEBUG_CSV" = true ]; then
|
|
WHALE_MODE="$WHALE_MODE" TRACK_POSITIONS=true SEED_OFFSET=$((i - 1)) OPTIMIZER_CLASS="$OPTIMIZER_CLASS" TRADES_PER_RUN="$TRADES_PER_RUN" FUZZING_RUNS=1 forge script FuzzingAnalysis.s.sol --ffi --via-ir --gas-limit 200000000 > "$OUTPUT_DIR/run_$i.log" 2>&1
|
|
else
|
|
WHALE_MODE="$WHALE_MODE" SEED_OFFSET=$((i - 1)) OPTIMIZER_CLASS="$OPTIMIZER_CLASS" TRADES_PER_RUN="$TRADES_PER_RUN" FUZZING_RUNS=1 forge script FuzzingAnalysis.s.sol --ffi --via-ir --gas-limit 200000000 > "$OUTPUT_DIR/run_$i.log" 2>&1
|
|
fi
|
|
|
|
if [ $? -eq 0 ]; then
|
|
echo -e "${GREEN}✓ Run $i completed${NC}"
|
|
|
|
# Extract P&L from RESULT line (may have leading spaces from forge output)
|
|
RESULT_LINE=$(grep "RESULT|" "$OUTPUT_DIR/run_$i.log")
|
|
if [ -n "$RESULT_LINE" ]; then
|
|
# Parse the RESULT line to extract P&L
|
|
PNL_VALUE=$(echo "$RESULT_LINE" | awk -F'|' '{print $5}' | sed 's/PNL://' | tr -d ' ')
|
|
|
|
# Debug: show values before calculation
|
|
# echo "DEBUG: Current cumulative: $CUMULATIVE_PNL, New P&L: $PNL_VALUE"
|
|
|
|
# Add to cumulative P&L using awk (handles large numbers better than bash arithmetic)
|
|
CUMULATIVE_PNL=$(LC_NUMERIC=C awk -v cum="$CUMULATIVE_PNL" -v pnl="$PNL_VALUE" 'BEGIN {printf "%.0f", cum + pnl}')
|
|
|
|
# Format cumulative P&L for display (convert from wei to ETH)
|
|
CUMULATIVE_ETH=$(LC_NUMERIC=C awk -v cum="$CUMULATIVE_PNL" 'BEGIN {printf "%.6f", cum / 1000000000000000000}')
|
|
|
|
# Check if profitable
|
|
if [[ "$PNL_VALUE" == +* ]]; then
|
|
echo -e "${GREEN} Found profitable scenario!${NC}"
|
|
((TOTAL_PROFITABLE++))
|
|
|
|
# Extract profit percentage
|
|
PROFIT_PCT=$(grep "PROFITABLE!" "$OUTPUT_DIR/run_$i.log" | grep -oE "Profit: [0-9]+%" | grep -oE "[0-9]+")
|
|
echo -e "${GREEN} Profit: ${PROFIT_PCT}%${NC}"
|
|
else
|
|
echo -e "${YELLOW} Loss scenario${NC}"
|
|
fi
|
|
|
|
# Display cumulative P&L
|
|
if awk -v cum="$CUMULATIVE_PNL" 'BEGIN {exit !(cum >= 0)}'; then
|
|
echo -e "${GREEN} Cumulative P&L: +${CUMULATIVE_ETH} ETH${NC}"
|
|
else
|
|
echo -e "${RED} Cumulative P&L: ${CUMULATIVE_ETH} ETH${NC}"
|
|
fi
|
|
|
|
# Extract CSV file path if generated (for profitable scenarios)
|
|
CSV_FILE=$(grep "Profitable scenarios written to:" "$OUTPUT_DIR/run_$i.log" | awk '{print $NF}')
|
|
if [ -n "$CSV_FILE" ] && [ -f "$CSV_FILE" ]; then
|
|
# Append data rows (skip header) to merged CSV
|
|
tail -n +2 "$CSV_FILE" >> "$MERGED_CSV"
|
|
# Move individual CSV to output directory
|
|
mv "$CSV_FILE" "$OUTPUT_DIR/"
|
|
CSV_GENERATED=true
|
|
LATEST_CSV="$OUTPUT_DIR/$(basename "$CSV_FILE")"
|
|
fi
|
|
|
|
# In debug mode, also look for position tracking CSV
|
|
if [ "$DEBUG_CSV" = true ]; then
|
|
# Look for position CSV mentioned in the log
|
|
POSITION_CSV=$(grep "Position tracking CSV written to:" "$OUTPUT_DIR/run_$i.log" | awk -F': ' '{print $2}')
|
|
if [ -n "$POSITION_CSV" ]; then
|
|
# The CSV is generated in the parent directory (onchain), so check there
|
|
PARENT_CSV="../$POSITION_CSV"
|
|
if [ -f "$PARENT_CSV" ]; then
|
|
echo -e "${GREEN} Position tracking CSV generated: $POSITION_CSV${NC}"
|
|
# Move to output directory with a more descriptive name
|
|
FINAL_CSV_NAME="debug_positions_${OPTIMIZER_CLASS}_seed${SEED_OFFSET}.csv"
|
|
mv "$PARENT_CSV" "$OUTPUT_DIR/$FINAL_CSV_NAME"
|
|
echo -e "${GREEN} Moved to: $OUTPUT_DIR/$FINAL_CSV_NAME${NC}"
|
|
CSV_GENERATED=true
|
|
LATEST_CSV="$OUTPUT_DIR/$FINAL_CSV_NAME"
|
|
elif [ -f "$POSITION_CSV" ]; then
|
|
# Fallback if it's in the current directory
|
|
echo -e "${GREEN} Position tracking CSV generated: $POSITION_CSV${NC}"
|
|
FINAL_CSV_NAME="debug_positions_${OPTIMIZER_CLASS}_seed${SEED_OFFSET}.csv"
|
|
mv "$POSITION_CSV" "$OUTPUT_DIR/$FINAL_CSV_NAME"
|
|
echo -e "${GREEN} Moved to: $OUTPUT_DIR/$FINAL_CSV_NAME${NC}"
|
|
CSV_GENERATED=true
|
|
LATEST_CSV="$OUTPUT_DIR/$FINAL_CSV_NAME"
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
echo -e "${YELLOW} Warning: No RESULT line found in output${NC}"
|
|
fi
|
|
else
|
|
echo -e "${RED}✗ Run $i failed${NC}"
|
|
((FAILED_RUNS++))
|
|
# Show last few lines of error
|
|
echo -e "${RED}Error details:${NC}"
|
|
tail -5 "$OUTPUT_DIR/run_$i.log"
|
|
fi
|
|
|
|
# Small delay to avoid overwhelming the system
|
|
sleep 0.5
|
|
done
|
|
|
|
# Update config with end time
|
|
echo "End time: $(date)" >> "$CONFIG_FILE"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}=== FUZZING CAMPAIGN COMPLETE ===${NC}"
|
|
echo "Optimizer: $OPTIMIZER_CLASS"
|
|
echo "Total runs: $TOTAL_RUNS"
|
|
echo "Trades per run: $TRADES_PER_RUN (±5)"
|
|
echo "Successful runs: $((TOTAL_RUNS - FAILED_RUNS))"
|
|
echo "Failed runs: $FAILED_RUNS"
|
|
echo "Total profitable scenarios: $TOTAL_PROFITABLE"
|
|
|
|
# Display final cumulative P&L
|
|
FINAL_CUMULATIVE_ETH=$(LC_NUMERIC=C awk -v cum="$CUMULATIVE_PNL" 'BEGIN {printf "%.6f", cum / 1000000000000000000}')
|
|
if awk -v cum="$CUMULATIVE_PNL" 'BEGIN {exit !(cum >= 0)}'; then
|
|
echo -e "${GREEN}Final Cumulative P&L: +${FINAL_CUMULATIVE_ETH} ETH${NC}"
|
|
else
|
|
echo -e "${RED}Final Cumulative P&L: ${FINAL_CUMULATIVE_ETH} ETH${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Results saved in: $OUTPUT_DIR"
|
|
echo "Merged CSV: $MERGED_CSV"
|
|
|
|
# Generate summary report
|
|
SUMMARY="$OUTPUT_DIR/summary.txt"
|
|
{
|
|
echo "Fuzzing Campaign Summary"
|
|
echo "========================"
|
|
echo "Date: $(date)"
|
|
echo "Optimizer: $OPTIMIZER_CLASS"
|
|
echo "Total runs: $TOTAL_RUNS"
|
|
echo "Trades per run: $TRADES_PER_RUN (±5)"
|
|
echo ""
|
|
echo "Results:"
|
|
echo "--------"
|
|
echo "Successful runs: $((TOTAL_RUNS - FAILED_RUNS)) / $TOTAL_RUNS"
|
|
echo "Failed runs: $FAILED_RUNS"
|
|
echo "Total profitable scenarios: $TOTAL_PROFITABLE / $((TOTAL_RUNS - FAILED_RUNS))"
|
|
echo "Success rate: $(awk "BEGIN {if ($TOTAL_RUNS - $FAILED_RUNS > 0) printf \"%.2f\", $TOTAL_PROFITABLE/($TOTAL_RUNS-$FAILED_RUNS)*100; else print \"0.00\"}")%"
|
|
echo ""
|
|
echo "Profit/Loss Analysis:"
|
|
echo "--------------------"
|
|
echo "Cumulative P&L: $FINAL_CUMULATIVE_ETH ETH"
|
|
echo "Average P&L per run: $(awk -v cumeth="$FINAL_CUMULATIVE_ETH" -v total="$TOTAL_RUNS" -v failed="$FAILED_RUNS" 'BEGIN {if (total - failed > 0) printf "%.6f ETH", cumeth/(total-failed); else print "0.000000 ETH"}')"
|
|
} > "$SUMMARY"
|
|
|
|
echo ""
|
|
echo "Summary report: $SUMMARY"
|
|
|
|
# If there were profitable scenarios, show a sample
|
|
if [ $TOTAL_PROFITABLE -gt 0 ]; then
|
|
echo ""
|
|
echo -e "${GREEN}Sample profitable scenarios:${NC}"
|
|
head -5 "$MERGED_CSV"
|
|
fi
|
|
|
|
# If debug mode was used, mention the position tracking CSV
|
|
if [ "$DEBUG_CSV" = true ]; then
|
|
echo ""
|
|
echo -e "${GREEN}Debug position tracking CSV generated!${NC}"
|
|
echo "View it with: ./view-scenarios.sh"
|
|
echo "Then navigate to the output directory and select the debug CSV file"
|
|
fi
|
|
|
|
# If any CSV was generated, launch the viewer
|
|
if [ "$CSV_GENERATED" = true ] && [ -n "$LATEST_CSV" ]; then
|
|
echo ""
|
|
echo -e "${GREEN}=== Launching Scenario Visualizer ===${NC}"
|
|
echo "CSV file: $LATEST_CSV"
|
|
|
|
# Create a temporary symlink to the CSV for the viewer
|
|
TEMP_LINK="profitable_scenario.csv"
|
|
if [ -f "$TEMP_LINK" ] || [ -L "$TEMP_LINK" ]; then
|
|
rm -f "$TEMP_LINK"
|
|
fi
|
|
# Use absolute path for the symlink
|
|
ln -s "$(pwd)/$LATEST_CSV" "$TEMP_LINK"
|
|
|
|
# Check if server is already running on common ports
|
|
SERVER_RUNNING=false
|
|
EXISTING_PORT=""
|
|
for PORT in 8000 8001 8002; do
|
|
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
# Check if it's a python http server in our analysis directory
|
|
if lsof -Pi :$PORT -sTCP:LISTEN 2>/dev/null | grep -q "python.*http.server"; then
|
|
SERVER_RUNNING=true
|
|
EXISTING_PORT=$PORT
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ "$SERVER_RUNNING" = true ]; then
|
|
echo -e "${YELLOW}Server already running on port $EXISTING_PORT${NC}"
|
|
echo -e "${GREEN}Browser should open to: http://localhost:$EXISTING_PORT/scenario-visualizer.html${NC}"
|
|
|
|
# Try to open browser to existing server
|
|
if command -v xdg-open &> /dev/null; then
|
|
xdg-open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
|
elif command -v open &> /dev/null; then
|
|
open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${YELLOW}Press Enter to exit (server will keep running)...${NC}"
|
|
read -r
|
|
else
|
|
# Start the viewer in background in its own process group
|
|
setsid ./view-scenarios.sh &
|
|
VIEWER_PID=$!
|
|
|
|
# Give the server time to start and browser to open
|
|
sleep 2
|
|
|
|
# Show the URL
|
|
echo ""
|
|
echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}"
|
|
echo "(If port 8000 was busy, check the port number mentioned above)"
|
|
echo "If browser didn't open, manually navigate to that URL"
|
|
|
|
# Wait for user input
|
|
echo ""
|
|
echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}"
|
|
read -r
|
|
|
|
# Kill the viewer process and its children
|
|
if [ -n "$VIEWER_PID" ]; then
|
|
# Kill the entire process group (includes python server)
|
|
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
|
# Give it a moment to clean up
|
|
sleep 1
|
|
# Force kill if still running
|
|
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
|
fi
|
|
|
|
echo -e "${GREEN}Viewer stopped.${NC}"
|
|
fi
|
|
|
|
# Clean up the symlink
|
|
rm -f "$TEMP_LINK"
|
|
fi |