매크로를 만들었는데 내가 존버하려고 매수한 특정 종목을 매크로가 팔아버리면 곤란하겠죠?!
그래서 오늘은 매크로가 건드리지 말았으면 하는 예외종목을 지정하는 기능을 진행하겠습니다.
시작하기 전, 왜 [HOLD]가 아닌 [HODL]인지에 대해서는 재미있는 일화가 있어요.
2013년 비트코인 가격이 크게 하락하던 어느 날, BitcoinTalk 포럼에 'GameKyuubi'라는 아이디를 쓰는 한 유저가 술에 취한 채로 글을 올렸다고 해요.
"나는 가격이 떨어져도 절대 팔지 않고 계속 보유할 것이다!"
하지만 여기서 "I AM HOLDING"이 아닌 "I AM HODLING"으로 오타를 쳤고,
그후 밈이되어 "Hold On for Dear Life" (살기 위해 꽉 붙잡아라 / 목숨 걸고 버텨라) 라는 새로운 의미가 부여됐다고 합니다.🤣
1. 예외 종목(HODL LIST)
포트폴리오 포맷 변경
포트폴리오를 초기화하는 initialize_vportfolio()함수에서 포맷을 변경합니다.
- 기존 포맷
{
"cash": 1000000.0, # 초기 가상 현금 100만원
"coins_owned": [], # 보유 코인 목록 (비어있음)
"strategy_params": {
"profit_take_percentage": 0.1,
"stop_loss_percentage": 0.05,
},
}
- 변경 된 포맷
{
"cash": 1000000.0, # 초기 가상 현금 100만원
"coins_owned": [], # 보유 코인 목록 (비어있음)
"strategy_params": {
"profit_take_percentage": 0.1,
"stop_loss_percentage": 0.05,
},
"hodl_list": ["PEPE", "ETH"] # 예외 항목 예시
}
HODL LIST 변경
hodl_list변경은 간단하게 vportfolio_state.json파일을 열어 변경하면 됩니다.
2. 수익실현/손절매
수익실현/손절매 목표에 도달했을 때 매도하는 로직을 구현하도록 하겠습니다.
이 로직은 main에 추가하는 함수로 3. 전체 테스트 진행에 포함되어 있습니다.
owned_coin_info = None
for coin in my_vportfolio["coins_owned"]:
if coin["symbol"] == coin_symbol:
owned_coin_info = coin
break
# --- 🔴 매도 조건 우선 확인 (보유 코인에 대해서만) ---
if owned_coin_info:
# 1. 현재가 조회
ticker = bithumb_api_client.get_ticker_info(coin_symbol)
if not ticker:
print(f"-> [{coin_symbol}] 현재가 조회 실패. 매도 판단을 건너뜁니다.")
continue # 다음 코인으로 넘어감
current_price = float(ticker['closing_price'])
avg_buy_price = owned_coin_info['avg_buy_price']
# 2. 수익률 계산
profit_rate = (current_price - avg_buy_price) / avg_buy_price
pt_rate = my_vportfolio['strategy_params']['profit_take_percentage']
sl_rate = my_vportfolio['strategy_params']['stop_loss_percentage']
print(f"-> [{coin_symbol}] 보유 중. 현재 수익률: {profit_rate:+.2%}")
# 3. 수익실현 또는 손절매 조건 확인
should_sell = False
if profit_rate >= pt_rate:
print(f"-> [{coin_symbol}] 💰 수익실현 조건 달성! (+{pt_rate:.1%}) 매도를 시도합니다.")
should_sell = True
elif profit_rate <= -sl_rate:
print(f"-> [{coin_symbol}] 🛡️ 손절매 조건 달성! (-{sl_rate:.1%}) 매도를 시도합니다.")
should_sell = True
if should_sell:
quantity_to_sell = owned_coin_info['quantity'] # 보유량 전량 매도
updated_portfolio = virtual_portfolio_manager.record_vportfolio_trade(
my_vportfolio, "sell", coin_symbol, quantity_to_sell, current_price
)
if updated_portfolio: my_vportfolio = updated_portfolio
3. 예외 종목 & 수익실현/손절매를 추가한 전체 테스트 진행
1). 코드 변경
main_trader.py의 main을 다음과 같이 변경합니다.
2025-06-08 [8] [v1.0.0 완료] 캔들스틱, 단순 이동 평균(SMA), 골든/데드 크로스의 로직에서 일부를 변경하였습니다.
if __name__ == "__main__":
# --- 봇 설정 ---
ANALYSIS_INTERVAL = "30m" # 분석할 캔들스틱 간격 (30분봉)
CHECK_INTERVAL_SECONDS = 30 * 60 # 다음 분석까지 대기할 시간 (1800초)
# --- 봇 시작 ---
trade_history.set_trade_mode(is_virtual=True)
my_vportfolio = virtual_portfolio_manager.load_vportfolio()
# while True: 루프 시작 전에 HODL 리스트 한번 출력 (확인용)
hodl_list = my_vportfolio.get("hodl_list", [])
print("=" * 50)
print(f"🚀 가상 자동매매 봇을 시작합니다! (HODL 리스트: {hodl_list})")
print(f"분석 간격: {ANALYSIS_INTERVAL}, 체크 주기: {CHECK_INTERVAL_SECONDS}초")
print("봇을 중지하려면 Ctrl+C 를 누르세요.")
print("=" * 50)
while True:
try:
# --- 1. 관심 종목 선정 ---
all_ticker_info = bithumb_api_client.get_ticker_info("ALL")
target_coins = filter_top_coins_by_trade_value(all_ticker_info, top_n=20)
# --- 2. 최종 분석 대상 확정 (HODL 리스트 필터링) ---
owned_symbols = [coin['symbol'] for coin in my_vportfolio.get('coins_owned', [])]
combined_list = list(set(target_coins + owned_symbols))
# HODL 리스트에 있는 코인들을 분석 대상에서 최종적으로 제외!
final_analysis_list = [coin for coin in combined_list if coin not in hodl_list]
print(f"\n--- 최종 분석 대상 ({len(final_analysis_list)}개) ---")
print(final_analysis_list)
print("-" * 50)
# --- 3. 최종 분석 대상을 순회하며 분석 및 매매 실행 ---
for coin_symbol in final_analysis_list:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {coin_symbol} 분석 시작...", end=" ")
# --- 보유 코인 여부 확인 ---
owned_coin_info = None
for coin in my_vportfolio["coins_owned"]:
if coin["symbol"] == coin_symbol:
owned_coin_info = coin
break
# --- 🔴 매도 조건 우선 확인 (보유 코인에 대해서만) ---
if owned_coin_info:
# 1. 현재가 조회
ticker = bithumb_api_client.get_ticker_info(coin_symbol)
if not ticker:
print(f"-> [{coin_symbol}] 현재가 조회 실패. 매도 판단을 건너뜁니다.")
continue # 다음 코인으로 넘어감
current_price = float(ticker['closing_price'])
avg_buy_price = owned_coin_info['avg_buy_price']
# 2. 수익률 계산
profit_rate = (current_price - avg_buy_price) / avg_buy_price
pt_rate = my_vportfolio['strategy_params']['profit_take_percentage']
sl_rate = my_vportfolio['strategy_params']['stop_loss_percentage']
print(f"-> [{coin_symbol}] 보유 중. 현재 수익률: {profit_rate:+.2%}")
# 3. 수익실현 또는 손절매 조건 확인
should_sell = False
if profit_rate >= pt_rate:
print(f"-> [{coin_symbol}] 💰 수익실현 조건 달성! (+{pt_rate:.1%}) 매도를 시도합니다.")
should_sell = True
elif profit_rate <= -sl_rate:
print(f"-> [{coin_symbol}] 🛡️ 손절매 조건 달성! (-{sl_rate:.1%}) 매도를 시도합니다.")
should_sell = True
if should_sell:
quantity_to_sell = owned_coin_info['quantity'] # 보유량 전량 매도
updated_portfolio = virtual_portfolio_manager.record_vportfolio_trade(
my_vportfolio, "sell", coin_symbol, quantity_to_sell, current_price
)
if updated_portfolio: my_vportfolio = updated_portfolio
# 매도 조건이 충족되면, 굳이 매수 조건을 볼 필요가 없으니 다음 코인으로 넘어감
continue
# --- 🟢 매수 조건 확인 (보유하지 않은 코인에 대해서만) ---
else:
# 캔들스틱 데이터 가져오기
candlestick_data = bithumb_api_client.get_candlestick_data(coin_symbol, chart_intervals=ANALYSIS_INTERVAL)
signal = "HOLD" # 기본 신호는 유지
if candlestick_data and len(candlestick_data) > 20:
import pandas as pd
df = pd.DataFrame(candlestick_data, columns=['timestamp', 'open', 'close', 'high', 'low', 'volume'])
df['close'] = pd.to_numeric(df['close'])
df['SMA5'] = technical_analyzer.calculate_sma(candlestick_data, period=5)
df['SMA20'] = technical_analyzer.calculate_sma(candlestick_data, period=20)
signal = check_cross_signal(df)
print(f"-> 현재 신호: {signal}")
else:
print(f"-> 현재 신호: HOLD (데이터가 충분하지 않아 매매 신호를 판단할 수 없습니다.)")
# --- 2. 신호에 따른 액션 수행 ---
if signal == "BUY":
print(f"[{coin_symbol}] 🟢 골든 크로스 발생! 매수 주문을 시도합니다.")
is_owned = False
for coin in my_vportfolio["coins_owned"]:
if coin["symbol"] == coin_symbol:
is_owned = True
break
if is_owned:
print(f"-> [{coin_symbol}] 이미 보유 중인 코인이므로 추가 매수하지 않습니다.")
else:
cash_to_invest = my_vportfolio['cash'] * 0.1 # 가용 현금의 10% 투자
# 현재가 조회
ticker = bithumb_api_client.get_ticker_info(coin_symbol)
if ticker:
current_price = float(ticker['closing_price'])
quantity_to_buy = cash_to_invest / current_price
# 가상 매수 실행
updated_portfolio = virtual_portfolio_manager.record_vportfolio_trade(my_vportfolio, "buy",
coin_symbol, quantity_to_buy,
current_price)
if updated_portfolio: my_vportfolio = updated_portfolio
elif signal == "SELL":
print(f"[{coin_symbol}] 🔴 데드 크로스 발생! 매도 주문을 시도합니다.")
quantity_to_sell = 0
for coin in my_vportfolio["coins_owned"]:
if coin["symbol"] == coin_symbol.upper():
quantity_to_sell = coin["quantity"]
break
if quantity_to_sell > 0:
ticker = bithumb_api_client.get_ticker_info(coin_symbol)
if ticker:
current_price = float(ticker['closing_price'])
# 가상 매도 실행
updated_portfolio = virtual_portfolio_manager.record_vportfolio_trade(my_vportfolio, "sell",
coin_symbol,
quantity_to_sell,
current_price)
if updated_portfolio: my_vportfolio = updated_portfolio
else:
print(f"-> {coin_symbol} 보유 수량이 없어 매도하지 않습니다.")
print(f"\n분석 완료. 다음 분석은 {CHECK_INTERVAL_SECONDS}초 후에 시작됩니다.")
time.sleep(CHECK_INTERVAL_SECONDS) # <--- 실제 운영 시 활성화
except KeyboardInterrupt:
# 사용자가 Ctrl+C를 눌러서 프로그램을 중단시킬 때
print("\n\n사용자에 의해 프로그램이 중단되었습니다. 최종 포트폴리오 상태를 저장합니다...")
virtual_portfolio_manager.save_vportfolio(my_vportfolio)
print("프로그램을 종료합니다.")
break # while 루프 탈출
except Exception as e:
# 예기치 못한 다른 에러 발생 시
print(f"⚠️ 예상치 못한 오류 발생: {e}")
print("60초 후 재시도합니다...")
time.sleep(60)
2). 테스트 진행
아래와 같이 출력되면 성공!
(전체 테스트를 위해 일부 data를 변경하였습니다.)


다음 단계
- 진행 방향 재 설정
끝!
'프로젝트 > 코인 투자 매크로' 카테고리의 다른 글
| 2025-06-25 [10] [v1.7] 현실 자산 거래 (3) | 2025.06.26 |
|---|---|
| 2025-06-21 [9] [v1.5] 연속 손실 제한 (Consecutive Loss Limit) (2) | 2025.06.21 |
| 2025-06-09 [9] 종목 선택 (0) | 2025.06.10 |
| 2025-06-08 [8] [v1.0.0 완료] 캔들스틱, 단순 이동 평균(SMA), 골든/데드 크로스 (3) | 2025.06.08 |
| 2025-06-08 [7] 가상 매수/매도 함수 구현 (1) | 2025.06.08 |