Initial public release
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import typer
|
||||
from hydra.cli import kill, status, trade, market, strategy, module
|
||||
from hydra.cli import setup_wizard
|
||||
|
||||
app = typer.Typer(name="hydra", help="HYDRA 자동매매 시스템")
|
||||
app.add_typer(kill.app, name="kill")
|
||||
app.add_typer(status.app, name="status")
|
||||
app.add_typer(trade.app, name="trade")
|
||||
app.add_typer(market.app, name="market")
|
||||
app.add_typer(strategy.app, name="strategy")
|
||||
app.add_typer(module.app, name="module")
|
||||
|
||||
app.command("setup")(setup_wizard.run_setup)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
@@ -0,0 +1,30 @@
|
||||
import asyncio
|
||||
import typer
|
||||
import httpx
|
||||
from hydra.config.settings import get_settings
|
||||
|
||||
app = typer.Typer(help="Kill Switch - 전 포지션 즉시 청산")
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def kill(reason: str = typer.Option("cli_manual", help="청산 사유")):
|
||||
"""전 포지션 즉시 시장가 청산."""
|
||||
confirm = typer.confirm("[주의] 전 포지션을 청산합니다. 계속하시겠습니까?")
|
||||
if not confirm:
|
||||
typer.echo("취소됨.")
|
||||
raise typer.Exit()
|
||||
|
||||
settings = get_settings()
|
||||
try:
|
||||
resp = httpx.post(
|
||||
"http://127.0.0.1:8000/killswitch",
|
||||
params={"reason": reason},
|
||||
headers={"X-HYDRA-KEY": settings.hydra_api_key},
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
typer.echo(f"[완료] Kill Switch 실행. 청산: {len(data.get('closed', []))}개")
|
||||
except httpx.ConnectError:
|
||||
typer.echo("[오류] HYDRA 서버에 연결할 수 없습니다. 서버가 실행 중인지 확인하세요.")
|
||||
raise typer.Exit(1)
|
||||
@@ -0,0 +1,31 @@
|
||||
import typer
|
||||
from hydra.config.markets import MarketManager
|
||||
|
||||
app = typer.Typer(help="시장 활성화/비활성화")
|
||||
|
||||
|
||||
@app.command()
|
||||
def enable(market: str, mode: str = typer.Option("paper", help="paper / live")):
|
||||
"""시장 활성화."""
|
||||
mm = MarketManager()
|
||||
mm.enable(market, mode)
|
||||
typer.echo(f"[완료] {market} 활성화 ({mode} 모드)")
|
||||
|
||||
|
||||
@app.command()
|
||||
def disable(market: str):
|
||||
"""시장 비활성화."""
|
||||
mm = MarketManager()
|
||||
mm.disable(market)
|
||||
typer.echo(f"[완료] {market} 비활성화")
|
||||
|
||||
|
||||
@app.command()
|
||||
def list_markets():
|
||||
"""활성화된 시장 목록."""
|
||||
mm = MarketManager()
|
||||
active = mm.get_active_markets()
|
||||
if active:
|
||||
typer.echo("활성 시장: " + ", ".join(active))
|
||||
else:
|
||||
typer.echo("활성화된 시장 없음. 'hydra market enable <market>'로 활성화하세요.")
|
||||
@@ -0,0 +1,24 @@
|
||||
import typer
|
||||
|
||||
app = typer.Typer(help="AI 모듈 관리")
|
||||
|
||||
MODULES = ["regime_detection", "signal_scoring", "feature_selection", "adaptive_retrain",
|
||||
"crash_detection", "dynamic_sizing", "sentiment"]
|
||||
|
||||
|
||||
@app.command()
|
||||
def enable(name: str):
|
||||
if name not in MODULES:
|
||||
typer.echo(f"[오류] 알 수 없는 모듈: {name}. 사용 가능: {MODULES}")
|
||||
raise typer.Exit(1)
|
||||
typer.echo(f"모듈 활성화: {name} - Phase 1에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def disable(name: str):
|
||||
typer.echo(f"모듈 비활성화: {name} - Phase 1에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def list_modules():
|
||||
typer.echo("AI 모듈 목록:\n" + "\n".join(f" - {m}" for m in MODULES))
|
||||
@@ -0,0 +1,122 @@
|
||||
import sys
|
||||
import psutil
|
||||
import typer
|
||||
from pathlib import Path
|
||||
|
||||
from hydra.config.keys import KeyManager
|
||||
from hydra.config.markets import MarketManager
|
||||
from hydra.logging.setup import configure_logging
|
||||
|
||||
|
||||
MARKETS = {
|
||||
"kr": "한국 주식 (KIS)",
|
||||
"us": "미국 주식 (KIS)",
|
||||
"upbit": "업비트 (암호화폐)",
|
||||
"binance": "바이낸스 (암호화폐)",
|
||||
"hl": "Hyperliquid ([주의] 고위험)",
|
||||
"poly": "Polymarket 예측시장 ([주의] 고위험)",
|
||||
}
|
||||
|
||||
|
||||
def detect_hardware() -> dict:
|
||||
return {
|
||||
"cpu_cores": psutil.cpu_count(logical=False),
|
||||
"ram_gb": round(psutil.virtual_memory().total / 1024**3),
|
||||
"disk_gb": round(psutil.disk_usage("/").total / 1024**3),
|
||||
}
|
||||
|
||||
|
||||
def recommend_profile(hw: dict) -> str:
|
||||
if hw["ram_gb"] >= 32 and hw["cpu_cores"] >= 16:
|
||||
return "expert"
|
||||
elif hw["ram_gb"] >= 16 and hw["cpu_cores"] >= 8:
|
||||
return "pro"
|
||||
return "lite"
|
||||
|
||||
|
||||
def run_setup():
|
||||
"""7단계 HYDRA 초기 설정 위자드."""
|
||||
configure_logging()
|
||||
typer.echo("\nHYDRA 설정 위자드에 오신 것을 환영합니다.\n")
|
||||
|
||||
# Step 1: 하드웨어 감지
|
||||
typer.echo("-- Step 1/7: 하드웨어 감지 --")
|
||||
hw = detect_hardware()
|
||||
typer.echo(f" CPU: {hw['cpu_cores']}코어 RAM: {hw['ram_gb']}GB Disk: {hw['disk_gb']}GB")
|
||||
|
||||
# Step 2: 프로필 추천
|
||||
typer.echo("\n-- Step 2/7: 프로필 선택 --")
|
||||
recommended = recommend_profile(hw)
|
||||
typer.echo(f" 추천 프로필: {recommended.upper()}")
|
||||
profile = typer.prompt(" 프로필 선택 [lite/pro/expert]", default=recommended)
|
||||
if profile not in ("lite", "pro", "expert"):
|
||||
typer.echo("[오류] 잘못된 프로필입니다. lite, pro, expert 중 선택하세요.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Step 3: AI 선택
|
||||
typer.echo("\n-- Step 3/7: AI 모드 선택 --")
|
||||
typer.echo(" [1] OFF (규칙 기반만) [2] 경량 CPU [3] GPU [4] 커스텀")
|
||||
ai_choice = typer.prompt(" 선택", default="1")
|
||||
ai_mode = {"1": "off", "2": "cpu", "3": "gpu", "4": "custom"}.get(ai_choice, "off")
|
||||
|
||||
# Step 4: 인터페이스
|
||||
typer.echo("\n-- Step 4/7: 인터페이스 선택 --")
|
||||
typer.echo(" [1] CLI+Telegram [2] Dashboard+Telegram [3] 전부 [4] Telegram만")
|
||||
interface = typer.prompt(" 선택", default="1")
|
||||
|
||||
# Step 5: 면책조항 동의
|
||||
typer.echo("\n-- Step 5/7: 면책조항 동의 --")
|
||||
disclaimer = Path("DISCLAIMER.md").read_text(encoding="utf-8") if Path("DISCLAIMER.md").exists() else ""
|
||||
typer.echo(disclaimer)
|
||||
accepted = typer.confirm("\n위 면책조항에 동의하십니까?")
|
||||
if not accepted:
|
||||
typer.echo("면책조항에 동의하지 않으면 설치를 진행할 수 없습니다.")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5b: 시장 선택 + API 키
|
||||
typer.echo("\n-- 시장 선택 --")
|
||||
selected_markets = []
|
||||
mm = MarketManager()
|
||||
km = KeyManager()
|
||||
|
||||
for market_id, label in MARKETS.items():
|
||||
if typer.confirm(f" {label} 사용?", default=False):
|
||||
selected_markets.append(market_id)
|
||||
mode = typer.prompt(f" {label} 모드 [paper/live]", default="paper")
|
||||
mm.enable(market_id, mode)
|
||||
|
||||
if market_id in ("kr", "us"):
|
||||
app_key = typer.prompt(f" KIS App Key ({label})", hide_input=True)
|
||||
secret = typer.prompt(f" KIS App Secret ({label})", hide_input=True)
|
||||
km.store(f"kis_{market_id}", app_key, secret)
|
||||
account_no = typer.prompt(" KIS 계좌번호 (예: 50123456-01)")
|
||||
km.store("kis_account", account_no, "")
|
||||
elif market_id in ("upbit", "binance", "hl"):
|
||||
api_key = typer.prompt(f" {label} API Key", hide_input=True)
|
||||
secret = typer.prompt(f" {label} Secret", hide_input=True)
|
||||
km.store(market_id, api_key, secret)
|
||||
|
||||
# Step 6: 벤치마크
|
||||
typer.echo("\n-- Step 6/7: 성능 벤치마크 실행 --")
|
||||
typer.echo(" (잠시 기다려 주세요...)")
|
||||
import subprocess
|
||||
try:
|
||||
subprocess.run(["python", "scripts/benchmark.py", "--profile", profile], timeout=30)
|
||||
except Exception:
|
||||
typer.echo(" 벤치마크 스킵 (scripts/benchmark.py 없음)")
|
||||
|
||||
# Step 7: 설정 저장
|
||||
typer.echo("\n-- Step 7/7: 설정 완료 --")
|
||||
env_content = f"""HYDRA_PROFILE={profile}
|
||||
HYDRA_API_KEY={_generate_api_key()}
|
||||
REDIS_URL=redis://localhost:6379
|
||||
"""
|
||||
Path(".env").write_text(env_content)
|
||||
typer.echo("\n[완료] 설정이 저장되었습니다.")
|
||||
typer.echo(f" 프로필: {profile.upper()} | AI: {ai_mode} | 시장: {', '.join(selected_markets) or '없음'}")
|
||||
typer.echo(" hydra start 또는 docker compose -f docker-compose.lite.yml up 으로 시작하세요.")
|
||||
|
||||
|
||||
def _generate_api_key() -> str:
|
||||
import secrets
|
||||
return secrets.token_urlsafe(32)
|
||||
@@ -0,0 +1,60 @@
|
||||
import typer
|
||||
import httpx
|
||||
from hydra.config.settings import get_settings
|
||||
|
||||
app = typer.Typer(help="시스템 상태 확인")
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def status():
|
||||
"""HYDRA 상태 확인."""
|
||||
settings = get_settings()
|
||||
try:
|
||||
h = httpx.get("http://127.0.0.1:8000/health", timeout=5)
|
||||
s = httpx.get(
|
||||
"http://127.0.0.1:8000/status",
|
||||
headers={"X-HYDRA-KEY": settings.hydra_api_key},
|
||||
timeout=5,
|
||||
)
|
||||
r = httpx.get(
|
||||
"http://127.0.0.1:8000/risk",
|
||||
headers={"X-HYDRA-KEY": settings.hydra_api_key},
|
||||
timeout=5,
|
||||
)
|
||||
p = httpx.get(
|
||||
"http://127.0.0.1:8000/pnl",
|
||||
headers={"X-HYDRA-KEY": settings.hydra_api_key},
|
||||
timeout=5,
|
||||
)
|
||||
typer.echo(f"[정상] 서버 상태 정상 | 프로필: {s.json()['profile']} | 가동시간: {h.json()['uptime_seconds']}초")
|
||||
|
||||
risk = r.json()
|
||||
ks = "ACTIVE" if risk["kill_switch_active"] else "NORMAL"
|
||||
typer.echo(f"Kill Switch: {ks} | 일일 손익(리스크): {risk['daily_pnl_pct']*100:.2f}%")
|
||||
|
||||
pnl = p.json()
|
||||
sign = lambda v: "+" if v >= 0 else ""
|
||||
typer.echo(
|
||||
f"\n=== 손익 현황 ===\n"
|
||||
f" 실현 손익 (누적): {sign(pnl['realized_total'])}{pnl['realized_total']:,.4f} USDT\n"
|
||||
f" 실현 손익 (오늘): {sign(pnl['daily_realized'])}{pnl['daily_realized']:,.4f} USDT\n"
|
||||
f" 미실현 손익: {sign(pnl['unrealized'])}{pnl['unrealized']:,.4f} USDT\n"
|
||||
f" 총 손익: {sign(pnl['total_pnl'])}{pnl['total_pnl']:,.4f} USDT\n"
|
||||
f" 체결 거래 수: {pnl['trade_count']}건"
|
||||
)
|
||||
|
||||
if pnl["positions"]:
|
||||
typer.echo("\n -- 오픈 포지션 --")
|
||||
for pos in pnl["positions"]:
|
||||
lev = f" {pos['leverage']}x" if pos.get("leverage", 1) > 1 else ""
|
||||
upnl = pos.get("unrealized_pnl", 0)
|
||||
typer.echo(
|
||||
f" [{pos['market']}] {pos['symbol']} {pos['side'].upper()}{lev} "
|
||||
f" 수량: {pos['qty']} 평균단가: {pos['avg_price']} "
|
||||
f"미실현: {sign(upnl)}{upnl:,.4f}"
|
||||
)
|
||||
else:
|
||||
typer.echo("\n 오픈 포지션 없음")
|
||||
|
||||
except httpx.ConnectError:
|
||||
typer.echo("[오류] 서버 오프라인")
|
||||
@@ -0,0 +1,18 @@
|
||||
import typer
|
||||
|
||||
app = typer.Typer(help="전략 관리 (Phase 2에서 구현)")
|
||||
|
||||
|
||||
@app.command()
|
||||
def list_strategies():
|
||||
typer.echo("전략 목록 - Phase 2에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def start(name: str):
|
||||
typer.echo(f"전략 시작: {name} - Phase 2에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def stop(name: str):
|
||||
typer.echo(f"전략 중지: {name} - Phase 2에서 구현 예정")
|
||||
@@ -0,0 +1,40 @@
|
||||
import typer
|
||||
from typing import Optional
|
||||
|
||||
app = typer.Typer(help="수동 매매 명령 (Phase 1에서 전략 연동)")
|
||||
|
||||
|
||||
@app.command()
|
||||
def kr(symbol: str, side: str, qty: float):
|
||||
"""한국 주식 수동 주문."""
|
||||
typer.echo(f"[한국주식] {side} {symbol} {qty}주 - Phase 1에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def us(symbol: str, side: str, qty: float):
|
||||
"""미국 주식 수동 주문."""
|
||||
typer.echo(f"[미국주식] {side} {symbol} {qty}주 - Phase 1에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def crypto(
|
||||
exchange: str,
|
||||
symbol: str,
|
||||
side: str,
|
||||
qty: float,
|
||||
leverage: int = typer.Option(1, "--leverage", "-l", help="선물 레버리지 배수 (1~125x). 현물은 1로 고정.", min=1, max=125),
|
||||
futures: bool = typer.Option(False, "--futures", help="선물 주문 여부"),
|
||||
):
|
||||
"""암호화폐 수동 주문. 선물은 --futures --leverage N 으로 레버리지 지정."""
|
||||
if futures and leverage > 1:
|
||||
typer.echo(f"[{exchange}] {side} {symbol} {qty} x {leverage} 레버리지 (선물) - Phase 1에서 구현 예정")
|
||||
elif futures:
|
||||
typer.echo(f"[{exchange}] {side} {symbol} {qty} (선물) - Phase 1에서 구현 예정")
|
||||
else:
|
||||
typer.echo(f"[{exchange}] {side} {symbol} {qty} (현물) - Phase 1에서 구현 예정")
|
||||
|
||||
|
||||
@app.command()
|
||||
def poly(market_id: str, side: str, amount: float):
|
||||
"""Polymarket 예측시장 주문."""
|
||||
typer.echo(f"[Polymarket] {side} {market_id} ${amount} - Phase 1에서 구현 예정")
|
||||
Reference in New Issue
Block a user