260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
type Config struct {
|
|
// Secrets (from .env)
|
|
DuneAPIKey string
|
|
WalletAddress string
|
|
PrivateKey string
|
|
EthereumAPIEndpoint string
|
|
WebhookURL string
|
|
BotName string
|
|
APIKey string
|
|
CORSAllowOrigin string
|
|
|
|
// Database
|
|
DBHost string
|
|
DBPort int
|
|
DBName string
|
|
DBUser string
|
|
DBPassword string
|
|
|
|
// Blockchain
|
|
ChainID int
|
|
QuoteTokenAddress string
|
|
QuoteTokenSymbol string
|
|
QuoteTokenDecimals int
|
|
WETHAddress string
|
|
UniswapRouterAddress string
|
|
|
|
// Support/Resistance
|
|
SRMethod string
|
|
SRRefreshHours int
|
|
SRLookbackDays int
|
|
|
|
// Risk Management
|
|
MaxDailyTrades int
|
|
MaxPositionSizeUSD float64
|
|
StopLossPercent float64
|
|
TakeProfitPercent float64
|
|
|
|
// Paper Trading
|
|
PaperTradingEnabled bool
|
|
PaperInitialETH float64
|
|
PaperInitialUSDC float64
|
|
PaperSlippagePercent float64
|
|
PaperSimulateGas bool
|
|
|
|
// Grid Configuration
|
|
GridLevels int
|
|
GridSpacingPercent float64
|
|
GridBasePrice float64
|
|
AmountPerGrid float64
|
|
|
|
// Trading Parameters
|
|
SlippageTolerance float64
|
|
GasMultiplier float64
|
|
MinProfitPercent float64
|
|
GasLimit int
|
|
|
|
// Timing
|
|
PriceCheckIntervalSeconds int
|
|
StatusReportIntervalMinutes int
|
|
PostTradeCooldownSeconds int
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
_ = godotenv.Load()
|
|
|
|
cfg := &Config{
|
|
// Secrets
|
|
DuneAPIKey: envStr("DUNE_API_KEY", ""),
|
|
WalletAddress: envStr("WALLET_ADDRESS", ""),
|
|
PrivateKey: envStr("PRIVATE_KEY", ""),
|
|
EthereumAPIEndpoint: envStr("ETHEREUM_API_ENDPOINT", ""),
|
|
WebhookURL: envStr("WEBHOOK_URL", ""),
|
|
BotName: envStr("BOT_NAME", "TrahnGridTrader"),
|
|
APIKey: envStr("API_KEY", ""),
|
|
CORSAllowOrigin: envStr("CORS_ALLOW_ORIGIN", "*"),
|
|
|
|
// Database
|
|
DBHost: envStr("DB_HOST", "localhost"),
|
|
DBPort: envInt("DB_PORT", 5432),
|
|
DBName: envStr("DB_NAME", "trahn_grid_trader"),
|
|
DBUser: envStr("DB_USER", ""),
|
|
DBPassword: envStr("DB_PASSWORD", ""),
|
|
|
|
// Blockchain
|
|
ChainID: envInt("CHAIN_ID", 1),
|
|
QuoteTokenAddress: envStr("QUOTE_TOKEN_ADDRESS", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
QuoteTokenSymbol: envStr("QUOTE_TOKEN_SYMBOL", "USDC"),
|
|
QuoteTokenDecimals: envInt("QUOTE_TOKEN_DECIMALS", 6),
|
|
WETHAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
UniswapRouterAddress: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
|
|
|
|
// Support/Resistance
|
|
SRMethod: envStr("SR_METHOD", "simple"),
|
|
SRRefreshHours: envInt("SR_REFRESH_HOURS", 48),
|
|
SRLookbackDays: envInt("SR_LOOKBACK_DAYS", 14),
|
|
|
|
// Risk Management
|
|
MaxDailyTrades: envInt("MAX_DAILY_TRADES", 50),
|
|
MaxPositionSizeUSD: envFloat("MAX_POSITION_SIZE_USD", 10000),
|
|
StopLossPercent: envFloat("STOP_LOSS_PERCENT", 0),
|
|
TakeProfitPercent: envFloat("TAKE_PROFIT_PERCENT", 0),
|
|
|
|
// Paper Trading
|
|
PaperTradingEnabled: envBool("PAPER_TRADING_ENABLED", true),
|
|
PaperInitialETH: envFloat("PAPER_INITIAL_ETH", 1.0),
|
|
PaperInitialUSDC: envFloat("PAPER_INITIAL_USDC", 1000),
|
|
PaperSlippagePercent: envFloat("PAPER_SLIPPAGE_PERCENT", 0.5),
|
|
PaperSimulateGas: envBool("PAPER_SIMULATE_GAS", true),
|
|
|
|
// Grid
|
|
GridLevels: envInt("GRID_LEVELS", 10),
|
|
GridSpacingPercent: envFloat("GRID_SPACING_PERCENT", 2),
|
|
GridBasePrice: envFloat("GRID_BASE_PRICE", 0),
|
|
AmountPerGrid: envFloat("AMOUNT_PER_GRID", 100),
|
|
|
|
// Trading Parameters
|
|
SlippageTolerance: envFloat("SLIPPAGE_TOLERANCE", 1.5),
|
|
GasMultiplier: envFloat("GAS_MULTIPLIER", 1.2),
|
|
MinProfitPercent: envFloat("MIN_PROFIT_PERCENT", 0.5),
|
|
GasLimit: envInt("GAS_LIMIT", 250000),
|
|
|
|
// Timing
|
|
PriceCheckIntervalSeconds: envInt("PRICE_CHECK_INTERVAL_SECONDS", 30),
|
|
StatusReportIntervalMinutes: envInt("STATUS_REPORT_INTERVAL_MINUTES", 60),
|
|
PostTradeCooldownSeconds: envInt("POST_TRADE_COOLDOWN_SECONDS", 60),
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
var errs []string
|
|
|
|
if c.WalletAddress == "" {
|
|
errs = append(errs, "WALLET_ADDRESS is required")
|
|
}
|
|
if !c.PaperTradingEnabled && c.PrivateKey == "" {
|
|
errs = append(errs, "PRIVATE_KEY is required for live trading")
|
|
}
|
|
if c.DuneAPIKey == "" {
|
|
fmt.Println("[WARN] DUNE_API_KEY not set — will use current price for grid center (fallback mode)")
|
|
}
|
|
if c.StopLossPercent == 0 && c.TakeProfitPercent == 0 {
|
|
fmt.Println("[WARN] STOP_LOSS_PERCENT and TAKE_PROFIT_PERCENT are both 0 — no portfolio circuit breakers active")
|
|
}
|
|
if c.MaxDailyTrades == 0 && c.MaxPositionSizeUSD == 0 {
|
|
fmt.Println("[WARN] MAX_DAILY_TRADES and MAX_POSITION_SIZE_USD are both 0 — no per-trade limits active")
|
|
}
|
|
if c.APIKey == "" {
|
|
fmt.Println("[WARN] API_KEY not set — REST API has no authentication")
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("config validation failed:\n %s", strings.Join(errs, "\n "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) Print() {
|
|
fmt.Println("=== ETH Grid Trading Bot Configuration ===")
|
|
|
|
if c.PaperTradingEnabled {
|
|
fmt.Println("════════════════════════════════════════")
|
|
fmt.Println(" PAPER TRADING MODE ENABLED")
|
|
fmt.Println(" No real transactions will execute")
|
|
fmt.Println("════════════════════════════════════════")
|
|
fmt.Printf("Paper Initial ETH: %.4f\n", c.PaperInitialETH)
|
|
fmt.Printf("Paper Initial %s: %.2f\n", c.QuoteTokenSymbol, c.PaperInitialUSDC)
|
|
fmt.Printf("Paper Slippage: 0-%.1f%%\n", c.PaperSlippagePercent)
|
|
fmt.Printf("Paper Gas Simulation: %v\n", c.PaperSimulateGas)
|
|
} else {
|
|
fmt.Println(" LIVE TRADING MODE")
|
|
}
|
|
|
|
fmt.Println("--------------------------------------")
|
|
fmt.Printf("Chain ID: %d\n", c.ChainID)
|
|
if len(c.WalletAddress) > 16 {
|
|
fmt.Printf("Wallet: %s...%s\n", c.WalletAddress[:10], c.WalletAddress[len(c.WalletAddress)-6:])
|
|
}
|
|
fmt.Printf("Trading Pair: ETH/%s\n", c.QuoteTokenSymbol)
|
|
fmt.Printf("Quote Token: %s (%s...)\n", c.QuoteTokenSymbol, truncAddr(c.QuoteTokenAddress))
|
|
fmt.Println("--------------------------------------")
|
|
fmt.Println("Grid Configuration:")
|
|
fmt.Printf(" Levels: %d\n", c.GridLevels)
|
|
fmt.Printf(" Spacing: %.1f%%\n", c.GridSpacingPercent)
|
|
fmt.Printf(" Amount/Grid: $%.0f\n", c.AmountPerGrid)
|
|
fmt.Println("--------------------------------------")
|
|
fmt.Println("Support/Resistance Configuration:")
|
|
fmt.Printf(" S/R Method: %s\n", c.SRMethod)
|
|
fmt.Printf(" S/R Refresh: every %d hours\n", c.SRRefreshHours)
|
|
fmt.Printf(" S/R Lookback: %d days\n", c.SRLookbackDays)
|
|
fmt.Printf(" Dune API: %s\n", boolLabel(c.DuneAPIKey != "", "configured", "not set (fallback mode)"))
|
|
fmt.Println("======================================")
|
|
}
|
|
|
|
func (c *Config) DSN() string {
|
|
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
|
|
c.DBUser, c.DBPassword, c.DBHost, c.DBPort, c.DBName)
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
func envStr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envInt(key string, fallback int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envFloat(key string, fallback float64) float64 {
|
|
if v := os.Getenv(key); v != "" {
|
|
if f, err := strconv.ParseFloat(v, 64); err == nil {
|
|
return f
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envBool(key string, fallback bool) bool {
|
|
if v := os.Getenv(key); v != "" {
|
|
v = strings.ToLower(v)
|
|
return v == "true" || v == "1" || v == "yes"
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func truncAddr(addr string) string {
|
|
if len(addr) > 10 {
|
|
return addr[:10]
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func boolLabel(cond bool, ifTrue, ifFalse string) string {
|
|
if cond {
|
|
return ifTrue
|
|
}
|
|
return ifFalse
|
|
}
|