feat: add runtime diagnostics and path management
This commit is contained in:
34
README.md
34
README.md
@@ -2,22 +2,46 @@
|
||||
|
||||
CoinHunter CLI is the executable tooling layer for CoinHunter.
|
||||
|
||||
- Code lives in this repository.
|
||||
- User runtime data lives in `~/.coinhunter/`.
|
||||
- Hermes skills can call this CLI instead of embedding large script collections.
|
||||
• Code lives in this repository.
|
||||
• User runtime data lives in ~/.coinhunter/ by default.
|
||||
• Hermes skills can call this CLI instead of embedding large script collections.
|
||||
• Runtime locations can be overridden with COINHUNTER_HOME, HERMES_HOME, COINHUNTER_ENV_FILE, and HERMES_BIN.
|
||||
|
||||
## Install (editable)
|
||||
## Install
|
||||
|
||||
Editable install:
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
## Example commands
|
||||
## Core commands
|
||||
|
||||
```bash
|
||||
coinhunter --version
|
||||
coinhunter doctor
|
||||
coinhunter paths
|
||||
coinhunter init
|
||||
coinhunter check-api
|
||||
coinhunter smart-executor balances
|
||||
coinhunter precheck
|
||||
coinhunter review-context 12
|
||||
coinhunter market-probe bybit-ticker BTCUSDT
|
||||
```
|
||||
|
||||
## Runtime model
|
||||
|
||||
Default layout:
|
||||
|
||||
• ~/.coinhunter/ stores config, positions, logs, reviews, and state.
|
||||
• ~/.hermes/.env stores exchange credentials unless COINHUNTER_ENV_FILE overrides it.
|
||||
• hermes is discovered from PATH first, then ~/.local/bin/hermes, unless HERMES_BIN overrides it.
|
||||
|
||||
## Next refactor direction
|
||||
|
||||
This repository now has a dedicated runtime layer and CLI diagnostics. The next major cleanup is to split command adapters from trading services so the internal architecture becomes:
|
||||
|
||||
• commands/
|
||||
• services/
|
||||
• runtime/
|
||||
• domain logic
|
||||
|
||||
@@ -18,10 +18,13 @@ from pathlib import Path
|
||||
|
||||
import ccxt
|
||||
|
||||
from .runtime import get_runtime_paths, load_env_file
|
||||
|
||||
# ============== 配置 ==============
|
||||
COINS_DIR = Path.home() / ".coinhunter"
|
||||
POSITIONS_FILE = COINS_DIR / "positions.json"
|
||||
ENV_FILE = Path.home() / ".hermes" / ".env"
|
||||
PATHS = get_runtime_paths()
|
||||
COINS_DIR = PATHS.root
|
||||
POSITIONS_FILE = PATHS.positions_file
|
||||
ENV_FILE = PATHS.env_file
|
||||
|
||||
CST = timezone(timedelta(hours=8))
|
||||
|
||||
@@ -58,12 +61,7 @@ def save_positions(positions: list):
|
||||
|
||||
|
||||
def load_env():
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
os.environ.setdefault(key.strip(), val.strip())
|
||||
load_env_file(PATHS)
|
||||
|
||||
|
||||
def calculate_position_size(total_usdt: float, available_usdt: float, open_slots: int) -> float:
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检查自动交易的环境配置是否就绪"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from .runtime import load_env_file
|
||||
|
||||
|
||||
def main():
|
||||
env_file = Path.home() / ".hermes" / ".env"
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
load_env_file()
|
||||
|
||||
api_key = os.getenv("BINANCE_API_KEY", "")
|
||||
secret = os.getenv("BINANCE_API_SECRET", "")
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
"""CoinHunter unified CLI entrypoint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
from . import __version__
|
||||
|
||||
MODULE_MAP = {
|
||||
"smart-executor": "smart_executor",
|
||||
"auto-trader": "auto_trader",
|
||||
"precheck": "precheck",
|
||||
"check-api": "check_api",
|
||||
"doctor": "doctor",
|
||||
"external-gate": "external_gate",
|
||||
"init": "init_user_state",
|
||||
"market-probe": "market_probe",
|
||||
"paths": "paths",
|
||||
"precheck": "precheck",
|
||||
"review-context": "review_context",
|
||||
"review-engine": "review_engine",
|
||||
"market-probe": "market_probe",
|
||||
"check-api": "check_api",
|
||||
"rotate-external-gate-log": "rotate_external_gate_log",
|
||||
"init": "init_user_state",
|
||||
"smart-executor": "smart_executor",
|
||||
"auto-trader": "auto_trader",
|
||||
}
|
||||
|
||||
|
||||
class VersionAction(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
print(__version__)
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="coinhunter",
|
||||
@@ -25,7 +37,10 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
epilog=(
|
||||
"Examples:\n"
|
||||
" coinhunter doctor\n"
|
||||
" coinhunter paths\n"
|
||||
" coinhunter check-api\n"
|
||||
" coinhunter smart-executor balances\n"
|
||||
" coinhunter smart-executor hold\n"
|
||||
" coinhunter smart-executor --analysis '...' --reasoning '...' buy ENJUSDT 50\n"
|
||||
" coinhunter precheck\n"
|
||||
@@ -36,7 +51,8 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
" coinhunter init\n"
|
||||
),
|
||||
)
|
||||
parser.add_argument("command", choices=sorted(MODULE_MAP.keys()))
|
||||
parser.add_argument("--version", nargs=0, action=VersionAction, help="Print installed version and exit")
|
||||
parser.add_argument("command", nargs="?", choices=sorted(MODULE_MAP.keys()), help="CoinHunter command to run")
|
||||
parser.add_argument("args", nargs=argparse.REMAINDER)
|
||||
return parser
|
||||
|
||||
@@ -59,6 +75,9 @@ def run_python_module(module_name: str, argv: list[str]) -> int:
|
||||
def main() -> int:
|
||||
parser = build_parser()
|
||||
parsed = parser.parse_args()
|
||||
if not parsed.command:
|
||||
parser.print_help()
|
||||
return 0
|
||||
module_name = MODULE_MAP[parsed.command]
|
||||
argv = list(parsed.args)
|
||||
if argv and argv[0] == "--":
|
||||
|
||||
66
src/coinhunter/doctor.py
Normal file
66
src/coinhunter/doctor.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Runtime diagnostics for CoinHunter CLI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from .runtime import ensure_runtime_dirs, get_runtime_paths, load_env_file, resolve_hermes_executable
|
||||
|
||||
|
||||
REQUIRED_ENV_VARS = ["BINANCE_API_KEY", "BINANCE_API_SECRET"]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
paths = ensure_runtime_dirs(get_runtime_paths())
|
||||
env_file = load_env_file(paths)
|
||||
hermes_executable = resolve_hermes_executable(paths)
|
||||
|
||||
env_checks = {}
|
||||
missing_env = []
|
||||
for name in REQUIRED_ENV_VARS:
|
||||
present = bool(os.getenv(name))
|
||||
env_checks[name] = present
|
||||
if not present:
|
||||
missing_env.append(name)
|
||||
|
||||
file_checks = {
|
||||
"env_file_exists": env_file.exists(),
|
||||
"config_exists": paths.config_file.exists(),
|
||||
"positions_exists": paths.positions_file.exists(),
|
||||
"logrotate_config_exists": paths.logrotate_config.exists(),
|
||||
}
|
||||
dir_checks = {
|
||||
"root_exists": paths.root.exists(),
|
||||
"state_dir_exists": paths.state_dir.exists(),
|
||||
"logs_dir_exists": paths.logs_dir.exists(),
|
||||
"reviews_dir_exists": paths.reviews_dir.exists(),
|
||||
"cache_dir_exists": paths.cache_dir.exists(),
|
||||
}
|
||||
command_checks = {
|
||||
"hermes": bool(shutil.which("hermes") or paths.hermes_bin.exists()),
|
||||
"logrotate": bool(shutil.which("logrotate") or shutil.which("/usr/sbin/logrotate")),
|
||||
}
|
||||
|
||||
report = {
|
||||
"ok": not missing_env,
|
||||
"python": sys.version.split()[0],
|
||||
"platform": platform.platform(),
|
||||
"env_file": str(env_file),
|
||||
"hermes_executable": hermes_executable,
|
||||
"paths": paths.as_dict(),
|
||||
"env_checks": env_checks,
|
||||
"missing_env": missing_env,
|
||||
"file_checks": file_checks,
|
||||
"dir_checks": dir_checks,
|
||||
"command_checks": command_checks,
|
||||
}
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return 0 if report["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -4,13 +4,13 @@ import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path.home() / ".coinhunter"
|
||||
STATE_DIR = BASE_DIR / "state"
|
||||
LOCK_FILE = STATE_DIR / "external_gate.lock"
|
||||
from .runtime import ensure_runtime_dirs, get_runtime_paths, resolve_hermes_executable
|
||||
|
||||
PATHS = get_runtime_paths()
|
||||
STATE_DIR = PATHS.state_dir
|
||||
LOCK_FILE = PATHS.external_gate_lock
|
||||
COINHUNTER_MODULE = [sys.executable, "-m", "coinhunter"]
|
||||
HERMES_BIN = Path.home() / ".local" / "bin" / "hermes"
|
||||
TRADE_JOB_ID = "4e6593fff158"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def parse_json_output(text: str) -> dict:
|
||||
|
||||
|
||||
def main():
|
||||
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
ensure_runtime_dirs(PATHS)
|
||||
with open(LOCK_FILE, "w", encoding="utf-8") as lockf:
|
||||
try:
|
||||
fcntl.flock(lockf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
@@ -66,7 +66,7 @@ def main():
|
||||
log(f"failed to mark run requested; stdout={mark.stdout.strip()} stderr={mark.stderr.strip()}")
|
||||
return 1
|
||||
|
||||
trigger = run_cmd([str(HERMES_BIN), "cron", "run", TRADE_JOB_ID])
|
||||
trigger = run_cmd([resolve_hermes_executable(PATHS), "cron", "run", TRADE_JOB_ID])
|
||||
if trigger.returncode != 0:
|
||||
log(f"failed to trigger trade cron job; stdout={trigger.stdout.strip()} stderr={trigger.stderr.strip()}")
|
||||
return 1
|
||||
|
||||
@@ -3,8 +3,11 @@ import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path.home() / ".coinhunter"
|
||||
CACHE_DIR = ROOT / "cache"
|
||||
from .runtime import ensure_runtime_dirs, get_runtime_paths
|
||||
|
||||
PATHS = get_runtime_paths()
|
||||
ROOT = PATHS.root
|
||||
CACHE_DIR = PATHS.cache_dir
|
||||
|
||||
|
||||
def now_iso():
|
||||
@@ -19,8 +22,7 @@ def ensure_file(path: Path, payload: dict):
|
||||
|
||||
|
||||
def main():
|
||||
ROOT.mkdir(parents=True, exist_ok=True)
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
ensure_runtime_dirs(PATHS)
|
||||
|
||||
created = []
|
||||
ts = now_iso()
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import json
|
||||
import traceback
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path.home() / ".coinhunter"
|
||||
LOG_DIR = BASE_DIR / "logs"
|
||||
from .runtime import get_runtime_paths
|
||||
|
||||
LOG_DIR = get_runtime_paths().logs_dir
|
||||
SCHEMA_VERSION = 2
|
||||
|
||||
CST = timezone(timedelta(hours=8))
|
||||
|
||||
16
src/coinhunter/paths.py
Normal file
16
src/coinhunter/paths.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Print CoinHunter runtime paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from .runtime import get_runtime_paths
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(json.dumps(get_runtime_paths().as_dict(), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -10,12 +10,15 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
import ccxt
|
||||
|
||||
BASE_DIR = Path.home() / ".coinhunter"
|
||||
STATE_DIR = BASE_DIR / "state"
|
||||
STATE_FILE = STATE_DIR / "precheck_state.json"
|
||||
POSITIONS_FILE = BASE_DIR / "positions.json"
|
||||
CONFIG_FILE = BASE_DIR / "config.json"
|
||||
ENV_FILE = Path.home() / ".hermes" / ".env"
|
||||
from .runtime import get_runtime_paths, load_env_file
|
||||
|
||||
PATHS = get_runtime_paths()
|
||||
BASE_DIR = PATHS.root
|
||||
STATE_DIR = PATHS.state_dir
|
||||
STATE_FILE = PATHS.precheck_state_file
|
||||
POSITIONS_FILE = PATHS.positions_file
|
||||
CONFIG_FILE = PATHS.config_file
|
||||
ENV_FILE = PATHS.env_file
|
||||
|
||||
BASE_PRICE_MOVE_TRIGGER_PCT = 0.025
|
||||
BASE_PNL_TRIGGER_PCT = 0.03
|
||||
@@ -66,13 +69,7 @@ def load_json(path: Path, default):
|
||||
|
||||
|
||||
def load_env():
|
||||
if not ENV_FILE.exists():
|
||||
return
|
||||
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
os.environ.setdefault(key.strip(), val.strip())
|
||||
load_env_file(PATHS)
|
||||
|
||||
|
||||
def load_positions():
|
||||
|
||||
@@ -9,20 +9,17 @@ from pathlib import Path
|
||||
import ccxt
|
||||
|
||||
from .logger import get_logs_last_n_hours, log_error
|
||||
from .runtime import get_runtime_paths, load_env_file
|
||||
|
||||
ENV_FILE = Path.home() / ".hermes" / ".env"
|
||||
REVIEW_DIR = Path.home() / ".coinhunter" / "reviews"
|
||||
PATHS = get_runtime_paths()
|
||||
ENV_FILE = PATHS.env_file
|
||||
REVIEW_DIR = PATHS.reviews_dir
|
||||
|
||||
CST = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def load_env():
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
os.environ.setdefault(key.strip(), val.strip())
|
||||
load_env_file(PATHS)
|
||||
|
||||
|
||||
def get_exchange():
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rotate external gate log using the user's logrotate config/state."""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path.home() / ".coinhunter"
|
||||
STATE_DIR = BASE_DIR / "state"
|
||||
LOGROTATE_STATUS = STATE_DIR / "logrotate_external_gate.status"
|
||||
LOGROTATE_CONF = BASE_DIR / "logrotate_external_gate.conf"
|
||||
LOGS_DIR = BASE_DIR / "logs"
|
||||
from .runtime import ensure_runtime_dirs, get_runtime_paths
|
||||
|
||||
PATHS = get_runtime_paths()
|
||||
STATE_DIR = PATHS.state_dir
|
||||
LOGROTATE_STATUS = PATHS.logrotate_status
|
||||
LOGROTATE_CONF = PATHS.logrotate_config
|
||||
LOGS_DIR = PATHS.logs_dir
|
||||
|
||||
|
||||
def main():
|
||||
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
cmd = ["/usr/sbin/logrotate", "-s", str(LOGROTATE_STATUS), str(LOGROTATE_CONF)]
|
||||
ensure_runtime_dirs(PATHS)
|
||||
logrotate_bin = shutil.which("logrotate") or "/usr/sbin/logrotate"
|
||||
cmd = [logrotate_bin, "-s", str(LOGROTATE_STATUS), str(LOGROTATE_CONF)]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.stdout.strip():
|
||||
print(result.stdout.strip())
|
||||
|
||||
107
src/coinhunter/runtime.py
Normal file
107
src/coinhunter/runtime.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Runtime paths and environment helpers for CoinHunter CLI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimePaths:
|
||||
root: Path
|
||||
cache_dir: Path
|
||||
state_dir: Path
|
||||
logs_dir: Path
|
||||
reviews_dir: Path
|
||||
config_file: Path
|
||||
positions_file: Path
|
||||
accounts_file: Path
|
||||
executions_file: Path
|
||||
watchlist_file: Path
|
||||
notes_file: Path
|
||||
positions_lock: Path
|
||||
executions_lock: Path
|
||||
precheck_state_file: Path
|
||||
external_gate_lock: Path
|
||||
logrotate_config: Path
|
||||
logrotate_status: Path
|
||||
hermes_home: Path
|
||||
env_file: Path
|
||||
hermes_bin: Path
|
||||
|
||||
def as_dict(self) -> dict[str, str]:
|
||||
return {key: str(value) for key, value in asdict(self).items()}
|
||||
|
||||
|
||||
def _default_coinhunter_home() -> Path:
|
||||
raw = os.getenv("COINHUNTER_HOME")
|
||||
return Path(raw).expanduser() if raw else Path.home() / ".coinhunter"
|
||||
|
||||
|
||||
def _default_hermes_home() -> Path:
|
||||
raw = os.getenv("HERMES_HOME")
|
||||
return Path(raw).expanduser() if raw else Path.home() / ".hermes"
|
||||
|
||||
|
||||
def get_runtime_paths() -> RuntimePaths:
|
||||
root = _default_coinhunter_home()
|
||||
hermes_home = _default_hermes_home()
|
||||
state_dir = root / "state"
|
||||
return RuntimePaths(
|
||||
root=root,
|
||||
cache_dir=root / "cache",
|
||||
state_dir=state_dir,
|
||||
logs_dir=root / "logs",
|
||||
reviews_dir=root / "reviews",
|
||||
config_file=root / "config.json",
|
||||
positions_file=root / "positions.json",
|
||||
accounts_file=root / "accounts.json",
|
||||
executions_file=root / "executions.json",
|
||||
watchlist_file=root / "watchlist.json",
|
||||
notes_file=root / "notes.json",
|
||||
positions_lock=root / "positions.lock",
|
||||
executions_lock=root / "executions.lock",
|
||||
precheck_state_file=state_dir / "precheck_state.json",
|
||||
external_gate_lock=state_dir / "external_gate.lock",
|
||||
logrotate_config=root / "logrotate_external_gate.conf",
|
||||
logrotate_status=state_dir / "logrotate_external_gate.status",
|
||||
hermes_home=hermes_home,
|
||||
env_file=Path(os.getenv("COINHUNTER_ENV_FILE", str(hermes_home / ".env"))).expanduser(),
|
||||
hermes_bin=Path(os.getenv("HERMES_BIN", str(Path.home() / ".local" / "bin" / "hermes"))).expanduser(),
|
||||
)
|
||||
|
||||
|
||||
def ensure_runtime_dirs(paths: RuntimePaths | None = None) -> RuntimePaths:
|
||||
paths = paths or get_runtime_paths()
|
||||
for directory in (paths.root, paths.cache_dir, paths.state_dir, paths.logs_dir, paths.reviews_dir):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
return paths
|
||||
|
||||
|
||||
def load_env_file(paths: RuntimePaths | None = None) -> Path:
|
||||
paths = paths or get_runtime_paths()
|
||||
if paths.env_file.exists():
|
||||
for line in paths.env_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
os.environ.setdefault(key.strip(), value.strip())
|
||||
return paths.env_file
|
||||
|
||||
|
||||
def resolve_hermes_executable(paths: RuntimePaths | None = None) -> str:
|
||||
paths = paths or get_runtime_paths()
|
||||
discovered = shutil.which("hermes")
|
||||
if discovered:
|
||||
return discovered
|
||||
return str(paths.hermes_bin)
|
||||
|
||||
|
||||
def mask_secret(value: str | None, *, tail: int = 4) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
if len(value) <= tail:
|
||||
return "*" * len(value)
|
||||
return "*" * max(4, len(value) - tail) + value[-tail:]
|
||||
@@ -20,13 +20,14 @@ from pathlib import Path
|
||||
import ccxt
|
||||
|
||||
from .logger import log_decision, log_error, log_trade
|
||||
from .runtime import get_runtime_paths, load_env_file
|
||||
|
||||
BASE_DIR = Path.home() / ".coinhunter"
|
||||
POSITIONS_FILE = BASE_DIR / "positions.json"
|
||||
POSITIONS_LOCK = BASE_DIR / "positions.lock"
|
||||
EXECUTIONS_FILE = BASE_DIR / "executions.json"
|
||||
EXECUTIONS_LOCK = BASE_DIR / "executions.lock"
|
||||
ENV_FILE = Path.home() / ".hermes" / ".env"
|
||||
PATHS = get_runtime_paths()
|
||||
POSITIONS_FILE = PATHS.positions_file
|
||||
POSITIONS_LOCK = PATHS.positions_lock
|
||||
EXECUTIONS_FILE = PATHS.executions_file
|
||||
EXECUTIONS_LOCK = PATHS.executions_lock
|
||||
ENV_FILE = PATHS.env_file
|
||||
DRY_RUN = os.getenv("DRY_RUN", "false").lower() == "true"
|
||||
USDT_BUFFER_PCT = 0.03
|
||||
MIN_REMAINING_DUST_USDT = 1.0
|
||||
@@ -43,12 +44,7 @@ def bj_now_iso():
|
||||
|
||||
|
||||
def load_env():
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
os.environ.setdefault(key.strip(), val.strip())
|
||||
load_env_file(PATHS)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
Reference in New Issue
Block a user