프로젝트/코인 투자 매크로

2025-06-06 [6] 가상 자산 관리 및 거래 내역

훈 님의 개발 블로그 2025. 6. 6. 01:04

1. 가상 자산 관리

- virtual_portfolio_manager.py 생성

1). import 선언

import json # 관리의 용이를 위해 json 사용
import os  # 파일 존재 여부 확인을 위해

 

2). 파일 이름 정의

# --- 상수 정의 ---
VPORTFOLIO_FILE = "vportfolio_state.json"  # 가상 자산 상태를 저장할 파일 이름

 

3).  가상 자산 초기화 함수 정의
  - 매크로 최초 실행 시 제공 할 가상자산을 설정합니다.
  - 현금, 보유 코인, 수익실현(PT), 손절매(SL) 값 지정

# --- 가상 자산 초기화 함수 ---
def initialize_vportfolio():
    print("새로운 포트폴리오를 초기화합니다...")
    return {
        "cash": 1000000.0,  # 초기 가상 현금 100만원
        "coins_owned": [],   # 보유 코인 목록 (비어있음)
        "strategy_params": {
            "profit_take_percentage": 0.1,  # 수익실현 +10%
            "stop_loss_percentage": 0.05,   # 손절매 -5%
        },
    }

 

4).   자산 Load 함수 정의
  - 이미 자산 정보가 존재한다면 자신을 로드
  - 자산 정보가 없다면 초기 값 반환

# --- 가상 자산 로드 함수 ---
"""
지정된 파일에서 포트폴리오 상태를 로드
파일이 없으면 초기 상태를 반환
"""
def load_vportfolio(filepath=VPORTFOLIO_FILE):
    if os.path.exists(filepath):    # 파일이 존재 할 경우
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                state_data = json.load(f)
            print(f"✅ '{filepath}'에서 포트폴리오 상태를 성공적으로 불러왔습니다.")
            return state_data
        except Exception as e:
            print(f"⚠️ 파일 불러오기 중 오류 발생: {e}. 초기 포트폴리오로 시작합니다.")
            # 오류 발생 시 (예: JSON 형식이 깨졌을 때) 안전하게 초기 상태로 시작
            return initialize_vportfolio()
    else:   # 파일이 존재하지 않을 경우
        print(f"ℹ️ '{filepath}' 파일이 존재하지 않습니다. 새 포트폴리오를 시작합니다.")
        return initialize_vportfolio() # 파일이 없으면 초기 상태로 시작

 

5). 자산 Save 함수 정의
  - 현재 자산을 파일에 저장

# --- 가상 자산 저장 함수 ---
"""
주어진 포트폴리오 상태 data를 JSON 파일에 저장
"""
def save_vportfolio(state_data, filepath=VPORTFOLIO_FILE):
    try:
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(state_data, f, ensure_ascii=False, indent=4)
        print(f"✅ 포트폴리오 상태가 '{filepath}'에 성공적으로 저장되었습니다.")
        return True
    except Exception as e:
        print(f"⚠️ 파일 저장 중 오류 발생: {e}")
        return False

 

6). 자산 Update 함수 정의
  - 매수/매도를 통해 자산에 변화가 생기면 Data를 업데이트하고 파일로 저장

# --- 가상 자산 변경 함수 ---
"""
주어진 포트폴리오 데이터에서 현금을 변경하고, 변경된 포트폴리오 데이터를 파일에 저장한 후 반환
:current_vportfolio_data: 현재 포트폴리오 상태 dictionary
:amount_to_change: 변경할 현금 액수 (양수면 증가, 음수면 감소)
:return: 현금이 변경된 포트폴리오 상태 dictionary
"""
def update_vportfolio_cash(current_vportfolio_data, amount_to_change):  # filepath 인자를 받을 필요 없어짐
    if "cash" not in current_vportfolio_data:
        print("⚠️ 오류: 포트폴리오 데이터에 'cash' 키가 없습니다.")
        return current_vportfolio_data  # 변경 없이 그대로 반환

    original_cash = current_vportfolio_data["cash"]
    current_vportfolio_data["cash"] += amount_to_change

    print(f"💰 가상 현금 변경: {original_cash:.2f} KRW -> {current_vportfolio_data['cash']:.2f} KRW (변경액: {amount_to_change:+.2f} KRW)")

    # *** 변경된 핵심: 현금 변경 후 바로 파일에 저장! ***
    save_vportfolio(current_vportfolio_data)  # VPORTFOLIO_FILE 상수를 사용해서 기본 파일에 저장

    return current_vportfolio_data

 

