Files
cointrader/tests/test_log_parser.py
21in7 15fb9c158a feat: add multi-symbol dashboard support (parser, API, UI)
- Add [SYMBOL] prefix to all bot/user_data_stream log messages
- Rewrite log_parser.py with multi-symbol regex, per-symbol state tracking, symbol columns in DB schema
- Rewrite dashboard_api.py with /api/symbols endpoint, symbol query params on all endpoints, SQL injection fix
- Update App.jsx with symbol filter tabs, multi-position display, dynamic header
- Add tests for log parser (8 tests) and dashboard API (7 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:19:16 +09:00

118 lines
4.4 KiB
Python

import sys
import os
import sqlite3
import tempfile
import pytest
# dashboard/api를 import path에 추가
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "dashboard", "api"))
@pytest.fixture
def parser():
"""임시 DB로 LogParser 인스턴스 생성."""
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
db_path = f.name
import log_parser as lp
lp.DB_PATH = db_path
p = lp.LogParser()
yield p
p.conn.close()
os.unlink(db_path)
def test_parse_signal_with_symbol(parser):
"""[SYMBOL] 프리픽스가 있는 신호 로그를 파싱한다."""
line = "2026-03-06 00:15:00 | INFO | [XRPUSDT] 신호: LONG | 현재가: 2.3456 USDT"
parser._parse_line(line)
row = parser.conn.execute("SELECT * FROM candles WHERE symbol='XRPUSDT'").fetchone()
assert row is not None
assert row["price"] == 2.3456
assert row["signal"] == "LONG"
def test_parse_entry_with_symbol(parser):
"""[SYMBOL] 프리픽스가 있는 진입 로그를 파싱한다."""
line = (
"2026-03-06 00:15:00 | SUCCESS | [TRXUSDT] SHORT 진입: "
"가격=0.2345, 수량=1000.0, SL=0.2380, TP=0.2240, "
"RSI=72.31, MACD_H=-0.001234, ATR=0.005678"
)
parser._parse_line(line)
row = parser.conn.execute("SELECT * FROM trades WHERE symbol='TRXUSDT'").fetchone()
assert row is not None
assert row["direction"] == "SHORT"
assert row["entry_price"] == 0.2345
def test_parse_close_with_symbol(parser):
"""[SYMBOL] 프리픽스가 있는 청산 로그를 심볼별로 처리한다."""
# 먼저 두 심볼의 포지션을 열어놓음
entry1 = "2026-03-06 00:00:00 | SUCCESS | [XRPUSDT] LONG 진입: 가격=2.3000, 수량=100.0, SL=2.2600, TP=2.4000"
entry2 = "2026-03-06 00:00:00 | SUCCESS | [TRXUSDT] SHORT 진입: 가격=0.2345, 수량=1000.0, SL=0.2380, TP=0.2240"
parser._parse_line(entry1)
parser._parse_line(entry2)
# XRPUSDT만 청산
close_line = (
"2026-03-06 01:00:00 | INFO | [XRPUSDT] 청산 감지(TP): "
"exit=2.4000, rp=+10.0000, commission=0.1000, net_pnl=+9.9000"
)
parser._parse_line(close_line)
# XRPUSDT는 CLOSED, TRXUSDT는 여전히 OPEN
xrp = parser.conn.execute("SELECT status FROM trades WHERE symbol='XRPUSDT'").fetchone()
trx = parser.conn.execute("SELECT status FROM trades WHERE symbol='TRXUSDT'").fetchone()
assert xrp["status"] == "CLOSED"
assert trx["status"] == "OPEN"
def test_parse_bot_start_multi_symbol(parser):
"""멀티심볼 봇 시작 로그를 각각 파싱한다."""
lines = [
"2026-03-06 00:04:54 | INFO | [XRPUSDT] 봇 시작, 레버리지 10x",
"2026-03-06 00:04:54 | INFO | [TRXUSDT] 봇 시작, 레버리지 10x",
"2026-03-06 00:04:54 | INFO | [DOGEUSDT] 봇 시작, 레버리지 10x",
]
for line in lines:
parser._parse_line(line)
symbols = parser.conn.execute(
"SELECT value FROM bot_status WHERE key LIKE '%:last_start'"
).fetchall()
assert len(symbols) == 3
def test_candles_table_has_symbol_column(parser):
"""candles 테이블에 symbol 컬럼이 있어야 한다."""
info = parser.conn.execute("PRAGMA table_info(candles)").fetchall()
col_names = [row[1] for row in info]
assert "symbol" in col_names
def test_daily_pnl_table_has_symbol_column(parser):
"""daily_pnl 테이블에 symbol 컬럼이 있어야 한다."""
info = parser.conn.execute("PRAGMA table_info(daily_pnl)").fetchall()
col_names = [row[1] for row in info]
assert "symbol" in col_names
def test_balance_log_with_symbol(parser):
"""[SYMBOL] 프리픽스가 있는 잔고 로그를 파싱한다."""
line = "2026-03-06 00:04:54 | INFO | [XRPUSDT] 기준 잔고 설정: 44.81 USDT (동적 증거금 비율 기준점)"
parser._parse_line(line)
row = parser.conn.execute("SELECT value FROM bot_status WHERE key='balance'").fetchone()
assert row is not None
assert row["value"] == "44.81"
def test_position_recover_with_symbol(parser):
"""[SYMBOL] 프리픽스가 있는 포지션 복구 로그를 파싱한다."""
line = "2026-03-06 00:04:54 | INFO | [DOGEUSDT] 기존 포지션 복구: LONG | 진입가=0.1800 | 수량=500.0"
parser._parse_line(line)
row = parser.conn.execute("SELECT * FROM trades WHERE symbol='DOGEUSDT'").fetchone()
assert row is not None
assert row["direction"] == "LONG"
assert row["entry_price"] == 0.1800