본문 바로가기

Quant

재진입과 데이터 분석

대학생 때 창업에 도전했었다.

동아리 선배들과 스쿠버 강사 어플 개발에 도전했다가 각자 군대, 육아, 이직의 문제로 해산했고,

친구와 파충류 사업에 도전했다가 나는 포기하고 그 친구는 꾸준히 해서 자리를 잡았다.

 

그때 든 생각은 해당 도메인에 대한 이해도가 높은 Specialist의 중요성이었는데 최근 생각이 바뀌었다.

생성형 AI의 발달로 다시 Generalist의 시대가 된 것 같다.

 

지금하는 시스템 트레이딩도 TabNine, ChatGPT, Claude 등 최신 생성형 AI를 활용해서 하고 있고,

트레이딩 인사이트, BE 엔지니어링, 데이터 분석, 인프라 세팅 등 혼자서 하기 힘든걸 덕분에 어찌저찌 굴러가게 하고 있다. AI 기술을 활용 못 했다면 이 프로젝트도 중간에 포기하지 않았을까...

chatGPT로 코드 짜다가 문득 든 생각이다.


현재는 손절가를 넘어간 종목은 재진입하지 못하도록 설정을 해두었다.

수익을 극대화 하기 위해 매도 내역을 분석하여 재진입 로직을 업데이트 하려한다.

 

 

1. 데이터 전처리

# 1. 날짜형식으로 변환 (이미 datetime이면 생략 가능)
df['buy_time'] = pd.to_datetime(df['buy_time'])
df['sell_time'] = pd.to_datetime(df['sell_time'])

# 2. 당일 매매가 원칙이므로 buy_time에서 날짜 정보만 추출
df['trade_date'] = df['buy_time'].dt.date

# 3. 각 거래별 수익률 계산 (퍼센트 수익률)
df['profit_pct'] = (df['sell_price'] - df['buy_price']) / df['buy_price'] * 100

# 4. 동일 일자에 동일 종목 매매 내역이 2건 이상인 그룹만 선택
df_filtered = df.groupby(['trade_date', 'code']).filter(lambda x: len(x) >= 2)

# 5. 그룹 내에서 매매 시간 순으로 정렬 후, 이전 거래의 sell_reason 추가
df_filtered = df_filtered.sort_values(by=['trade_date', 'buy_time'])
df_filtered['prev_sell_reason'] = df_filtered.groupby(['trade_date', 'code'])['sell_reason'].shift(1)

# 6. 손실 거래와 수익 거래로 분리
df_losses = df_filtered[df_filtered['profit_pct'] < 0]

 

2. 손실 내역 시각화

sell_reason_palette = {
    'HIGH_RSI': 'tab:red',
    'TOP_STOCK_OUT': 'tab:blue'
}

# --- 손실 거래 시각화 ---
# (a) 손실 거래 건수를 이전 sell_reason별로 카운트하여 시각화
plt.figure(figsize=(12, 6))
sns.countplot(data=df_losses, x='trade_date', hue='prev_sell_reason', palette=sell_reason_palette)
plt.xticks(rotation=45)
plt.title('Number of lost transactions by date and previous Sell Reason')
plt.xlabel('Date')
plt.ylabel('Number of Transactions')
plt.legend(title='Last Sell Reason')
plt.tight_layout()
plt.show()

# (b) 손실 거래의 평균 손실률(%)을 이전 sell_reason별로 시각화
plt.figure(figsize=(12, 6))
sns.barplot(data=df_losses, x='trade_date', y='profit_pct', hue='prev_sell_reason', palette=sell_reason_palette)
plt.xticks(rotation=45)
plt.title('Average Return on Loss Transactions by Date (%) and Previous Sell Reason')
plt.xlabel('Trading Date')
plt.ylabel('Avg Profit (%)')
plt.legend(title='Last Sell Reason')
plt.tight_layout()
plt.show()

RSI가 높아서 매도한 이후에 재진입을 한 경우, 발생하는 손실이 많다.

해당 이유로 매도 시, 재진입 불가능한 종목에 추가하면 되지만 그 전에 해당 이유로 수익을 본 것은 없는지도 파악이 필요하다.

 

3. 수익 내역 시각화

# --- 수익 거래 시각화 ---
# (a) 수익 거래 건수를 이전 sell_reason별로 카운트하여 시각화
plt.figure(figsize=(12, 6))
sns.countplot(data=df_profits, x='trade_date', hue='prev_sell_reason', palette=sell_reason_palette)
plt.xticks(rotation=45)
plt.title('Number of profit transactions by date and previous Sell Reason')
plt.xlabel('Date')
plt.ylabel('Number of Transactions')
plt.legend(title='Last Sell Reason')
plt.tight_layout()
plt.show()

# (b) 수익 거래의 평균 수익률(%)을 이전 sell_reason별로 시각화
plt.figure(figsize=(12, 6))
sns.barplot(data=df_profits, x='trade_date', y='profit_pct', hue='prev_sell_reason', palette=sell_reason_palette, errorbar=None)
plt.xticks(rotation=45)
plt.title('Average Return on Profit Transactions by Date (%) and Previous Sell Reason')
plt.xlabel('Trading Date')
plt.ylabel('Avg Profit (%)')
plt.legend(title='Last Sell Reason')
plt.tight_layout()
plt.show()

HIGH_RSI 매도 이후 재진입 시 수익이 발생한 건은 24-12-26에 1건 아주 미세하게 있으므로 해당 내역은 무시할만하다.

대장주 교체로 인한 재진입은 실보다 득이 많으므로 냅둔다.

 

4. 매도 로직 업데이트 및 백테스팅

elif same_stock_code and row['rsi'] > sell_rsi:
    prohibited_stock.add(row['code']) #추가
    return True, SellReason.HIGH_RSI

영업일 39일 기준 백테스팅 수치.

  Before After
Total trades 86 77
Winning trades 45 44
Win rate 52.33% 57.14%
Total Profit 84.86% 123.25%

수익률이 38.39%p 증가했다 ㄷㄷ

 

주말 코딩 끝

728x90

'Quant' 카테고리의 다른 글

대체거래소 첫날과 매매 시간 최적화  (0) 2025.03.05
프론트엔드와 삽질  (0) 2025.03.03
Trading bot 업데이트 (4)  (2) 2025.03.01
볼린저 밴드 적용  (0) 2025.03.01
Trading bot 업데이트 (4)  (0) 2025.02.26