148 lines
7.2 KiB
Plaintext
148 lines
7.2 KiB
Plaintext
"""
|
|
Grid Trading Bot — config.
|
|
All secrets MUST come from environment (.env), never from defaults in code.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
# Load .env from project root (cwd or script dir) so all os.getenv() below
|
|
# see real values, not code defaults. Safe to call multiple times.
|
|
_ENV_PATH = Path(__file__).resolve().parent / ".env"
|
|
if _ENV_PATH.exists():
|
|
load_dotenv(_ENV_PATH, override=False)
|
|
else:
|
|
# fallback: cwd/.env (for 'python main.py' from project dir)
|
|
load_dotenv(override=False)
|
|
|
|
|
|
def _require(name: str) -> str:
|
|
"""Read required env var. Empty/None → raise (only when LIVE mode is on)."""
|
|
v = os.getenv(name, "").strip()
|
|
if not v:
|
|
raise RuntimeError(
|
|
f"Environment variable {name!r} is empty. "
|
|
f"Set it in .env (see .env.example). Aborting."
|
|
)
|
|
return v
|
|
|
|
|
|
def _opt(name: str, default: str = "") -> str:
|
|
return os.getenv(name, default).strip()
|
|
|
|
|
|
# === Tradernet API credentials ===
|
|
# LIVE: ключи обязательны → main.py вызовет _require().
|
|
# DEMO: ключи могут быть пустыми (например, dry-run без сети).
|
|
TRADERNET_PUBLIC_KEY = os.getenv("TRADERNET_PUBLIC_KEY", "")
|
|
TRADERNET_PRIVATE_KEY = os.getenv("TRADERNET_PRIVATE_KEY", "")
|
|
TRADERNET_LOGIN = os.getenv("TRADERNET_LOGIN", "")
|
|
TRADERNET_PASSWORD = os.getenv("TRADERNET_PASSWORD", "")
|
|
TRADERNET_BASE_URL = os.getenv("TRADERNET_BASE_URL", "https://tradernet.by")
|
|
|
|
|
|
def require_tradernet_keys() -> None:
|
|
"""Вызывайте из main.py в начале LIVE-режима.
|
|
Падает, если хотя бы один ключ пустой."""
|
|
_require("TRADERNET_PUBLIC_KEY")
|
|
_require("TRADERNET_PRIVATE_KEY")
|
|
|
|
|
|
# === Trading settings ===
|
|
SYMBOL = os.getenv("SYMBOL", "BTC-USDT.IMEX")
|
|
QUOTE = os.getenv("QUOTE", "USDT")
|
|
BASE = os.getenv("BASE", "BTC")
|
|
|
|
# === Grid settings (default 10 levels = 20 orders total) ===
|
|
GRID_LEVELS = int(os.getenv("GRID_LEVELS", "10"))
|
|
GRID_STEP_PERCENT = float(os.getenv("GRID_STEP_PERCENT", "0.005"))
|
|
GRID_TAKE_PROFIT_PERCENT = float(os.getenv("GRID_TAKE_PROFIT_PERCENT", "0.5"))
|
|
GRID_RENEW_THRESHOLD = int(os.getenv("GRID_RENEW_THRESHOLD", "3"))
|
|
GRID_PRICE_OFFSET = float(os.getenv("GRID_PRICE_OFFSET", "0"))
|
|
|
|
# === Stale grid recovery ===
|
|
GRID_STALE_DROP_PERCENT = float(os.getenv("GRID_STALE_DROP_PERCENT", "5.0"))
|
|
|
|
# === Inactivity-driven soft rebuild ===
|
|
# Если за INACTIVITY_RESTART_HOURS часов не было ни одной сделки (BUY/SELL fill)
|
|
# И BTC отошёл от центра сетки (start_price) более чем на
|
|
# INACTIVITY_PRICE_DRIFT_PCT процентов — отменяем висящие BUY-ордера и
|
|
# пересобираем сетку вокруг текущей цены (мягкий рестарт: процесс не
|
|
# перезапускается). Применяется ТОЛЬКО в DEMO-режиме.
|
|
INACTIVITY_RESTART_HOURS = float(os.getenv("INACTIVITY_RESTART_HOURS", "1"))
|
|
INACTIVITY_PRICE_DRIFT_PCT = float(os.getenv("INACTIVITY_PRICE_DRIFT_PCT", "0.3"))
|
|
|
|
# === State ===
|
|
STATE_DIR = Path(os.getenv("STATE_DIR", str(Path.home() / ".openclaw")))
|
|
STATE_FILE = str(STATE_DIR / "grid-state.json")
|
|
|
|
# === Public server URL ===
|
|
SERVER_URL = os.getenv("SERVER_URL", "https://gridbot.kolp.pro")
|
|
|
|
# === Demo / Live ===
|
|
DEMO_MODE = os.getenv("DEMO_MODE", "true").lower() in ("true", "1", "yes")
|
|
DEMO_START_BALANCE = float(os.getenv("DEMO_START_BALANCE", "100"))
|
|
LIVE_TRADING_DRY_RUN = os.getenv("LIVE_TRADING_DRY_RUN", "true").lower() in ("true", "1", "yes")
|
|
|
|
# === Telegram ===
|
|
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
|
|
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
|
|
|
|
# === Flask ===
|
|
HOST = os.getenv("HOST", "0.0.0.0")
|
|
PORT = int(os.getenv("PORT", "5555"))
|
|
DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "yes")
|
|
|
|
# === Web auth (Basic Auth для дашборда /api/*) ===
|
|
# ВАЖНО: обязательно сменить дефолты в .env. Сейчас — deprecation-дефолты,
|
|
# которые выдаются на 1 запуск, если .env не заполнен.
|
|
WEB_USERNAME = os.getenv("WEB_USERNAME", "admin")
|
|
WEB_PASSWORD = os.getenv("WEB_PASSWORD", "changeme")
|
|
|
|
# === Kronos advisor ===
|
|
# KRONOS_ENABLED: подмешивать kronos_advice.json в параметры сетки
|
|
# KRONOS_LIVE_ENABLED: только в LIVE — реально применять советы к ордерам
|
|
# KRONOS_MODEL: "kronos-mini" | "kronos-small" (mini быстрее, small точнее)
|
|
# KRONOS_TF_MIN: таймфрейм свечей (1, 5, 15, 60, 1440)
|
|
# KRONOS_LOOKBACK: сколько свечей подавать в модель (≤ 512 для small/mini)
|
|
# KRONOS_PRED_LEN: сколько свечей прогнозировать
|
|
# KRONOS_MIN_CONFIDENCE: ниже этого — fallback на константы из .env
|
|
KRONOS_ENABLED = os.getenv("KRONOS_ENABLED", "false").lower() in ("true", "1", "yes")
|
|
KRONOS_LIVE_ENABLED = os.getenv("KRONOS_LIVE_ENABLED", "false").lower() in ("true", "1", "yes")
|
|
KRONOS_MODEL = os.getenv("KRONOS_MODEL", "kronos-mini")
|
|
KRONOS_TF_MIN = int(os.getenv("KRONOS_TF_MIN", "60"))
|
|
KRONOS_LOOKBACK = int(os.getenv("KRONOS_LOOKBACK", "400"))
|
|
KRONOS_PRED_LEN = int(os.getenv("KRONOS_PRED_LEN", "24"))
|
|
KRONOS_MIN_CONFIDENCE = float(os.getenv("KRONOS_MIN_CONFIDENCE", "0.4"))
|
|
KRONOS_ADVICE_FILE = os.getenv("KRONOS_ADVICE_FILE", str(STATE_DIR / "kronos_advice.json"))
|
|
|
|
# === Local trend detector (slope-based, on price_history) ===
|
|
# Independent of Kronos. Linear-regression slope over the last TREND_LOOKBACK
|
|
# points in price_history. When slope > TREND_THRESHOLD_PCT% AND R² >
|
|
# TREND_MIN_CONFIDENCE — bias="up" (or "down" for negative slope).
|
|
# On uptrend: shift buy_anchor up by TREND_UP_ANCHOR_SHIFT to make BUY
|
|
# levels sit closer to current price (catch pullbacks in rising trends).
|
|
# On downtrend: shift buy_anchor down (skip falling knives).
|
|
TREND_ENABLED = os.getenv("TREND_ENABLED", "false").lower() in ("true", "1", "yes")
|
|
TREND_LOOKBACK = int(os.getenv("TREND_LOOKBACK", "60"))
|
|
TREND_THRESHOLD_PCT = float(os.getenv("TREND_THRESHOLD_PCT", "0.3"))
|
|
TREND_MIN_CONFIDENCE = float(os.getenv("TREND_MIN_CONFIDENCE", "0.5"))
|
|
TREND_UP_ANCHOR_SHIFT = float(os.getenv("TREND_UP_ANCHOR_SHIFT", "0.0015"))
|
|
TREND_DOWN_ANCHOR_SHIFT = float(os.getenv("TREND_DOWN_ANCHOR_SHIFT", "0.0015"))
|
|
|
|
# === Local trend detector (slope-based, on price_history) ===
|
|
# Independent of Kronos. Linear-regression slope over the last TREND_LOOKBACK
|
|
# points in price_history. When slope > TREND_THRESHOLD_PCT% AND R² >
|
|
# TREND_MIN_CONFIDENCE — bias="up" (or "down" for negative slope).
|
|
# On uptrend: shift buy_anchor up by TREND_UP_ANCHOR_SHIFT to make BUY
|
|
# levels sit closer to current price (catch pullbacks in rising trends).
|
|
# On downtrend: shift buy_anchor down (skip falling knives).
|
|
TREND_ENABLED = os.getenv("TREND_ENABLED", "false").lower() in ("true", "1", "yes")
|
|
TREND_LOOKBACK = int(os.getenv("TREND_LOOKBACK", "60"))
|
|
TREND_THRESHOLD_PCT = float(os.getenv("TREND_THRESHOLD_PCT", "0.3"))
|
|
TREND_MIN_CONFIDENCE = float(os.getenv("TREND_MIN_CONFIDENCE", "0.5"))
|
|
TREND_UP_ANCHOR_SHIFT = float(os.getenv("TREND_UP_ANCHOR_SHIFT", "0.0015"))
|
|
TREND_DOWN_ANCHOR_SHIFT = float(os.getenv("TREND_DOWN_ANCHOR_SHIFT", "0.0015"))
|