예제 소스
암호화폐 자동매매를 위한 CNN 시각 정책망과 VLM · 파일 59개
test_img.py
import argparse
import os
from datetime import datetime, timedelta
from io import StringIO
import sys
import numpy as np
import pandas as pd
from ChartImageGenerator import ChartImageConfig, ChartImageGenerator
def _dummy_df(rows: int = 600) -> pd.DataFrame:
"""
차트 생성 테스트용 더미 OHLCV + 지표 컬럼 생성.
(실데이터가 없을 때도 test_img.py가 바로 돌아가게 하기 위함)
"""
rng = np.random.default_rng(42)
base = 100_000 + np.cumsum(rng.normal(0, 200, size=rows))
high = base + rng.uniform(50, 400, size=rows)
low = base - rng.uniform(50, 400, size=rows)
open_ = base + rng.normal(0, 50, size=rows)
close = base + rng.normal(0, 50, size=rows)
volume = rng.uniform(10, 1000, size=rows)
df = pd.DataFrame(
{
"open": open_,
"high": high,
"low": low,
"close": close,
"volume": volume,
}
)
# 간단 지표(필요 컬럼) 구성
df["ma20"] = df["close"].rolling(20).mean()
df["ma60"] = df["close"].rolling(60).mean()
df["ma120"] = df["close"].rolling(120).mean()
mid = df["close"].rolling(40).mean()
std = df["close"].rolling(40).std()
df["middle"] = mid
df["upper"] = mid + (std * 2.3)
df["lower"] = mid - (std * 2.3)
# ma20_deg 근사
df["ma20_deg"] = (df["ma20"] - df["ma20"].shift()).fillna(0)
# PB, MFI10 더미
df["PB"] = ((df["close"] - df["lower"]) / (df["upper"] - df["lower"])).replace(
[np.inf, -np.inf], np.nan
)
df["PB"] = df["PB"].fillna(0.5).clip(0, 1)
df["MFI10"] = (50 + rng.normal(0, 10, size=rows)).clip(0, 100)
# 시간 컬럼(옵션)
start = datetime.now() - timedelta(minutes=rows * 3)
df["time"] = [start + timedelta(minutes=3 * i) for i in range(rows)]
return df.ffill().fillna(0)
def _load_df(path: str | None) -> pd.DataFrame:
if not path:
return _dummy_df()
if not os.path.exists(path):
raise FileNotFoundError(path)
if path.lower().endswith(".csv"):
return pd.read_csv(path)
if path.lower().endswith(".parquet"):
return pd.read_parquet(path)
raise ValueError("지원 포맷: .csv 또는 .parquet")
def _load_df_from_redis(
*,
host: str = "localhost",
port: int = 6379,
db: int = 0,
key: str = "cul_df",
) -> pd.DataFrame:
"""
main_ri_findsell_dqn_v8_real.py / RealTimeMarket와 동일하게
Redis의 JSON(records) 형태 DataFrame을 읽어옵니다.
"""
try:
import redis # type: ignore
except ImportError as e:
raise ImportError("Redis에서 읽으려면 redis 패키지가 필요합니다. `pip install redis`") from e
r = redis.StrictRedis(host=host, port=port, db=db)
raw = r.get(key)
if not raw:
raise KeyError(f"Redis key '{key}'에 데이터가 없습니다.")
df_str = raw.decode("utf-8") if isinstance(raw, (bytes, bytearray)) else str(raw)
df = pd.read_json(StringIO(df_str), orient="records")
if df is None or df.empty:
raise ValueError(f"Redis key '{key}'의 DataFrame이 비어있습니다.")
return df
def _png_bytes_to_jpg_file(png_bytes: bytes, out_path: str, quality: int = 92) -> None:
try:
from PIL import Image
except ImportError as e:
raise ImportError("jpg 저장에는 Pillow가 필요합니다. `pip install pillow`") from e
from io import BytesIO
im = Image.open(BytesIO(png_bytes)).convert("RGB")
im.save(out_path, format="JPEG", quality=quality, optimize=True)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--csv", default=None, help="입력 데이터(.csv/.parquet). 없으면 더미 데이터 사용")
ap.add_argument("--redis", action="store_true", help="Redis에서 cul_df를 읽어 이미지 생성")
ap.add_argument("--redis_host", default="localhost", help="Redis host")
ap.add_argument("--redis_port", type=int, default=6379, help="Redis port")
ap.add_argument("--redis_db", type=int, default=0, help="Redis db index")
ap.add_argument("--redis_key", default="cul_df", help="Redis key (기본: cul_df)")
ap.add_argument("--outdir", default="out_imgs", help="이미지 저장 폴더")
ap.add_argument("--ticker", default="KRW-ETH", help="차트 타이틀용 티커 문자열")
ap.add_argument("--idx", type=int, default=500, help="df.head(idx) 기준 idx")
ap.add_argument("--max_data", type=int, default=300, help="화면에 그릴 최대 데이터 수")
ap.add_argument("--n_slices", type=int, default=3, help="가로 분할 조각 수")
ap.add_argument("--overlap_px", type=int, default=0, help="조각 간 겹침 픽셀")
ap.add_argument("--scale", type=int, default=2, help="plotly 렌더링 scale")
ap.add_argument("--debug_env", action="store_true", help="실행 python/plotly/kaleido 환경 출력")
args = ap.parse_args()
os.makedirs(args.outdir, exist_ok=True)
if args.debug_env:
print("python:", sys.executable)
print("version:", sys.version.replace("\n", " "))
try:
import plotly
print("plotly:", plotly.__version__, plotly.__file__)
except Exception as e:
print("plotly import FAIL:", repr(e))
try:
import kaleido
print("kaleido:", getattr(kaleido, "__version__", None), kaleido.__file__)
except Exception as e:
print("kaleido import FAIL:", repr(e))
if args.redis:
df = _load_df_from_redis(
host=args.redis_host,
port=args.redis_port,
db=args.redis_db,
key=args.redis_key,
)
else:
df = _load_df(args.csv)
gen = ChartImageGenerator(
ticker=args.ticker,
config=ChartImageConfig(show=False),
)
base_png, parts_png = gen.render_and_slice_by_x(
df,
idx=args.idx,
max_data=args.max_data,
ticker=args.ticker,
n_slices=args.n_slices,
overlap_px=args.overlap_px,
scale=args.scale,
)
origin_path = os.path.join(args.outdir, "origin.jpg")
_png_bytes_to_jpg_file(base_png, origin_path)
for i, p in enumerate(parts_png):
out_path = os.path.join(args.outdir, f"slice_{i:02d}.jpg")
_png_bytes_to_jpg_file(p, out_path)
print(f"saved: {origin_path}")
print(f"saved slices: {len(parts_png)} (dir: {args.outdir})")
if __name__ == "__main__":
main()