검색결과 리스트
분류 전체보기에 해당되는 글 101건
- 2020.12.19 시스템트레이딩 시스템 개발일지(3)-매매상태 흐름도 v.2
- 2020.12.12 [시스템트레이딩] API를 이용한 자동매매(3)
- 2020.12.12 [시스템트레이딩] API를 이용한 자동매매(2) 10
- 2020.12.11 [시스템트레이딩] API를 이용한 자동매매(1) 2
- 2020.12.03 [시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 2
- 2020.11.26 [시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링
- 2020.11.22 [시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 2
- 2020.11.21 [시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 2
- 2020.11.19 [시스템트레이딩] 전략 시뮬레이션(3)
- 2020.11.18 [시스템트레이딩] 전략 시뮬레이션(2)
글
시스템트레이딩 시스템 개발일지(3)-매매상태 흐름도 v.2
기존 매매 흐름 상태도에 추가할 사항이 생겼습니다. 기존에 소개해드린 매매 상태 흐름도입니다. 흐름도와 관련된 자세한 사항은 이전 글을 참고하시기 바랍니다.
그동안 이 흐름도를 기반으로 개발을 진행하였고, 실 매매도 진행을 하였습니다. 하지만 이번에 시뮬레이터를 만들면서 개발한 간단한 전략을 적용하려고 하니 부족한 부분이 생겼습니다.
현재 시뮬레이터의 전략은 아래와 같습니다.
매수 : 특정 x% 이상 오르면 매수
매도 : 매수가 대비하여 특정 y% 이상 오르면 익절매도
혹은 매수가 대비하여 특정 z% 이상 오르면 손절매도
그런데 최초에 특정 x% 이상인 종목은 어떻게 할 것인가? 그리고 익절 후 다시 매매를 하려고 하면 어떻게 할 것인가에 대한 고민이 생겼습니다. 따라서 매매할 대상 중 아직 조건을 만족하지 않는 상태가 필요하게 되었습니다(상태 24번 추가). 이 상태에서는 새로 매매할 조건이 만족하면 상태 11번으로 변경을 하는 부분을 담당하면 될 것 같습니다.
새로운 상태 24번은 아래의 경우에 사용됩니다.
1. trader를 처음 만들 때 코인 현재 가격에 따라 x% 이하면 상태 11번 x% 이상이면 상태 24번으로 설정
2. 익절 후인 상태 16번에서도 상태 24번으로 보내면 됩니다. 익절을 하였다는 말은 x% 이상이라는 의미이기 때문입니다.
3. 중간에 이런 저런 사유로 보유 종목이 없어지는 경우가 있습니다. 그 이유는 여러가지인데요. 프로그램 오류 때문일 수도 있고, 너무 많이 올라서 수동으로 매도했을 수도 있습니다. 혹은 실수로 보유 종목을 hts에서 팔았을 수도 있습니다. 이런 경우에 탈출 감시시 조건을 만족하였을 때 매도 주문을 내게 되는데, 매도 오류가 발생합니다(보유 종목이 없으므로). 이런 경우에도 상태 24번을 보내면 됩니다. 만약 x% 이상인 상태라면 한동안 24번에 머물것이고, x% 이하라면 바로 상태 11번으로 갈 수 있기 때문입니다.
'개발일지' 카테고리의 다른 글
시스템트레이딩 시스템 개발일지(2)-전략코딩 (4) | 2020.06.15 |
---|---|
시스템트레이딩 시스템 개발일지(1)-매매흐름상태도 (0) | 2020.06.11 |
글
[시스템트레이딩] API를 이용한 자동매매(3)
개발하기 좋은 추운 주말입니다. 이럴때는 조용이 집에서 개발에 집중할 시간을 벌 수 있어서 좋은 것 같습니다.
websocket을 이용한 자동 매매 예제에서 매매 로직의 시초가를 입력을 하였었는데요. test 용으로는 문제가 없지만 제대로 전략을 돌리려면 본인이 원하는 시작값을 설정할 수 있어야 합니다.
오늘 소개할 방법은 오늘 시초가를 로직의 시작가로 설정하는 방법입니다. 오늘 시초가를 받아오는 방법은 크게 두가지가 있습니다. 하나는 오늘 일봉을 읽는 것이고, 두 번째는 현재 시세를 받는 것입니다. 현재 시세를 받으면 오늘 시초가가 들어있습니다.
그리고 websocket의 경우에 이유는 정확하지 않지만 연결이 끊어지는 경우가 종종 있습니다. 또한 매매할 coin이 많으면 websocket으로 모든 코인의 실시간 체결 데이터를 받기에 부담이 생깁니다. 따라서 전 종목을 대상으로 매매할 종목을 찾는 경우에는 현실적으로 현재 시초가를 받아서 처리하는 것이 대안이 될 수 있습니다.
그래서 이번에는 현재 시세를 받아서 매매하는 방법도 함께 소개하도록 하겠습니다.
우선 현재 시세를 받아오는 방법입니다. 해당 함수는 get_cur_price_all() 입니다. 입력은 원하는 암호화폐명이 기술된 list입니다. upbit에 등록된 모든 암호화폐 코드를 입력하면 전 종목 시세를 한번에 얻어올 수 있습니다. return되는 형태는 list이며 list의 첫 번째 항목에 dict 형태로 저장되어 있습니다. dict의 key는 암호화폐명입니다. return되는 값은 아주 다양한 정보가 들어있습니다. 그 중 시작가는 'opening_price'입니다.
이렇게 받은 시작가격을 로직의 시작가격으로 설정하면 됩니다.
ticker = 'KRW-SBD'
info = upbit.get_cur_price_all([ticker])
if 'error' not in info[0] :
start_price = info[0][ticker]['opening_price']
tr_logic.set_start_price(start_price)
다음으로는 주기적으로 현재 시세를 받아서 기존 자동매매 프로그램을 작동시키는 방법입니다.
앞에서 설명한 함수인 get_cur_price_all()를 사용하면 됩니다. get_cur_price_all()에서 돌려주는 자료의 형태는 websocket에서 돌려주는 형식과 틀립니다. 하지만 기존 trader를 수정하지 않고 사용하기 위하여 websocket을 사용하던 형태로 변환하여 사용하면 됩니다. 이를 위하여 만든 함수가 make_info_from_upbit_tickers()입니다. 나머지 부분은 수정할 필요없습니다. 이렇게 거래 부분은 시세와 독립적으로 개발을 하면 향후 다른 거래소 혹은 다른 방식으로 거래하는 경우에도 수정할 부분을 최소화할 수 있습니다.
지금 예제는 코인 하나에 대하여 동작하지만, 복수개의 코인에 대하여 동작하도록 수정할 수 있습니다.
방법은 원하는 코인에 대하여 trader를 여러 개 만들어서 list를 만듭니다. 원하는 코인 리스트를 만든 후 get_cur_price_all()에 해당 코인명을 넣으면 됩니다.
USING_WEBSOCKET = 0
# websocket 실간 시세를 이용하여 자동매매하기
if USING_WEBSOCKET :
:
:
else :
# 10초에 한번씩 최근 거래 값을 받아서 자동매매
target_coins = [ticker]
while(1) :
prices = upbit.get_cur_price_all(target_coins) # 원하는 코인을 list로 넣는다.
if 'error' not in prices :
ticker = target_coins[0]
info = make_info_from_upbit_tickers(prices[0][ticker])
trader.do_trading(info)
time.sleep(10)
다음에는 ebest api를 추가하는 방법에 대하여 기술하도록 하겠습니다. 증권사 API는 암호화폐 API와는 많이 틀립니다. 하지만 매매에서 사용하는 api는 몇 개 없으므로 기존 pyupbit의 api와 같은 형태로 부를 수 있도록 개발할 예정입니다. 이렇게 되면 기존 프로그램에서 아래에 있는 거래소 생성하는 부분만 변경하면 됩니다. 거래소와 관련된 부분을 독립하여 개발을 하면 향후 다른 거래소를 추가하는 경우에도 수정할 부분을 최소화할 수 있습니다.
upbit = MyUpbit(access, secret)
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(9) - 과거 데이터(체결, tick데이타) 연속으로 가져오기 (0) | 2021.01.17 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(8) - 과거 데이터(분봉) 연속으로 가져오기 (18) | 2021.01.12 |
[시스템트레이딩] API를 이용한 자동매매(2) (10) | 2020.12.12 |
[시스템트레이딩] API를 이용한 자동매매(1) (2) | 2020.12.11 |
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 (2) | 2020.12.03 |
글
[시스템트레이딩] API를 이용한 자동매매(2)
upbit에서 주는 실시간 데이터를 받는 방법은 아래 글에 기술해 놓았습니다. 여기에 나오는 함수를 사용할 예정입니다.
https://money-expert.tistory.com/32?category=757693
실시간 거래를 위하여 기존 시뮬레이터를 다시 refactoring해봅니다. 프로그램 내용을 최대한 간단하게 만들기 위하여 로그관련된 부분은 과감하게 삭제하고, 매매 로직에서도 클래스 하나로 구현해보았습니다.
최대한 간단한 구조로 만들었으며, 로직도 래리윌리암스가 아닌 아래와 같이 아주 간단한 로직으로 개발하였습니다.
매매로직 :
매수 :
기준가 대비 x% 오르면 매수
매도 :
1) 익절 : 매수가 대비 y% 오르면 매도
2) 손절 : 매수가 대비 z% 내리면 매도
우선 Trader()라는 class를 하나 추가했습니다.
Trader 클래스에서는 실제 매매를 담당합니다.
Trader의 간단한 동작 방식은 아래와 같습니다.
실시간 거래 정보가 들어온다.
if 매수 대기 중이면 :
if 현재 가격이 매수 조건이면 :
매수주문
else 매도 대기 중이면 :
if 현재 가격이 매도 조건이면 :
매도주문
매수 대기중으로 변경
코딩할 내용은 단순하지만 이런 저런 조건문이 많이 붙기 때문에 실제 코드는 약간 복잡합니다. 예를들어 매수할 수량을 정하는 부분에서도 암호화폐의 특성상 소숫점 이하까지 주문을 할 수 있습니다. 그렇다고 소숫점 10자리까지 주문을 할 수 없으니 매수할 수량에 따라서 적절한 수량을 계산해야합니다. 아래와 같은 방식으로요. 이런식으로 매매 흐름과는 관계없이 세세하게 코딩하여야 하는 부분이 나오면 생각외로 시간이 많이 걸리게 됩니다.
# 살 수량 결정
buying_vol = amount / buy_price # 매수 주수
if buying_vol > 10 : #10개 보다 크면 소숫점 0
buying_vol = int(buying_vol)
elif buying_vol > 0 : #0개 보다 크면 소숫점 2
buying_vol = float(format(buying_vol,'.2f'))
else : #0개 적으면 크면 소숫점 4
buying_vol = float(format(buying_vol,'.4f'))
다음은 프로그램을 시작하는 부분입니다.
우선 거래소를 생성합니다. 여기에서는 upbit 거래소를 사용합니다. 이때 본인의 키로 변경하셔야 합니다.
사용할 로직도 생성합니다. 일반적으로 candle 정보를 사용하기 때문에 인자로 넘겨주지만 본 예제에서는 candle은 사용하지 않습니다. 대신에 로직에서 사용할 매수기준, 매도기준, 손절기준을 %를 logic에 설정을 합니다.
다음으로는 로직에서 사용할 시작가격을 설정합니다. 일반적으로는 candle 정보를 읽어서 적절한 값을 설정하거나, 아니면 일정 기간 돌리면서 candle 정보를 쌓아나가야 합니다. 이 부분도 일단은 간단하게 입력하도록 하였습니다.
이렇게 필요한 class를 만들고 변수 값을 설정한 후 Trader를 생성하면 됩니다.
access = 'my acess' # 본인의 access 키값
secret = 'my secret' # 본인의 secret 키값
upbit = MyUpbit(access, secret)
# 상승 따라가기
tr_logic = TR_FOLLOW_TREND('min', 1) # candle정보는 무시
ticker = 'KRW-LBC'
seed = 1000
buy_perc = 0.03 # 시작가 대비 3% 오르면 매수
sell_perc = 0.01 # 매수가 대비 10% 오르면 매도(익절)
losscut = 0.03 # 매수가 대비 losscut 내리면 매도(손절)
tr_logic.init_set(buy_perc, sell_perc, losscut)
start_price = 102 # 시작가, 로직에 따라 시초가일 수도 있고, 이전 30분 candle의 open 가격일 수 있음.
tr_logic.set_start_price(start_price)
trader = Trader(upbit, ticker, tr_logic, seed)
다음에는 websocket를 설정하는 부분입니다. myconnect() 함수에 앞에서 만든 trader와 관련 정보를 설정하면 됩니다. 만약 받은 실시간 정보를 출력하고 싶으면 display_web_socket_recv 값을 1로 하면 됩니다. 체결 정보가 너무 많으면 시스템이 느려질 수 있으므로 정상적으로 동작하는지 확인할 때는 1로 설정하고, 그 후에는 0으로 하는 것을 권합니다. 막상 돌려보니 연결이 끊어지는 경우가 빈번하더군요. 따라서 접속이 끝어지면 잠시 쉬었다가 다시 연결하는 방식으로 코딩을 했습니다.
display_web_socket_recv = 0
while(1) :
try :
asyncio.get_event_loop().run_until_complete(my_connect(trader, ticker, display_web_socket_recv))
except Exception as x:
print('websocket error : ', x)
time.sleep(10)
이제 체결 정보를 실시간으로 받을 수 있습니다. upbit에서 넘겨주는 체결정보에는 당양한 정보가 있는데요. 이걸 제가 필요한 내용만 추려서 dict 형태 정보로 변경을 합니다. 이렇게 하는 이유는 향후 다른 거래소에서 거래를 할 때 거래소 의존적인 부분을 최소화하기 위함입니다. 실거래 데이터가 왔으니 이제는 거래를 한는 trader에게 이 정보를 넘겨줍니다.
data_rev = await websocket.recv()
my_json = data_rev.decode('utf8').replace("'", '"')
data = json.loads(my_json)
if show :
print(data['code'], data['trade_time'], data['ask_bid'], data['trade_price'], data['trade_volume'])
if 'type' in data :
if data['type'] == 'trade' :
info = make_info_from_upbit_real(data)
real.do_trading(info)
거래를 하는 방식은 앞에서 설명한대로입니다. 매수 조건이 되면 매수, 매수되었다면 매도 조건이면 매도. 간단하죠.
그런데 실제로는 좀더 작업을 해야합니다. 예를들어 매수 주문을 했다고 매수가 된 것이 아닙니다. 시장가 주문을 하면 되겠지만 이런 경우에는 수익률이 떨어질 수 있습니다. 만약 지정가 매수를 하게되면 매수가 되었는지 확인하는 코드를 꼭 추가하여야 합니다. 이번 소스에서는 빠졌지만 실제 매매를 하기 위해서는 꼭 추가하여야 합니다.
def do_trading(self, info) :
if self.buying_order_info == {} : # 매수 대기 중
buy_price = self.tr_logic.is_enter_condition(info)
if buy_price > 0 : # 매수조건임
amount = min(self.balance, self.init_seed)
# 살 수량 결정
buying_vol = amount / buy_price # 매수 주수
self.do_new_order('buy', buy_price, buying_vol)
else : # 이미 매수함
sell_price, losscut = self.tr_logic.is_exit_condition(info) #
if sell_price > 0 : # 매도 조건
# 매도 주문
# 매도 가격 : tr_logic에서 결정한 값,
# 매도 수량 : 매수 수량
self.do_new_order('sell', sell_price, self.buying_order_info['org_qty'])
로직은 앞에서 설명한 것과 같이 일정 % 오르면 매수하고, 매수한 후 일정 % 이상 오르면 매도, 일정 %이하 내리면 손절입니다. 필요한 초기값들 설정하는 부분도 추가를 해야합니다. 주 부분은 매수조건, 매도저건입니다. 예로 만든 로직은 아주 간단합니다. 매수는 현재가가 기준가 대비하여 올랐으면 현재가로 매수 가격으로 돌려줍니다. 매도는 익절/손절 조건을 만족하면 현재가를 매도 가격으로 돌려줍니다.
class TR_FOLLOW_TREND()
def is_enter_condition(self, info) :
# buy_price는 시작가 * 상승률
buy_price = self.open * (1+self.buy_percent)
if info['close'] >= buy_price :
self.bought_price = info['close'] # 매수 가격 저장
return info['close']
return 0
# 조건 조사
# cut : 익절(1) 혹은 손절 (2)
def is_exit_condition(self, info) :
sell_price = self.bought_price * (1+self.sell_percent) # 매수 가격 대비 올랐으면
if info['close'] >= sell_price :
self.bought_price = 0
return info['close'], 1 # 1: 익절
sell_price = self.bought_price * (1-self.losscut) # 매수 가격 대비 내렸으면
if info['close'] <= sell_price :
self.bought_price = 0
return info['close'], 2 # 2: 손절
return 0, 0
upbit websocket으로 실시간 체결 데이터를 받아서 정의한 로직에 맞게 매매를 하는 프로그램이 완성되었습니다.
물론 매수/매도 주문 후 실제 체결이 되었는지 확인하는 부분을 추가하여야 완벽한 실전 매매가 됩니다.
이 부분은 좀 복잡하기 때문에 일전에 소개해드린 전략매매 흐름도를 바탕으로 trader class를 변경하면 향후 기능 변경시에 적응하기 좋습니다.
다음에는 업비트에서 전종목 시세를 받아서 (open 가격 자동 설정) 돌아가는 방식을 설명하도록 하겠습니다.
현재 개인적으로 upbit에서 실시간 시세를 받아서 래리윌리엄스 로직을 돌리고 있는데요. 생각만큼 수익이 나오지는 않습니다. 시뮬레이션에서는 수익이 좋았는데, 실제 적용해보니 생각만큼 수익이 안나는군요. 세상에 쉬운 일이 없군요.
관련 코드는 아래 깃허브에 있습니다. pyupbit 수정한 버전도 함께 올려 놓았습니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(8) - 과거 데이터(분봉) 연속으로 가져오기 (18) | 2021.01.12 |
---|---|
[시스템트레이딩] API를 이용한 자동매매(3) (0) | 2020.12.12 |
[시스템트레이딩] API를 이용한 자동매매(1) (2) | 2020.12.11 |
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 (2) | 2020.12.03 |
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링 (0) | 2020.11.26 |
글
[시스템트레이딩] API를 이용한 자동매매(1)
그동안 전략 시뮬레이션을 개발하는 과정을 기술하였는데요. 이제는 실제 매매를 해 보겠습니다.
이글에서는 upbit에서 자동매매를 개발하는 과정을 기술합니다.
우선 실제 매매를 위해서는 거래소에서 API key를 받아야 합니다. upbit 거래소에 로그인 한 후 고객센터/Open API 안내/OPen API 사용하기를 누른 후 access키와 secret 키를 받아야 합니다.
이렇게 받은 API 키를 upbit.txt 파일에 아래와 같은 형태로 저장하면 됩니다.
acess key
secret key
옆에 있는 업비트 개발자 센터를 누르면 API관련 문서가 나옵니다. API에 대하여 궁금한 사항이 있을 때 확인하시면 됩니다.
github에서 찾아보면 upbit api를 사용하기 쉽게 class로 만든 소스가 많이 있습니다. 이중 많이 사용하는 pyupbit을 fork하여 사용하도록 하겠습니다.
pyupbit에는 대부분의 API가 구현되어 있지만 몇가지 함수도 추가하고, return 값을 변경하기도 하였습니다.
이렇게 변경한 소스는 아래 위치에서 다운받을 수 있습니다.
github.com/multizone-quant/pyupbit
다만 위 링크에 접속한 후 아래 그림과 같이 브렌치인 my_upbit-0.2를 선택한 후 오른쪽에 있는 code download를 누릅니다. 소스 zip 파일이 다운되면 개발할 곳에 압축해제하시면 됩니다.
pyupbit을 바로 사용할 수 있겠지만 앞으로 다양한 거래소 자동매매를 개발할 예정이기 때문에 거래소에 독립적인 나만의 API가 필요합니다. 그래서 여러 거래소의 특성을 무시하고, 매매에 필요한 정보를 얻을 수 있는 wrapper class를 별도로 개발합니다.
pyupbit class를 기반으로 class MyUpbit를 만들었습니다. 따라서 pyupbit을 직접 사용하지 않기 때문에 pyupbit에 있는 readme.txt는 무시하셔도 됩니다.
앞으로 개발할 upbit 거래소 자동 매매 프로그램은 아래 링크에서 다운받으시면 됩니다.
github.com/multizone-quant/system-trading-crypto
거래소 관련 class를 먼저 올렸습니다.
jpy_basic_ex.py : 거래소 base class
jpy_dummy_ex.py : simulation을 위한 dummy class
jpy_upbit.py : pyupbit wrapper class
jpy_upbit.py가 앞으로 사용할 upbit 거래소에서 자동매매를 위한 class입니다. jpy_upbit.py 단독으로 실행을 하면 제공하는 API test를 하는 함수들이 동작을 합니다.
jpy_upbit에서 제공하는 함수들의 return 값은 list의 array로 구성되어 있습니다. 만약 오류가 생기면 [0]번째 항목에 'error'라는 key word가 있습니다. 오류 message는 ret[0]['error']['message']에 포함되어 있습니다.
실제 매매하는 함수는 아래 5가지입니다. 이중 시장가 주문의 경우에는 buy의 경우에는 매수할 금액을 입력하고, sell의 경우에는 매도할 수량을 입력하면 됩니다.
buy_limit_order(ticker, price, qty)
buy_market_order(ticker, amount)
sell_limit_order(ticker, price, qty)
sell_market_order(ticker, qty)
cancel_order(uuid)
아직 체결되지 않은 주문은 아래 함수를 사용합니다. 특정 코인에 대한 미체결 주문을 알 수 있을 뿐 아니라, 'ALL'을 입력하면 현재 체결되지 않은 모든 코인 정보를 얻어올 수 있습니다.
pending_orders(ticker) # or 'ALL'
잔고를 확인하는 방법입니다. 'KRW'를 인자로 넣으면 현재 원화 잔고가 특정 암호화폐 (예, 'BTC')를 넣으면 해당 암호화폐가, 'ALL'을 인자로 넣으면 모든 잔고를 얻을 수 있습니다.
return값은 list의 array로 [0]번째 값에 하나 혹은 복수 코인의 잔고가 들어있습니다. 또한 오류인 경우에는 'error' keyword가 포함되어 있습니다. 사용예는 jpy_upbit.py에 밑 부분에 있습니다.
잔고는 ['total']과 ['orderable'] 항목이 있는데, 'orderable'은 잔고 중 신규 주문이 가능한 수량입니다.
다음에는 이전에 기술한 전략 시뮬레이터와 거래소 API를 합쳐서 upbit에서 특정 전략으로 자동매매를 하는 방법을 기술하도록 하겠습니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] API를 이용한 자동매매(3) (0) | 2020.12.12 |
---|---|
[시스템트레이딩] API를 이용한 자동매매(2) (10) | 2020.12.12 |
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 (2) | 2020.12.03 |
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링 (0) | 2020.11.26 |
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 (2) | 2020.11.22 |
글
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기
전략 시뮬레이션(1) 편에서 upbit 일봉 데이터를 가져오는 부분에 대하여 설명을 하였습니다. 일봉은 최대 400개만 가져올 수 있기 때문에 아주 간단하게 코딩을 했었는데요.
이번에는 분봉 및 tick 데이터를 가져오는 방법에 대하여 설명하도록 하겠습니다.
분봉과 틱은 아래 형식을 사용하면 됩니다.
- 분봉 : 1, 3, 5, 10, 30, 60분 봉을 가져올 수 있습니다. 아래 형식에서 minutes 뒤에 원하는 분 정보를 넣으면 됩니다.
crix-api-endpoint.upbit.com/v1/crix/candles/minutes/1?code=CRIX.UPBIT.KRW-BTC&count=10
- 틱봉 : 틱봉을 가져오는 url입니다. 틱봉은 접속 주소도 틀리고 인자들도 조금 틀립니다. 특히 코드명 방식도 틀리니 참고하시기 바랍니다.
api.upbit.com/v1/trades/ticks?market=KRW-JST&count=500
우선 분봉은 숫자 상 제한이 없는 것 같기도 합니다. 1분 봉을 받아보니 한참을 받더군요.
문제는 분봉도 한번에 받을 수 있는 수량은 500개로 제한된다는 것입니다. 계속 이어 받기 위해서는 to 필드에 적절한 값을 넣어서 데이터를 받는 함수를 계속 호출하여야 합니다.
형식은 아래와 같습니다.
https://crix-api-endpoint.upbit.com/v1/crix/candles/minutes/1?code=CRIX.UPBIT.KRW-BTC&count=10&to=2019-09-01 00:00:00
to 값은 앞에서 받은 데이터 중 가장 마지막 데이터의 'candleDateTime' 값을 사용하면 됩니다.
그리고 받는 중간에 연결이 끊어질 수 있으므로 500개씩 받은 데이터를 파일에 계속 써 나가는게 좋을 것 같습니다. 방식은 코인이름과 일자 그리고 일련번호를 쭉 붙여나가는 방식이면 될 것 같습니다. 우선 간단하게 관련 코드만 소개해드리고, tick 데이터를 모두 받는 코드가 정리되면 함께 github에 올리겠습니다.
# ago : tick인 경우에만 사용
# 오늘 기준으로 며칠 전 : 예 오늘:0, 어제:1
def get_data_continue(candle, coin, ty='day', interval=1, count=10, frm=None, to=None, ago=0) :
end = False
cnt = 1
while(end == False) :
t = int(time.time())
if candle :
ret = get_candle_history(coin, ty, interval, count, to)
if ret != None :
if len(ret) < 2 : # no more data
return
# 마지막에서 두 번째 시간을 to로 사용함.
# to를 포함하지 않는 시간까지 자료가 넘어옴.
# 이전에 검색한 to까지 자료가 부족할 수 있음
# 차후에 merge할 때 to 시간이 겹칠 수 있으므로 빼는 부분 추가하여야 함
info = ret[-2]
# candle과 tick 정보가 다름.
# dt, to용 값을 구함
if candle :
dt = info['candleDateTime'].split('+')
to = dt[0].replace('T', ' ')
day = to.split(' ')[0]
if frm != None : # from보다 이전 데이터인지 확인
info = ret[-1] # 마지막 데이터
if candle :
if info['candleDateTime'] < frm :
ret = remove_data(ret, frm, 'candleDateTime')
end = True
# cnt 번호를 추가하여 파일이름 생성
fname = coin+'_' + ty + '_' + str(interval) + '_' + format(cnt, '03d') + '_' + day + '.csv'
cnt += 1
save_to_file_csv(fname, ret)
print ('save ', fname)
if ty == 'day' : # day는 400개만 받을 수 있다.ㅣ
end = True
else : # 분 봉은 계속 받을 수 있다.
time.sleep(1)
else :
end = True
프로그램이 실행되고 나면 아래와 같은 형식으로 파일이 생성됩니다.
이후에는 파일 하나로 합치면 됩니다. 이 부분도 현재 작업 중이므로, 함께 소개하도록 하겠습니다.
시뮬레이션은 데이터를 만드는 과정을 잘 정의해놓아야 필요할 때 원하는 구간의 데이터를 쉽게 빼올 수 있습니다. 처음에는 간단하게 시작했는데, 이 부분에서 작업할 양이 많은 것 같습니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] API를 이용한 자동매매(2) (10) | 2020.12.12 |
---|---|
[시스템트레이딩] API를 이용한 자동매매(1) (2) | 2020.12.11 |
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링 (0) | 2020.11.26 |
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 (2) | 2020.11.22 |
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 (2) | 2020.11.21 |
글
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링
기존 코드를 한번 더 리펙토링을 해야할 것 같습니다.
이번에 수정하고자 하는 부분은 아래와 같습니다.
1. 지표를 추가할 때 변경하는 부분 최소화
2. 기존 전략을 수정하거나 새로운 전략을 추가할 때 변경할 부분 최소화
우선 지표를 추가하는 부분을 다시 보겠습니다.
일단 새로운 지표인 경우에는 지표를 코딩하는 부분은 어쩔 수가 없이 추가하여야 합니다. 다만 새로운 지표가 추가되었을 때 simulation 과정에서 중간 값을 저장하는 부분에 새로운 지표를 추가하여야 하는데요. 이런 경우에 관계없는 부분도 변경하여야 하기 때문에 좋은 구조라고 볼 수 없습니다.
우선 기존 코드입니다. save_mid_values 함수 하나로 모든 변수를 저장하였습니다. 이러다 보니 관계없는 부분에서도 수정을 해야합니다.
def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd, trend, ma7) :
이 문제를 해결하는 방법은 함수를 쪼개는 것입니다.
관련 변수를 모아서 몇 개의 함수로 나눠보겠습니다. 우선 save_mid_values()에 default 값을 추가하여 초기화하는 부분에서는 save_mid_values()만 호출하면 됩니다.
만약 새로운 지표가 추가된다면 save_mid_values()에 인자를 추가하고, save_mid_values_jipyo()에 관련 변수를 추가하면 됩니다.
# 지표가 추가되는 경우에는 마지막에 변수 추가
def save_mid_values(candle_data, buy_price=0, sell_price=0, profit=0, total_profit=0, dd=0, trend=0, ma7=0) :
save_mid_values_buy_sell(candle_data, buy_price, sell_price)
save_mid_values_result(candle_data, profit, total_profit, dd)
save_mid_values_jipyo(candle_data, trend, ma7)
def save_mid_values_buy_sell(candle_data, buy_price, sell_price) :
candle_data['buy_price'] = buy_price # buy 여부 저장
candle_data['sell_price'] = sell_price # buy 여부 저장
def save_mid_values_result(candle_data, profit, total_profit, dd) :
candle_data['profit'] = profit # profit 저장
candle_data['total_profit'] = total_profit # total_profit 저장
candle_data['dd'] = dd # downd draw 저장
def save_mid_values_jipyo(candle_data, trend, ma7) :
candle_data['trend'] = trend # 추세값 저장
candle_data['ma7'] = ma7 # downd draw 저장
이 함수를 사용하는 부분도 적절하게 변경하였습니다.
다음은 전략 부분을 refactoring해보겠습니다.
이전 글에서 횡보구간에서는 매수를 하지 않는 전략을 추가하였는데요. 아래와 같이 적용할 전략이 아닌 부분은 # 처리했었습니다. 이렇게 되면 코드 관리가 어려워집니다. 따라서 어떤 방식건 # 처리를 하지 않고 각 전략에 쉽게 적용할 수 있어야 합니다. 이 문제를 풀기위해 전략 코딩 부분을 refactoring 해보겠습니다.
def is_enter_condition(self, candle) :
enter = 0
# 아래 세 조건 중에 원하는 조건 선택
# if self.range > 0 :
# if self.range > 0 and self.trend > 0.2 # trend 동시 적용
if self.range > 0 and self.trend > 0.2 and (self.MA != 0 and self.MA < candle.open) : # trend & MA 동시 적용
전략코딩 부분을 찬찬히 살펴보면 이런 식으로 구성되어 있습니다.
candle이 변경되었을 때 관련 변수 수정
if 매수 중이면
전략에 따른 매도할 가격을 받는다.
현재 candle 기준으로 매도 조건이면
매도주문
else # 매수대기
전략에 따른 매수할 가격을 받는다.
현재 candle 기준으로 매수 조건이면
매수주문
따라서 이런 구조로 class를 정의하면 새로운 전략을 추가하는 경우에 필요한 부분만 변경하면 됩니다. class를 사용하면 이런 구조를 아주 효과적으로 개발할 수 있습니다.
기존적인 전략에 대한 코딩은 base class에서 하고, 새로운 전략을 적용하는 경우에는 매수/매도 가격 결정, 매수/매도 조건 결정하는 함수만 개발하면 됩니다.
class의 가장 큰 장점이 상속기능입니다. 개념이 조금 복잡하지만 쉽게 설명하면 공통으로 사용하는 함수는 부모 클래스가 가지고 있고 차이가 나는 부분은 자식클래스에서 개발하자는 취지입니다.
앞에서 소개한 TR_LW를 base class로 옮기고, 이 중 전략별로 다르게 개발하여햐하는 함수만 자식 class에서 개발하면 됩니다.
우선 TR_Basic()은 기존 TR_LW와 같다고 보시면 됩니다. 특이한 부분은 자식 클래스입니다. 우선 TR_LW_SIM 선언시에 누가 부모인지를 인자로 넣습니다. 그 후 필요한 경우에는 초기화를 하는데요. 이때 부모 클래스의 초기화를 진행해야하기 때문에 super()라는 키워드를 사용하여 부모 클래스에 있는 함수를 호출합니다. 그 후 자기 class에서 필요한 변수를 만들고 초기화합니다. LW 전략이므로 range와 k 값을 초기화합니다.
class TR_Basic() : # 전략 부모 클래스
def __init__(self, interval, num) :
# 공통 초기화 부분
class TR_LW_SIM(TR_Basic) : # LW 시뮬레이션용 클래스
def __init__(self, interval, num) :
super().__init__(interval, num)
self.range = 0
self.k = 0.5
전략에서 필요한 함수는 크게 5가지로 구분할 수 있습니다.
def update_new_candle(self, cur) : # candle이 변경되었을 때 관련 변수 변경
def get_buy_price(self, candle) : # 매수할 가격 구하기
def is_meet_buy_cond(self, candle, buy_price) : # 매수 조건 만족?
def get_sell_price(self, candle) : # 매도할 가격 구하기
def is_meet_sell_cond(self, candle, sell_price) : # 매도 조건 만족?
이렇게 만든 상태에서 비추세 구간에는 매수를 하지 않는 전략으로 수정을 한다고 가정을 합시다.
기존 TR_LW_SIM과 비교하여 차이가 나는 부분은 매수 가격을 정하는 부분입니다. 따라서 TR_LW_SIM을 부모로 갖는 TR_LW_TREND라는 classs를 만들고 get_buy_price함수에 관련 코드를 작성하면 됩니다.
class TR_LW_TREND(TR_LW_SIM) :
# 매수 조건을 만족하면 매수가를 돌려준다.
def get_buy_price(self, candle) :
if self.range > 0 and self.trend > 0.2 : # trend 동시 적용
buy_price = (candle.open + self.range * self.k)
return buy_price
return 0
이렇게 만든 전략을 사용하는 경우에는 아래와 같이 class 명을 명시하시면 됩니다. 비추세와 이평을 이용하는 전략도 함께 수정해놓았으니 참고하시기 바랍니다.
tr_logic = TR_LW_TREND('min', 1)
시뮬레이션은 비교적 간단한 개발이기 때문에 이 정도로 동작을 하지만 실제 매매와 연동을 하게되면 구조가 좀 더 복잡해집니다. 이것도 시간이 되면 정리해서 올리도록 하겠습니다.
관련 src코드 파일명과 github 위치는 아래와 같습니다.
Larry_williams4.py
TR_LW_1.py
my_candle.py
my_util.py
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] API를 이용한 자동매매(1) (2) | 2020.12.11 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 (2) | 2020.12.03 |
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 (2) | 2020.11.22 |
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 (2) | 2020.11.21 |
[시스템트레이딩] 전략 시뮬레이션(3) (0) | 2020.11.19 |
글
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기
LW 전략 적용해보고, 그 결과를 차트로 그려서 매매 상황을 확인해 볼 수 있는 부분까지 마무리를 하였습니다. 이번에는 기본 LW 전략에 몇몇 아이디어를 추가해보도록 하겠습니다.
일단 비추세 구간에서는 LW 매수 시그널이 나오더라도 이익도 적고, 손실이 발생할 확률이 높습니다. 따라서 비추세 기간에는 매매를 하지 않도록 수정해보겠습니다.
비추세 구간을 확인하는 방법은 여러가지가 있겠지만, 이번에는 아래 글에 나오는 추세추종 필터를 적용해보겠습니다.
여기에서 사용하는 추세추종 필터는 아래와 같습니다.
A : 7일간 코인 가격 차이의 절대값 = abs(1일전 코인종가 - 7일전 코인종가)
B : 7일간 당일 코인 움직임 절대값의 총합 : abs(7일전 시/종가 차이)+ ...... + abs(1일전 시/종가 차이)
추세추정필터는 A/B 값으로 계산합니다. 값이 크면 추세가 강하다는 의미입니다.
결국 매일의 시/종 가격 변화의 합과 시작/킅 날의 가격 차이의 비율로 추세를 판단하는 방법입니다.
이전에 만들었던 프로그램에서 추가할 사항들은 아래와 같습니다.
1. simultaion 중간에 저장할 값으로 추세추종 필터 값 추가 (debuging용)
2. tr_lw에서 추세추종 필터 계산하는 함수
3. candle update하는 update_new_range() 함수에서 추세주총필터 계산하는 함수 호출하기
4. tr_lw에서 is_enter_condition() 수정
비교적 변경하는 부분이 적다고 볼 수 있습니다.
그럼 하나씩 추가해보겠습니다.
1. 중간값을 저장하는 함수 수정
def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd, trend) :
candle_data['buy_price'] = buy_price # buy 여부 저장
candle_data['sell_price'] = sell_price # buy 여부 저장
candle_data['profit'] = profit # profit 저장
candle_data['total_profit'] = total_profit # total_profit 저장
candle_data['dd'] = dd # downd draw 저장
candle_data['trend'] = trend # 추세값 저장
중간값을 저장하는 함수를 부르는 곳에도 trend 값을 전달할 수 있도록 수정합니다.
# simulation 중 파일에 저장할 변수들 추가
trend = tr_logic.get_trend()
save_mid_values(candle_data[i], 0, 0, 0, 0, 0, trend)
2. TR-LW.py에 추세를 계산하는 함수를 추가합니다.
def __init__(self, interval, num) :
:
self.trend = 1 # 변수 추가
def get_trend(self) :
cur = self.history[-1] # last one
day_sum = 0
candle = None
hist_len = len(self.history)
if hist_len >= 8 :
range_total = 0
pos = len(self.history)-1
for i in range(7) :
candle = self.history[pos]
day_sum += abs(candle.open - candle.close) # candle 시/종가 차이의 절대치 합
pos -= 1
period_range = abs(candle.close - cur.close)
self.trend = period_range / day_sum
self.trend = float(format(self.trend, '.2f')) # 소숫점 2자리 값으로 변경
return self.trend
history에는 candle 정보가 오름차순으로 저장되어 있으므로, 최신 7 candle의 값을 구하기 위해서는 뒤에서 부터 계산을 해야합니다. 그리고 float 값은 소숫점 이하 긴 숫자로 저장되므로, 소숫점 이하 2자리만 가지고 있기 위해서는 .2f 포맷으로 변경한 후(문자열) 이것을 다시 float로 변경하면 소숫점 2자리 실수를 가지고 있을 수 있습니다.
3. 다음은 get_trend() 함수를 부르는 곳을 추가해야 합니다.
def update_new_range(self, cur) : #
self.pre = cur
self.history.append(cur)
self.range = cur.high - cur.low # 전 candle의 range
self.get_trend()
새로운 candle이 들어오면 range값을 계산하고 history에 추가하는 함수입니다. 여기에서 get_trend()를 부르면 될 것 같습니다.
4. 마지막으로 매수 조건에 trend 값이 특정 값 이상이면 매수하도록 수정합니다. 일단 0.4로 해보겠습니다.
def is_enter_condition(self, candle) :
enter = 0
if self.range > 0 and self.trend > 0.4 : # 매수 조건
buy_price = (candle.open + self.range * self.k)
if candle.high > buy_price : # 최고가가 buy_price보다 높으면 매수되었다고 가정
enter = 1
self.bought_price = buy_price
추세추종 필터를 추가하기 위하여 필요한 코딩이 마무리되었습니다. 새로운 기능이 들어오면 변경할 곳이 많아집니다. 이 중 한 곳이라도 수정하지 않으면 정확하게 동작하지 않게 됩니다. 따라서 본인이 작성한 프로그램 구조를 정확하게 파악을 해야합니다.
이렇게 반영한 결과를 확인해보겠습니다.
profit : ticker : KRW-BTC
total # trading : 215
total profit : 1,926,201.75
trading fee : 151,161.74
total Net Profit : 1,775,040.00
# winning : 121
# losing : 94
MDD : 51.78
max loss : -17,060.96
max gain : 1,926,201.75
아쉽게도 추세추종 필터를 추가하기 전 보다 수익이 더 떨어졌군요. 그 원인을 확인해 보도록 하겠습니다. 두 그래프를 비교해보니 아래 그림과 같이 급락하였다가 다시 원래로 돌아오는 경우에 추세추종 필터가 추가된 경우에는 매수에 참여를 안하고 있습니다. 추세추종 값이 0.18, 0.33이군요.
추세추종 정도를 변경하면서 돌려본 결과입니다. 수익률 면에서는 추세추종 필터를 사용하지 않은 경우가 제일 좋습니다. 다만 추세추종 필터를 추가하면 수익률은 다소 떨어지지만 MDD 값은 개선이 됨을 알 수 있습니다. 각자 장단점이 있을 것 같습니다.
결국 어떤 전략에서 특정 필터를 사용할 때 적용 여부를 판단하는 파라미터 값을 찾는 것이 중요합니다. 트레이딩하는 코인의 종류에 따라 최적의 값은 달라질 것입니다. 따라서 실전에서는 많은 시도를 해보면서 계속 변경해야할 것 같습니다.
매매를 한 시점을 분석해보니 하락 시점에 매수를 하면 손실이 발생하는 경우가 많군요.
하락 기간에는 매수를 하지 않으면 수익이 좋아질지 궁금합니다.
하락 추세를 판단하는 방법도 여러가지가 있겠지만 일단 이동평균선을 사용해보겠습니다. 주식에서는 MA(5일)을 사용하지만 코인은 주말에도 거래가 되므로 MA(7일)을 사용하겠습니다.
하락추세추종 필터와 하는 역할이 같으니 변경되는 부분도 비슷합니다.
1. save_mid_values()에 ma값 추가하고 save_mid_values()를 부르는 함수에 ma값 추가
2. TR-LW.py에 MA 계산하는 함수 추가, 원하는 일자로 변경할 수 있도록 일자를 함수 인자로 받도록 하겠습니다.
def __init__(self, interval, num) :
:
self.MA = 0 # 변수 추가
def get_MA(self, num_days) :
hist_len = len(self.history)
if hist_len >= num_days :
day_sum = 0
pos = len(self.history)-1
for i in range(num_days) :
candle = self.history[pos]
pos -= 1
day_sum += candle.close # 종가 합
self.MA = day_sum / num_days
self.MA = float(format(self.MA, '.2f')) # 소숫점 2자리 값으로 변경
return self.MA
3. get_MA()를 부르는 곳 변경. get_trend()와 같이 update_new_range() 함수에 추가하면 됩니다.
4. is_enter_condition() 변경
MA 값이 candle의 open 가격보다 낮으면 상승 추세로 가정합니다.
def is_enter_condition(self, candle) :
enter = 0
if self.range > 0 and self.trend > 0.4 and (self.MA != 0 and self.MA < candle.open) :
buy_price = (candle.open + self.range * self.k)
이렇게 변경한 내용을 반영해보았습니다. 추세추종 값은 0.2, MA(7일) 적용 결과입니다.
위에서 언급한 구간에서 매수 후 손실은 사라졌습니다만 수익률은 더 떨어지는군요. 아마 수익이 날 곳에서 매수를 못하고 있을 것 같습니다.
그래프를 다시 비교해보니 아래와 같이 하락 후 상승하는 시기에 나오는 양봉에 매수를 못하고 있습니다. 당연한 결과인 것 같습니다. 하락 추세 중 발생하는 양봉은 아직 하락 추세 중이므로, 당연히 매수가 안됩니다. 하지만 이런 경우에 장대 양봉이 나오는 경우가 종종있습니다.
하락시에 손실이 발생하는 매수를 없애기 위하여 적용한 상승추세 시 매수 전략의 단점이 있군요. A 문제를 풀려고 하니 B 문제가 생기는 셈인데요. 어떻게 할지는 좀 더 고민해봐야 할 것 같습니다.
결론적으로 다양한 필터의 특성을 정확하게 파악해서 특정 움직임에 맞는 필터를 자유자재로 적용할 수 있는 내공이 필요해보입니다. 필터에 필수적으로 필요한 파마미터 값을 결정하는 것도 큰 숙제입니다.
파이썬을 이용하면 최적의 필터와 파라미터의 조합을 비교적 빠르게 찾을 수 있습니다. 파이썬을 이용하여 시뮬레이션을 하는 이유도 이것 때문이고요.
지금까지 간단하게 전략 시뮬레이터를 만들어 보았습니다. 최대한 간단하게 코딩을 했으니, 코드를 읽는데 큰 문제는 없을 것이라고 생각합니다. 수정한 코드는 아래위치에 있습니다. Larry_williams3.py에 지금까지의 수정 사항이 반영되어 있습니다.
github.com/multizone-quant/System_trading_ex
여기에서 아래 프로그램을 다운받으시면 됩니다.
Larry_williams3.py
TR_LW.py
my_candle.py
my_util.py
sim_data/*
이외에도 lw 매수 조건 계산할 때 k 값을 평균 값으로 변경하기 등 추가할 사항이 더 있습니다. 이 부분도 차후에 반영하겠습니다.
오늘 지표를 추가하면서 수정할 부분을 찬찬히 살펴보면 tr_lw.py에 기능 추가하는 부분 이외에 Larry_williams3.py에 추가하는 지표를 저장하기 위하여 코드를 수정하는 부분이 있습니다. 이렇게 여러 파이썬 파일을 수정하는 경우에는 실수할 확률이 높아지므로, 이 부분도 모으면 좋습니다.
다음에는 업비트에서 실시간 시세를 받으면서 실제로 투자하는 실전용 코드를 소개하도록 하겠습니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(7) - 과거 데이터(분봉) 가져오기 (2) | 2020.12.03 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링 (0) | 2020.11.26 |
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 (2) | 2020.11.21 |
[시스템트레이딩] 전략 시뮬레이션(3) (0) | 2020.11.19 |
[시스템트레이딩] 전략 시뮬레이션(2) (0) | 2020.11.18 |
글
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기
LW 전략을 돌려본 후 mdd, 수익률 등등을 보면서 성과가 얼마나 나왔는지 확인이 가능합니다. 그런데 전략을 개선하기 위해서는 언제 수익이 났고 언제 손실이 발생하였는지를 chart 그래프로 확인해볼 수 있으면 인사이트를 얻기가 더 좋을 것 같습니다.
일전에 candle chart에 text를 출력하는 방법에 대하여 글을 올렸었는데요. 무난하게 동작하였지만 시간만 가지고 있는 경우에 출력이 제대로 되지 않는 문제를 발견하였습니다. 또한 차트를 그리는데 시간이 너무 많이 걸려서 다른 방법을 찾아보기로 했습니다.
구글링을 열심히 한 결과 아래 글을 발견했습니다.
info.cloudquant.com/2019/08/candlestick_plotly/
이 글을 바탕으로 plotly를 이용하여 chart를 그리고 필요한 위치에 text를 그리는 코드를 만들어보았습니다.
plotly를 사용하기 위해서는 pandas를 사용하여야 합니다. 그래서 차트를 그리는 함수에서 date/open/high/low/close 데이터를 저장한 후 pandas로 읽는 부분을 추가하였습니다.
def draw_chart_plotly(candle_data, ticker, stat) :
fname = 'chart.csv'
save_ohlc(fname, candle_data)
mcd = pd.read_csv(fname)
LW 전략을 수행하면 매매가 발생한 경우에 'profit' 부분에 수익금을 기록합니다. 이 정보를 바탕으로 +이면 'P(rofit)'를 -이면 'L(oss)'를 high 값 위치에 표시하도록 해보겠습니다.
매매가 발생한 위치에 annotation을 추가한 후 이후에 그를 candle에 추가하면 됩니다.
그럼 매매가 발생한 candle에 annotation을 추가해보겠습니다. candle_data를 뒤에서 앞으로 읽으면서 profit 값이 0이 아니면 매매가 있었다는 의미입니다. 이때 값이 0보다 크면 이익(P), 아니면 손실(L)을 출력하는 annotation을 생성하여 추가합니다. 글을 쓰는 위치는 마음대로 정할 수 있습니다. 여기에서는 x값은 거래가 발생한 시간, y값은 high 값으로 정하겠습니다. default 값은 (시간,high) 위치입니다. 여기에 출력을 해보면 chadle과 겹치게 됩니다. 그래서 x, y 축 방향으로 약간 shift를 합니다. 이 값은 본인이 원하는 값을 입력하시면 됩니다. 일단은 xshift로 1, yshift로 10을 넣었습니다. 이렇게 되면 chadle 기준으로 x 방향으로는 center, y 방향으로는 high candle보다 약간 위에 text가 출력됩니다.
annotations = []
for i in range(len(candle_data)-1, -1, -1) :# 뒤에서 앞으로
text = 'L'
if candle_data[i]['profit'] > 0 : # profit 이면 P lost이면 L 출력
text = 'P'
if candle_data[i]['profit'] != 0 :
annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'],
y=candle_data[i]['highPrice'],
xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동
yshift=10, # y 축 기준으로 오른쪽으로 y칸 이동
showarrow=False,
text=text))
다음은 화면에 보여줄 layout를 정하는 부분입니다. 보여줄 데이터의 양에 따라 width를 조정합니다. test를 해보니 보여줄 데이터 수의 10배 정도가 적당하더군요. 화면에 보여 줄 title에 적절한 내용을 만듭니다. layout를 만들 때 앞에서 만든 annotations를 추가합니다.
# draw할 layout 생성
width = len(candle_data) * 10
layout = dict(
title=ticker+':: # tradings : ' + str(stat.num_trading) + ' profit : ' + format(stat.get_profit_percent(), '.2f') + '% mdd : ' + format(stat.mdd, '.2f'),
xaxis=go.layout.XAxis(title=go.layout.xaxis.Title( text="Time"), rangeslider=dict (visible = False)),
yaxis=go.layout.YAxis(title=go.layout.yaxis.Title( text="Price")),
width=width,
height=800,
annotations=annotations
)
candle 차트는 만드는 방법은 아주 간단합니다. 단 두 줄
data_candle = go.Candlestick(x=data.Date,open=data.Open,high=data.High,low=data.Low,close=data.Close)
data = [data_candle]
이렇게 만든 candle을 화면에 보여주면 됩니다. 이것도 단 두 줄
fig = go.Figure(data=data,layout=layout)
fig.show()
화면에 보이는 모습니다.
아래에 스크롤바가 있어서 좌우로 옮기면서 볼 수 있습니다.
차트를 보면 예상한대로 움직이는군요. 장대 양봉은 잘 따라가서 수익을 내고 있고, 아래/위 긴 꼬리가 나오는 경우에는 손실이 발생합니다.
충분히 수익이 좋아보이지만 개선할 점도 보입니다. 아래 그림과 같이 행보장에서 매수하면 손실이 발생하는 경우가 많습니다. 이렇게 횡보장에서는 매수 신호가 나오도 무시하는 코드를 넣으면 성능이 더 좋아질 것 같습니다.
수정한 소스는 아래 github에 있습니다.
github.com/multizone-quant/System_trading_ex/tree/main
여기에서 아래 프로그램들을 다운받으시면 됩니다.
Larry_williams3.py
TR_LW.py
my_candle.py
my_util.py
sim_data/*
다음 글에서는 횡보장을 빼는 방법에 대하여 적어보도록 하겠습니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(6) - 리펙토링 (0) | 2020.11.26 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 (2) | 2020.11.22 |
[시스템트레이딩] 전략 시뮬레이션(3) (0) | 2020.11.19 |
[시스템트레이딩] 전략 시뮬레이션(2) (0) | 2020.11.18 |
[시스템트레이딩] 전략 시뮬레이션(1) (2) | 2020.11.16 |
글
[시스템트레이딩] 전략 시뮬레이션(3)
(2) 편에서 기술한 LW 전략 시뮬레이션 코드를 정리(refactoring)를 해 보겠습니다.
우선 class를 만드는 방법을 간단하게 기술하겠습니다. 클래스는 객체지향 언어가 등장하면서 나온 개념입니다. 클래스란 유사한 성질의 객체들을 하나로 그룹화한 것입니다. 쉽게 이야기하면 목적이 유사한 함수들을 모아놓았다고 보셔도 됩니다.
파이썬에서 클래스를 사용하는 방법은 아래와 같습니다.
class name() :
def __init__(self) :
# 각종 초기화
self.var1 = 0
def func1(self) :
# 함수 1
def func2(self) :
# 함수 2
self.func1() # 함수 1을 부를 때
self.var1 = 1
목적이 유사한 함수를 name이라는 class로 선언한 경우입니다. 여기에서 __init__(self)는 초기화를 위하여 사용되는 함수인데 항상 이름이 __init__이여야 합니다 이외에는 본인이 원하는 함수명을 추가하면 됩니다. 여기에서 self가 문제인데요. 이것도 그냥 외우고 사용하시면 됩니다. self가 사용되는 경우입니다.
- class 내 함수의 인자는 항상 self가 먼저 들어가야합니다.
- class 내에서 class내 함수를 부를 때는 항상 self. 으로 시작합니다.
- class 내에서 class내 변수를 사용할 때는 항상 self. 으로 시작합니다.
다음으로는 import 기능입니다. py 파일 하나에 모든 클래스와 함수가 들어가 있으면 가독성이 떨어집니다. 화면 스크롤을 하면서 소스를 봐야하는데 수 십 페이지면 특정 부분을 찾기가 힘들겠죠. 그래서 class로 만든 경우에는 대부분 별도 파이썬 파일로 분리합니다. 그리고 공통 lib의 경우에도 별도 파이썬으로 분리하면 좋습니다. 다른 프로젝트를 개발할 때 사용할 수 있으니까요.
이렇게 분리된 파이썬 파일은 import라는 명령어를 이용하여 불러올 수 있습니다.
전편에 만든 소스를 아래와 같이 파이썬 파일 3개를 추가로 만들었습니다.
- my_util.py : file io와 같이 공통적으로 사용하는 함수들
- my_candle.py : candle class
- TR_LW : LW class
이렇게 나뉘어진 class 혹은 파일을 사용하는 경우에는 아래와 같이 import를 하면 됩니다.
from my_util import *
from my_candle import *
from TR_LW import *
기존 프로그램이 총 4개의 파일로 나뉘어졌습니다. 그럼에도 불구하고 simulation 함수의 줄 수가 너무 많습니다. 여기도 손을 좀 보겠습니다. 우선 각종 통계를 위하여 사용하는 변수가 많습니다. 이런 경우에는 통계용 변수를 위한 class를 별도로 만들어 보겠습니다.
그냥 class 명 하나 정하고, 사용할 변수들 이동하면 됩니다. 그리고 필요한 함수도 만들면 됩니다.
class sim_stat :
def __init__(self) : # 각종 통계용 변수들 초기화
def update_stat(self, profit) : # 트레이딩이 완료되었을 때 수익금 update
이렇게 클래스로 뽑으면 아래와 같이 함수 한 줄로 대체가 가능합니다.
그리고 거래세 관련 부분, 시뮬레이션 중간 결과 저장하는 부분, 화면에 결과를 출력하는 부분, 결과를 파일에 저장하는 부분도 별도 함수로 뽑았습니다. 역시 복잡한 내용이 사라지고 함수 한 줄로 대체가 됩니다.
다음으로는 트레이딩 부분도 좀 더 재사용이 가능하게 변경하였습니다.
현재 LW 로직에서는 일봉을 대상으로 하기 때문에 매수 조건을 만족하면 당일 종가로 매도가 된다고 코딩을 했었습니다.
하지만 당일 종가가 아닌 다른 조건에 매도를 할 수도 있기 때문에 아래와 같이 수정하였습니다.
if 매수조건 :
매수
if 사졌다면 :
if 매도 조건이면 :
매도
현재와 같이 일봉 기준으로 당일 종가에 판다고 가정하였을 때 TR_LW class에서 매도 조건은 아래과 같이 코딩이 가능합니다.
def is_exit_condition(self, candle) :
return candle.close
만약 매도 조건을 바꾼다면 이 함수에서만 수정하면 됩니다. 다른 부분은 수정할 필요가 없기 때문에 오류가 날 확률이 줄어들게 됩니다.
여러 변화를 준 코드를 돌려보았습니다. 이전에 만든 프로그램과 결과가 같아야 되겠죠? 같은 결과가 나왔습니다. 이렇게 기존 코드를 수정하는 경우에는 반드시 이전과 결과가 같은지 확인을 해 보아야합니다. 이런 검증 과정도 아주 시간이 많이 걸리는데요. testcase를 이용하면 좀 더 효율적으로 변경한 코드의 정확성을 확인할 수 있습니다. 이 부분도 차후에 소개하도록 하겠습니다.
전체적인 구조가 많이 변경되었지만 프로그램 내용은 변화가 없기 때문에 이해하는데 큰 문제는 없을 것입니다.
아직도 class로 쪼개거나 class로 만들어야 하는 부분이 더 있습니다. 우선은 여기까지만 정리해보도록 하겠습니다.
다음에는 현재 개발한 LW 실행 결과를 그래프로 확인하는 과정과 성능을 개선하는 과정을 소개하도록 하겠습니다.
수정한 소스는 아래 github에 있습니다.
github.com/multizone-quant/System_trading_ex/tree/main
여기에서 아래 프로그램들을 다운받으시면 됩니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(5) - 추세구간, 이동평균선 추가하기 (2) | 2020.11.22 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 (2) | 2020.11.21 |
[시스템트레이딩] 전략 시뮬레이션(2) (0) | 2020.11.18 |
[시스템트레이딩] 전략 시뮬레이션(1) (2) | 2020.11.16 |
차트 그릴 때 진입 위치를 표시하는 방법 (2) | 2020.07.22 |
글
[시스템트레이딩] 전략 시뮬레이션(2)
이제 본격적으로 전략 시뮬레이션을 개발해 보겠습니다.
이 글은 시스템 트레이딩에 관심을 가지고 직접 개발해보고 싶은 전산 비 전공자를 대상으로 합니다. 가능한 쉽게 이해할 수 있도록 기술해보도록 하겠습니다.
이번에 개발할 전략은 Larry Williams의 변동성 돌파 전략(이하 LW)입니다.
LW와 관련하여 검색을 해보니 좋은 글들이 많았습니다. 이 중 heybit에서 LW를 실제 운영 중이라고 하는군요. 여기 나오는 전략을 개발해보도록 하겠습니다.
support.heybit.io/ko/articles/2297782-
이외에도 systraders79님의 아래 글도 굉장히 좋습니다. 이제 막 퀀트를 시작하시는 분들은 일독을 권합니다.
전략에 대한 자세한 설명은 위에 Heybit글을 참고하시면 될 것 같습니다.
LW를 개발하기 위하여 필요한 사항을 정리해보겠습니다.
1. test 대상 ticker : 암호화폐 BTC, ETH
2. back data : 2017-09-25 ~ 2020-11-12 일봉 데이타.
3. 거래 규칙 :
매수 : 일봉 데이타의 high 값이 매수 예정 가격(시가+전일 변동성*k)보다 높으면 매수
청산 : 당일 종가 (주식의 경우에는 다음 날 시초가 매도가 성과가 좋다고 알려져 있으나 암호화폐는 24시간 거래가 되므로 다음 날 시초가 의미가 없음)
수수료 : 0.035% (upbit 기준)
일단 k = 0.5로 고정, noise는 사용하지 않습니다.
전략을 돌려보기 위해서는 준비해야할 것들이 많습니다. 우선 파일에 저장된 과거 데이터를 읽어와야 합니다. 이런 부분은 전략을 만드는 것과는 크게 관련이 없기 때문에 제가 소개해드리는 코드를 그냥 사용하시면 됩니다.
기본 함수들
- read_csv_to_dict() : csv 형태로 저장된 과거 데이터를 dict 형태로 돌려주는 함수
- save_to_file_csv() : 전략 test 중 중간 값들을 보관한 후 그 결과를 csv 파일에 저장하는 함수. 이렇게 하는 이유는 LW가 제대로 동작하였는지 excel로 검정해보기 위하여 사용합니다.
- class Candle() : candle 데이터를 묶어서 처리하는 class. class라는 용어가 나오는데, 일단은 관련 함수의 집합이라고 생각하시면 됩니다. 앞으로 Candle()을 발전시켜서 분/일봉 관리 그리고 각종 지표들도 모두 구현해볼 예정입니다.
- make_candle_info() : simluation 데이터를 candle로 바꾸어 주는 함수
여기까지는 그냥 이런 함수들이 필요하다 정도만 생각하시고 내용은 무시하여도 됩니다.
이 함수들을 바탕으로 이제 본격적으로 전략 시뮬레이터를 만들어보겠습니다. 일단은 막 짜보겠습니다. 막 짠 코드이기 때문에 직관적이고 읽기 좋은 코드입니다 :)
시뮬레이터는 아래와 같은 형식으로 동작할 것입니다.
백데이터 읽기
LW 초기화 ( 가장 오래된 일봉에서 range값 구함 )
while(모든 데이터에 대하여) :
if 매수 조건을 만족하면 :
수익 = 오늘 종가 - 매수가격
누적수익 += 수익
print(누적수익)
LW와 관련된 구현내용입니다. 이것도 편의상 class로 개발해보았습니다. 앞에서 설명하였듯이 class는 그냥 관련 함수의 집합이다라고 생각하시면 이해가 편하실 것입니다.
class TR_LW()은 크게 세 함수로 구성되어 있습니다.
- __init__() : 초기화 함수, LW에서 사용하는 변수를 초기화 합니다. k 값은 일단 0.5로 사용합니다.
- update_new_range() : 새로운 일봉으로 변경하는 부분입니다. range 값을 update합니다.
- is_enter_candle() : 주어진 일봉에 대하여 매수 가능한지 여부를 판단합니다. 주어진 일봉의 고가가 매수예정가 보다 높으면 매수 예정가를 돌려줍니다. 아니면 0 return
이제 주 함수인 simulation() 입니다.
test용 백데이터는 candle_data에 저장되어 있습니다. simulation 중 발생하는 데이터를 기록하고 싶으면 이 변수에 추가를 하면 됩니다. 일단은 buy한 일봉에 대하여 buying 가격, 수익, draw down 값을 저장하도록 하겠습니다. 개발 과정에서 궁금한 변수가 있으면 여기에 계속 추가하면 됩니다.
# simulation 중 파일에 저장할 변수들 추가 [-1]은 가장 마지막을 의미함
candle_data[-1]['buying'] = 0 # buy 여부 저장
candle_data[-1]['profit'] = 0 # profit 저장
candle_data[-1]['total_profit'] = 0 # total_profit 저장
candle_data[-1]['dd'] = 0 # downd draw 저장
시뮬레이션에 필요한 변수를 초기화합니다. 초기 투입금액, 각종 통계용 변수들 등등
candle_data는 내림차순으로 저장되어 있기 때문에 simulation은 뒤에서 앞으로 읽어가면서 처리합니다.
for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩
매수 조건을 만족하는 일봉이라면 아래 일들을 합니다.
1. 매수 금액 결정 : 매수 금액을 복리로 할지 단리로 할지 결정을 해야합니다. 일단 최대 초기 원금으로 투자하는 것으로 하겠습니다. 다만 원금 손실이 발생하면 초기 원금보다 적을 수 있으므로 현재잔고(balance)와 초기원금(deposit) 중 min 값을 매수가능 금액으로 결정합니다.
# 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance
buying_amount = min(balance, deposit)
2. 매수 수량을 구한 후 종가에 매도 기준으로 수익을 계산한 후 balance update합니다.
3. 수수료를 계산하여 balance에 update합니다.
4. 통계를 위한 변수들을 update
5. log를 위하여 필요한 변수들을 저장
이렇게 모든 데이터에 대하여 돌린 후에는 그 결과를 csv 파일 형태로 저장합니다.
다음에는 simulation 결과를 간단하게 화면에 표시합니다.
마지막으로 simulation 결과를 파일에 저장합니다 파일 이름은 임의로 정의하시면 됩니다.
simulation을 위한 준비가 끝났습니다. 이제 이 함수를 부르기만 하면 됩니다.
이 함수는 ticker와 백데이터가 저장된 파일명, 전략을 인자로 받습니다.
simulation(ticker, fname, tr_logic)
simulation을 부를 때 전략을 전달하기 때문에 앞으로는 전략만 개발하면 됩니다.
fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv'
ticker = 'KRW-STEEM'
# Larry William 변동성 돌파, 일봉 사용
tr_logic = TR_LW('day', 1)
simulation(ticker, fname, tr_logic)
2017년 09월 25일 부터 2020년 11월 12일 동안 BTC 일봉으로 돌려보았습니다. 올해 BTC가 상승세를 보여서인지 1백만원 투입해서 수수료 제외하고 약 2.9백만원이 되었군요.
LW 전략이 암호화폐에 좋다는 이야기는 많이 들었는데, 역시나 좋군요.
github에 올린 코드를 보면 직관적으로 막 짰기 때문에 조금 지저분한 느낌이 듭니다. 새로운 전략이 나올 때 마다 코드 수정량이 많으면 생산성이 떨어지기 때문에 코드를 조금 정리를 해야합니다.
다음에는 이번에 소개해드린 코드를 조금 정리해서 향후 새로온 전략을 적용하기 좋은 형태로 바꾸어 보겠습니다.
소스코드는 아래에 있습니다.
github.com/multizone-quant/System_trading_ex/blob/main/Larry_williams1.py
주의사항.
시뮬레이션 결과가 저장된 파일이 열려있으면 동작 중 오류가 발생합니다. 따라서 시뮬레이션을 돌릴 때는 결과가 저장된 파일은 반드시 닫아야 합니다.
'시스템트레이딩' 카테고리의 다른 글
[시스템트레이딩] 전략 시뮬레이션(4) - 차트에 문자열 출력하기 (2) | 2020.11.21 |
---|---|
[시스템트레이딩] 전략 시뮬레이션(3) (0) | 2020.11.19 |
[시스템트레이딩] 전략 시뮬레이션(1) (2) | 2020.11.16 |
차트 그릴 때 진입 위치를 표시하는 방법 (2) | 2020.07.22 |
웹크롤링으로 관리종목, 거래중지 종목 구하기 (0) | 2020.06.05 |