fix: restore positions on restart and fix notify env loading
- restore_positions(): read Upbit balances on startup to prevent double-buying after restart - notify.py: read TOKEN/CHAT_ID inside _send() to avoid empty values when module is imported before load_dotenv() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,19 +9,19 @@ import requests
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_TOKEN = os.getenv("TELEGRAM_TRADE_TOKEN", "")
|
|
||||||
_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
|
|
||||||
_API = "https://api.telegram.org/bot{token}/sendMessage"
|
_API = "https://api.telegram.org/bot{token}/sendMessage"
|
||||||
|
|
||||||
|
|
||||||
def _send(text: str) -> None:
|
def _send(text: str) -> None:
|
||||||
if not _TOKEN or not _CHAT_ID:
|
token = os.getenv("TELEGRAM_TRADE_TOKEN", "")
|
||||||
|
chat_id = os.getenv("TELEGRAM_CHAT_ID", "")
|
||||||
|
if not token or not chat_id:
|
||||||
logger.warning("Telegram 설정 없음, 알림 스킵")
|
logger.warning("Telegram 설정 없음, 알림 스킵")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
requests.post(
|
requests.post(
|
||||||
_API.format(token=_TOKEN),
|
_API.format(token=token),
|
||||||
json={"chat_id": _CHAT_ID, "text": text, "parse_mode": "HTML"},
|
json={"chat_id": chat_id, "text": text, "parse_mode": "HTML"},
|
||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -39,6 +39,38 @@ def get_positions() -> dict:
|
|||||||
return _positions
|
return _positions
|
||||||
|
|
||||||
|
|
||||||
|
def restore_positions() -> None:
|
||||||
|
"""시작 시 Upbit 실제 잔고를 읽어 포지션 복원 (재시작 이중 매수 방지)."""
|
||||||
|
upbit = _get_upbit()
|
||||||
|
balances = upbit.get_balances()
|
||||||
|
for b in balances:
|
||||||
|
currency = b["currency"]
|
||||||
|
if currency == "KRW":
|
||||||
|
continue
|
||||||
|
amount = float(b["balance"]) + float(b["locked"])
|
||||||
|
if amount <= 0:
|
||||||
|
continue
|
||||||
|
ticker = f"KRW-{currency}"
|
||||||
|
current = pyupbit.get_current_price(ticker)
|
||||||
|
if not current:
|
||||||
|
continue
|
||||||
|
invested_krw = int(amount * current)
|
||||||
|
if invested_krw < 1_000: # 소액 잔고 무시
|
||||||
|
continue
|
||||||
|
with _lock:
|
||||||
|
_positions[ticker] = {
|
||||||
|
"buy_price": current, # 정확한 매수가 불명 → 현재가로 초기화
|
||||||
|
"peak_price": current,
|
||||||
|
"amount": amount,
|
||||||
|
"invested_krw": min(invested_krw, PER_POSITION),
|
||||||
|
"entry_time": datetime.now(),
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
f"[복원] {ticker} 수량={amount} | 현재가={current:,.0f}원 "
|
||||||
|
f"(재시작 시 복원, 매수가 불명으로 현재가 기준)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def buy(ticker: str) -> bool:
|
def buy(ticker: str) -> bool:
|
||||||
"""시장가 매수. 예산·포지션 수 확인 후 진입."""
|
"""시장가 매수. 예산·포지션 수 확인 후 진입."""
|
||||||
with _lock:
|
with _lock:
|
||||||
|
|||||||
4
main.py
4
main.py
@@ -17,10 +17,14 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
|
|
||||||
from core.monitor import run_monitor
|
from core.monitor import run_monitor
|
||||||
|
from core.trader import restore_positions
|
||||||
from daemon.runner import run_scanner
|
from daemon.runner import run_scanner
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
# 재시작 시 기존 잔고 복원 (이중 매수 방지)
|
||||||
|
restore_positions()
|
||||||
|
|
||||||
# 트레일링 스탑 감시 스레드 (10초 주기)
|
# 트레일링 스탑 감시 스레드 (10초 주기)
|
||||||
monitor_thread = threading.Thread(
|
monitor_thread = threading.Thread(
|
||||||
target=run_monitor, args=(10,), daemon=True, name="monitor"
|
target=run_monitor, args=(10,), daemon=True, name="monitor"
|
||||||
|
|||||||
Reference in New Issue
Block a user