7). 테스트용 main부

# 이 파일을 직접 실행했을 때 간단한 테스트
if __name__ == "__main__":
    # 1. 로드 시도 (파일 없으면 초기화됨)
    vportfolio = load_vportfolio()
    print("\n--- 로드된 (또는 초기화된) 포트폴리오 ---")
    print(json.dumps(vportfolio, indent=4, ensure_ascii=False))

    # 2. 약간의 변경 후 저장 테스트
    update_vportfolio_cash(vportfolio, -20000)

    # 3. 변경 후 포트폴리오 출력
    print("\n--- 값이 변경 된 포트폴리오 ---")
    print(json.dumps(vportfolio, indent=4, ensure_ascii=False))

 

8). 실행 후 결과 확인
  - 최초 실행 후 초기 Data 확인

  - 재실행 후 결과가 변경됨을 확인

 


2. 거래 내역

- trade_history.py 생성

1). import 선언

import json # 관리의 용이를 위해 json 사용
import os  # 파일 존재 여부 확인을 위해
import time # 거래 시간 저장을 위해

 

2). 파일 이름 및 거래 모드 정의
  - 가상 거래와 실제 거래를 구분하기 위해 별도의 파일 지정

# --- 상수 정의 ---
_IS_VIRTUAL_MODE = True
REAL_TRADE_HISTORY_FILE = "trade_history.json" # 거래 내역 파일 이름 상수 추가
VIRTUAL_TRADE_HISTORY_FILE = "vtrade_history.json" # 거래 내역 파일 이름 상수 추가

 

3). 거래 모드를 지정하는 함수 정의

# --- 거래 모드 설정 함수 ---
"""거래 기록 모드를 설정 (Virtual 또는 Real)."""
def set_trade_mode(is_virtual=True):
    global _IS_VIRTUAL_MODE
    _IS_VIRTUAL_MODE = is_virtual
    mode_name = "가상" if _IS_VIRTUAL_MODE else "실제"
    print(f"ℹ️ 거래 기록 모드가 '{mode_name}'으로 설정되었습니다.")

 

4). 거래 내역 파일 경로 반환 함수 정의

# --- 거래 내역 파일 경로 반환 함수 ---
"""현재 설정된 모드에 맞는 거래 내역 파일 경로를 반환"""
def _get_current_history_filepath():
    if _IS_VIRTUAL_MODE:
        return VIRTUAL_TRADE_HISTORY_FILE
    else:
        return REAL_TRADE_HISTORY_FILE

 

5). 거래 내역 저장 함수 정의

# --- 거래 내역 저장 함수 ---
"""
거래 내역을 지정된 파일(JSON 리스트 형식)에 추가
파일이 없으면 새로 만들고, 내용이 잘못되었으면 경고 후 새 내역으로 시작
:trade_info: 추가할 거래 정보 (딕셔너리)
:return: 성공 시 True, 실패 시 False
"""
def add_trade_to_history(trade_info):
    history_filepath = _get_current_history_filepath()
    history = []  # 기본적으로 빈 리스트로 시작

    if os.path.exists(history_filepath):
        try:
            with open(history_filepath, 'r', encoding='utf-8') as f:
                content = f.read()
                if content.strip():  # 파일 내용이 공백이 아닐 때만 로드 시도
                    history = json.loads(content)
                    # 로드한 데이터가 리스트 형태인지 확인
                    if not isinstance(history, list):
                        print(f"⚠️ 경고: '{history_filepath}' 파일 내용이 리스트 형식이 아닙니다. 새 거래 내역으로 시작합니다.")
                        history = []  # 잘못된 형식이면 초기화
                else:
                    # 파일은 있지만 내용이 비어있는 경우
                    history = []  # 빈 리스트로 시작
        except json.JSONDecodeError:
            # JSON 형식이 아니거나 손상된 경우
            print(f"⚠️ 경고: '{history_filepath}' 파일의 JSON 형식이 잘못되었거나 비어있습니다. 새 거래 내역으로 시작합니다.")
            history = []  # 오류 시 빈 리스트로 시작
        except Exception as e:
            print(f"⚠️ 거래 내역 파일 불러오기 중 예상치 못한 오류 발생: {e}")
            # 심각한 I/O 오류 등이면 False 반환하여 호출한 쪽에서 알 수 있게 함
            return False

    # 새 거래 내역 추가
    history.append(trade_info)

    # 업데이트된 전체 거래 내역을 파일에 저장
    try:
        with open(history_filepath, 'w', encoding='utf-8') as f:
            json.dump(history, f, ensure_ascii=False, indent=4)
        print(f"📜 거래 내역이 '{history_filepath}'에 성공적으로 추가/업데이트되었습니다.")
        return True
    except Exception as e:
        print(f"⚠️ 거래 내역 파일 저장 중 오류 발생: {e}")
        return False

 

