안녕하세요. 안녕하세요, 방구석 코딩 퀀트입니다.
대체거래소 종가베팅을 시도하였으나, 실패하여 다른것을 시도하게 되었습니다.
지금 제가 계좌가 3개인데 단타 2개, 스윙1개인데 단타2개는 타점이 같고, 스윙은 길게 가야하는데 자동감시주문이 계좌선택이 1개밖에 되지 않아 매도가 되는 코딩을 도전하게 되었다.
1. 시작은 창대했으나 결과는 '0.00%'
오늘의 야심 찬 목표는 종가 베팅 계좌를 위한 '완벽한 자동 매도 엔진'을 장착하는 것이었다. 이름하여 V4.2 업그레이드. 하지만 실전 가동 직후, 나는 황당한 상황에 직면했다. HTS 화면상으로는 종목이 2.5%가 넘는 수익을 기록하며 빨간 불을 켜고 있는데, 정작 내가 만든 봇은 로그 창에 **'실질 수익률: 0.00%'**라는 숫자를 띄우며 요지부동이었던 것이다.
분명히 수익권인데 봇은 "아직 0%니까 팔 때가 아니야"라고 우기는 상황. 소통의 부재가 부른 이 '거짓말 같은 상황'을 해결하기 위해 나는 코드의 심장부를 다시 열어야 했다.
2. 범인은 '정수 계산'과 '데이터 타입'의 함정
왜 봇은 2.5%를 0%로 읽었을까? 원인은 파이썬의 데이터 처리 방식에 있었다. 기존 코드에서 현재가와 매입가를 계산할 때 int(정수) 타입을 사용하거나, 나누기 연산 시 소수점을 무시하는 계산 방식이 적용되었던 것이다.
예를 들어, 2.5% 수익은 숫자로 치면 0.025다. 그런데 시스템이 이를 정수로만 인식하려고 하면 소수점 아래는 다 잘려나가고 결국 '0'만 남게 된다. 봇 입장에서는 거짓말을 한 게 아니라, 자기가 가진 계산기(Int)로는 0 외에는 답이 안 나왔던 셈이다. 이를 해결하기 위해 모든 가격 데이터와 계산식에 float(실수) 타입을 강제로 지정하고, 데이터를 읽어올 때 문자열 공백을 완벽히 제거하는 수술을 집행했다.
3. 화면 번호(Screen No) 분리: 혼선의 종지부
또 다른 문제는 '데이터 혼선'이었다. 스윙 계좌와 베팅 계좌를 동시에 감시하다 보니, 키움 API 서버에서 보내주는 데이터가 서로 뒤섞이는 현상이 발생했다. 봇이 스윙 계좌를 조회했는데 베팅 계좌 데이터가 날아오기도 했다.
이를 해결하기 위해 화면 번호(Screen No) 분리라는 특단의 조치를 취했다.
- 스윙 계좌 전용 채널: 2000번
- 베팅 계좌 전용 채널: 2001번 주파수를 다르게 맞추듯 채널을 분리하자, 데이터 꼬임 현상이 마법처럼 사라졌다. 이제 봇은 스윙과 베팅을 명확히 구분하여 각각의 전략대로 매매를 수행할 수 있게 되었다.
4. V4.2의 핵심: 단계별 분할 익절과 철저한 손절
기술적 오류를 잡은 후, 드디어 내가 원하는 '사냥꾼의 로직'을 이식했다.
- 분할 매도 시스템: 2%, 3%, 4%, 5% 등 목표 수익률에 도달할 때마다 보유 수량의 20%씩을 시장가(03)로 던진다. 수익을 길게 가져가면서도 챙길 건 확실히 챙기는 전략이다.
- 철벽 손절 라인: 어떤 경우에도 수익률이 -7%에 도달하면 미련 없이 전량 매도한다. 계좌를 지키는 최후의 보루다.
# [8강 핵심 코드] 수익률 정밀 계산 및 계좌 채널 분리 시스템
def request_account_status(self):
""" 스윙과 베팅 계좌의 데이터 혼선을 막기 위한 채널(화면번호) 분리 """
if self.check_swing_turn:
target_acc = self.acc_swing
rq_name = "스윙잔고조회"
scr_no = "2000" # 스윙 전용 주파수
else:
target_acc = self.acc_betting
rq_name = "베팅잔고조회"
scr_no = "2001" # 베팅 전용 주파수 (서로 간섭 방지!)
print(f"🚀 [베팅감시] {target_acc} 조회 시도 (채널 2001)")
self.check_swing_turn = not self.check_swing_turn
# ... (생략) ...
self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", rq_name, "OPW00018", 0, scr_no)
def on_tr_data(self, screen_no, rqname, trcode, record, prev):
""" 봇의 눈을 뜨게 만든 float 기반 정밀 수익률 계산 로직 """
if "잔고조회" in rqname:
cnt = self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
for i in range(cnt):
# ... (가격 데이터 수신 부분) ...
# 💡 [핵심] 정수(int)가 아닌 실수(float)로 계산하여 0.00% 오류 해결!
profit_rate = 0.0
if buy_p > 0:
# 현재가와 매입가를 float로 강제 형변환하여 소수점 수익률 사수
profit_rate = (float(curr_p) - float(buy_p)) / float(buy_p) * 100
if rqname == "베팅잔고조회":
# 이제 0.00%가 아니라 2.49%처럼 정밀한 숫자로 매도 로직 진입!
self.process_betting_sell_logic(code, name, qty, profit_rate)
def process_betting_sell_logic(self, code, name, qty, profit_rate):
""" 단계별 분할 익절 시스템 (V4.2 사냥꾼 모드) """
# 2%, 3%, 4%, 5% 구간마다 20%씩 기계적으로 수익 실현
for s in [2.0, 3.0, 4.0, 5.0]:
if profit_rate >= s and s not in self.betting_stocks[code]["achieved_steps"]:
sell_qty = max(1, math.floor(qty * 0.2))
print(f"💰 [익절발사] {name} {s}% 돌파! {sell_qty}주 시장가 매도 ㅋ")
self.send_order(self.acc_betting, code, sell_qty, 0, "03", f"{s}% 익절")
self.betting_stocks[code]["achieved_steps"].append(s)
break
마치며: AI와 사람의 협업, 그 끝은 익절이다
오늘 봇과 씨름하며 깨달은 것이 있다. 아무리 똑똑한 AI(봇)라도 주인이 정확한 기준(데이터 타입, 채널 분리)을 주지 않으면 무용지물이라는 점이다. 봇이 "수익률 0.00%"라고 거짓말을 할 때, 화를 내기보다 왜 그런 결과가 나왔는지 로직을 추적하는 과정이 진짜 개발자이자 투자자의 자세임을 배웠다.
이제 봇의 눈은 번쩍 뜨였다. 내일 아침, 소수점 단위까지 정밀하게 계산된 수익률을 보며 봇이 시원하게 매도 주문을 쏘아 올릴 순간이 기다려진다. 시스템은 완성되었고, 이제 남은 건 시장이 주는 수익을 즐기는 일뿐이다.
성공여부는 다음시간에.. 우리 제미나이가 거짓말을 하도 많이해서 동작이 잘되는지 안되는지 확인은 필수다
'주식 자동매매 개발기' 카테고리의 다른 글
| [주식 자동매매 9] AI도 못 고치는 '매도 함수'의 늪: 자동매매, 생각보다 어렵다? ㅋ (0) | 2026.02.04 |
|---|---|
| [주식 자동매매 7강] 대체거래소(NXT) 종가베팅의 배신... 그리고 나스닥(QQQM) 입성! ㅋ (0) | 2026.01.23 |
| [주식 자동매매 6] ATS 자동매수 또 실패... 그리고 매도 로직의 배신 (코딩은 만만치 않다 ㅋ) (0) | 2026.01.21 |
| [주식자동 매매 5강] 제목: "어? 왜 매수를 안 해?" 야간 ATS 자동매매 대실패 후기 (범인은 나였다)(부제: 파이썬 봇 만들다가 멘붕 온 썰) (3) | 2026.01.20 |
| [주식 자동매매 4강] 실전은 개싸움(?)이다! 대체거래소 시간 설정 오류와 1주 보호막의 배신 (0) | 2026.01.19 |