6). 거래 내역 출력 함수 정의

# --- 거래 내역 출력 함수 ---
"""
저장된 거래 내역(JSON 파일)을 불러와서 사람이 보기 좋은 형태로 콘솔에 출력
"""
def print_trade_history():
    history_filepath = _get_current_history_filepath()

    if not os.path.exists(history_filepath):
        print(f"ℹ️ 거래 내역 파일 ('{history_filepath}')이 아직 없습니다.")
        return

    try:
        with open(history_filepath, 'r', encoding='utf-8') as f:
            content = f.read()
            if not content.strip():  # 파일 내용이 비어있는지 확인
                print("ℹ️ 거래 내역이 비어있습니다.")
                return
            history = json.loads(content)

        if not isinstance(history, list) or not history:  # 로드된 데이터가 리스트가 아니거나 비어있는 경우
            print("ℹ️ 거래 내역이 없거나 파일 형식이 잘못되었습니다.")
            return

        print("\n--- 📜 가상 거래 내역 📜 ---")
        for i, trade in enumerate(history):
            # trade dictionary에 있을 것으로 예상되는 키들 (실제 저장하는 키에 맞춰야 함)
            ts = trade.get("timestamp", "시간정보없음")
            trade_type = trade.get("type", "N/A").upper()  # BUY 또는 SELL
            symbol = trade.get("symbol", "알수없음")
            quantity = trade.get("quantity", 0)
            price = trade.get("price_per_unit", 0)
            total_value = trade.get("total_value", 0)
            cash_after_trade = trade.get("cash_after_trade", 0)

            # 현금 변동 표시 (매수면 마이너스, 매도면 플러스로 가정)
            cash_flow_indicator = ""
            if trade_type == "BUY":
                cash_flow_indicator = f"(현금 변동: -{total_value:,.0f} KRW)"
            elif trade_type == "SELL":
                cash_flow_indicator = f"(현금 변동: +{total_value:,.0f} KRW)"

            cash_balance_info = f"(거래 후 현금 : {cash_after_trade:,.0f} KRW)"

            print(f"{i + 1}. [{ts}] [{trade_type}] {symbol} {quantity}개 @ {price:,.0f} KRW (총액: {total_value:,.0f} KRW) {cash_flow_indicator} {cash_balance_info}")

        if not history:  # 이중 체크: 혹시 반복문 안 돌았으면
            print("기록된 거래가 없습니다.")
        print("--- 거래 내역 끝 ---")

    except json.JSONDecodeError:
        print(f"⚠️ 오류: 거래 내역 파일 ('{history_filepath}')의 JSON 형식이 잘못되었습니다.")
    except Exception as e:
        print(f"⚠️ 거래 내역을 불러오는 중 오류 발생: {e}")

 

7). 테스트용 main부

# 이 파일을 직접 실행했을 때 간단한 테스트 (선택 사항)
if __name__ == "__main__":
    print("\n--- 거래 내역 추가 테스트 ---")
    # 테스트용 샘플 거래 정보 (실제로는 record_vportfolio_trade 함수 등에서 생성될 데이터)
    sample_trade_1 = {
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), # 현재 시간을 보기 좋은 형태로
        "type": "buy",
        "symbol": "BTC",
        "quantity": 0.001,
        "price_per_unit": 70000000,
        "total_value": 70000.0,
        "cash_after_trade": 930000.0,
        "notes": "First virtual buy test"
    }
    add_trade_to_history(sample_trade_1)

    sample_trade_2 = {
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time() + 60)), # 1분 뒤
        "type": "sell",
        "symbol": "BTC",
        "quantity": 0.0005,
        "price_per_unit": 71000000,
        "total_value": 35500.0,
        "cash_after_trade": 965500.0,
        "notes": "Partial virtual sell for profit test"
    }
    add_trade_to_history(sample_trade_2)

    print("\n--- 저장된 거래 내역 출력 테스트 ---")
    print_trade_history()

 

7). 실행 후 결과 확인
  - 최초 실행 후 초기 Data 확인

  - 재실행 후 결과가 변경됨을 확인

 

끝!!