자동매매 프로그램 제작
5
1705
2022-10-07 15:08:34
지난 글에 말했던 자동매매 프로그램의 코드들 중 메인 모듈에 해당하는 코드입니다.
calemarsi 함수는 이동평균선 3개와 RSI값을 합쳐서 LONG인지 SHORT인지 아니면 HOLD인지 반환합니다.
가장 짧은 기간의 이평선이 가장 위에 있고 RSI값이 70 초과이면 SHORT을 반환하고, 앞의 이평선 값이 가장 아래에 있고 RSI 값이 30 미만이면 LONG을 반환하는 식입니다. 자세한 것은 나중에 그 모듈을 볼 때 작성하겠습니다.
lwstrategy 함수는 이전 글에 언급했던 래리윌리엄스의 변동성 돌파 전략을 함수로 만든 것으로 조건을 만족하면 True를 반환하고, 아니면 False를 반환하게 되어 있습니다.
저는 두 신호를 조합해서 long 포지션을 매수하거나 short 포지션을 매수하는 전략을 쓰고 있습니다.
하지만 계쏙 HOLD만 나오고 실제 매수하는 건이 너무 적어 내부적으로 기준을 조금씩 조정하고 있습니다.
그리고 신호가 나오면 어떤 신호가 나왔는지, 제대로 작동은 하는지 알아보기 위해 텔레그램을 통해 메시지를 매 15분마다 받고 있습니다.
이것도 지금 시그널이 적어 텔레그램으로 보내는 시그널을 늘리려고 생각 중입니다.
현재는 XRP에 대해서만 프로그램을 돌리고 있는데, 완성이 되는대로 다른 코인들도 추가해서 돌릴 예정입니다.
그 때를 대비해서 코드는 짜놓고 주석으로 처리했습니다.
#tickers = get_ticker()
ticker = 'XRPUSD_PERP'
notified_tickers = [] # 텔레그램 메시지 보낸 ticker 리스트(15분마다 초기화됨)
while True:
try:
# for ticker in tickers:
# Buying signal checking
osig = calemarsi(ticker)
lwst = lwstrategy(ticker, osig)
if (osig == 'LONG' and lwst == True and notified_tickers.count(ticker) == 0 and ALARM_TELEGRAM):
notified_tickers.append(ticker)
side = 'BUY'
elif (osig == 'SHORT' and lwst == True and notified_tickers.count(ticker) == 0 and ALARM_TELEGRAM):
notified_tickers.append(ticker)
side = 'SELL'
elif lwst == False or osig == 'HOLD':
notified_tickers.append(ticker)
side = 'HOLD'
밑의 모듈은 15분 마다 포지션을 정산하기 위해 만든 것으로 포지션잔고?를 바이낸스에서 끌고와서 확인한 뒤, 포지션이 있으면 정리하는 식으로 작성했습니다. sellorder 함수는 나중에 또 이 부분을 다룰 때 설명하겠습니다.
이러한 데이터를 처리할 때 리스트보다는 데이터프레임이 보기 편해서 데이터프레임을 쓰는데, 막상 데이터프레임에서 특정 값을 뽑아내는 것은 또 까다로워서 고민입니다. 지금이야 이 부분을 손댈 일이 없지만, 향후 데이터프레임으로 또 데이터를 가공했을 때 어떻게 할지 생각해봐야 겠습니다.
# Close position already opened
position = get_user_data('/dapi/v1/positionRisk')
df3 = pd.DataFrame( position, columns = ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'maxQty', 'marginType', 'isolatedMargin', 'isAutoAddMargin', 'positionSide', 'notionalValue', 'isolatedWallet', 'updateTime'])
df3 = df3.astype({'entryPrice' : 'float'})
df3_1 = df3.loc[(df3['entryPrice'] > 0), ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'isolatedMargin', 'positionSide']]
po = int(len(df3_1.loc[df3_1['symbol'] == ticker]))
pl = int(len(df3_1.loc[ (df3_1['positionSide'] == 'LONG') & (df3_1['symbol'] == ticker) ]))
ps = int(len(df3_1.loc[ (df3_1['positionSide'] == 'SHORT') & (df3_1['symbol'] == ticker) ]))
mp_1 = getmarkprice(ticker)
mp_2 = pd.DataFrame(mp_1, columns=['symbol', 'pair', 'markPrice', 'indexPrice', 'estimatedSettlePrice', 'lastFundingRate', 'interestRate', 'nextFundingTime', 'time'])
mp_3 = float(mp_2['markPrice'])
if po > 0 and pl > 0:
osig_1 = 'LONG'
side_1 = 'SELL'
msg6 = createsellorder(ticker, side_1, osig_1)
message6 = f'{msg6}'
bot.sendMessage(TELEGRAM_CHAT_ID, text=message6)
print(message6)
elif po > 0 and ps > 0:
osig_2 = 'SHORT'
side_2 = 'BUY'
msg6 = createsellorder(ticker, side_2, osig_2)
message6 = f'{msg6}'
bot.sendMessage(TELEGRAM_CHAT_ID, text=message6)
print(message6)
else:
msg7 = f'전체 포지션 {po}개, 롱포지션 {pl}개 숏포지션 {ps}개'
밑의 코드는 실제 매수 주문을 넣는 코드입니다.
long과 buy 신호가 같이 오면 롱포지션을 매수하고, short과 sell 신호가 같이오면 숏포지션을 매수하는 방식입니다. 왜 short은 buy가 아니라 sell에 매수하냐고 하실 수 있는데, 외국애들은 short 포지션을 sell 하는 것이 실제로는 포지션을 구축하는 것으로 보더라구요. short에 buy로 주문 넣었다가 숏포지션을 매도 해버린 경험이 있기 때문에 혹시 바이낸스로 자동매매 하실 분들은 주의하세요.
매수 하면 실제로 매수가 되었는지 바이낸스에서 오더 정보를 받아와서 확인하고 sqlite에 매수 단가랑 수량, 오더 넘버 등을 기록하게 해놨습니다. 그 결과는 똑같이 텔레그램으로 받구요. sqlite에 기록이 제대로 안되면 또 다른 메세지가 오도록 셋팅도 했습니다. 그건 나중에 오더함수를 자세히 볼 때 쓰겠습니다.
# Send buying order either long or short
if osig == 'LONG' and side == 'BUY':
position = get_user_data('/dapi/v1/positionRisk')
df3 = pd.DataFrame( position, columns = ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'maxQty', 'marginType', 'isolatedMargin', 'isAutoAddMargin', 'positionSide', 'notionalValue', 'isolatedWallet', 'updateTime'])
df3 = df3.astype({'entryPrice' : 'float'})
df3_1 = df3.loc[(df3['entryPrice'] > 0), ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'isolatedMargin', 'positionSide']]
pl = int(len(df3_1.loc[ (df3_1['positionSide'] == 'LONG') & (df3_1['symbol'] == ticker) ]))
if pl == 0:
msg1 = createbuyorder(ticker, side, osig)
message1 = f'{msg1}'
bot.sendMessage(TELEGRAM_CHAT_ID, text=message1)
print(message1)
elif pl > 0:
message3 = f"{ticker} position is already opened. please check your account! {osig} {side}"
bot.sendMessage(TELEGRAM_CHAT_ID, text=message3)
print(message3)
elif osig == 'SHORT' and side == 'SELL':
position = get_user_data('/dapi/v1/positionRisk')
df3 = pd.DataFrame( position, columns = ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'maxQty', 'marginType', 'isolatedMargin', 'isAutoAddMargin', 'positionSide', 'notionalValue', 'isolatedWallet', 'updateTime'])
df3 = df3.astype({'entryPrice' : 'float'})
df3_1 = df3.loc[(df3['entryPrice'] > 0), ['symbol', 'positionAmt', 'entryPrice', 'markPrice', 'unRealizedProfit', 'liquidationPrice', 'leverage', 'isolatedMargin', 'positionSide']]
ps = int(len(df3_1.loc[ (df3_1['positionSide'] == 'SHORT') & (df3_1['symbol'] == ticker) ]))
if ps == 0:
msg5 = createbuyorder(ticker, side, osig)
message1 = f'{msg5}'
bot.sendMessage(TELEGRAM_CHAT_ID, text=message1)
print(message1)
elif ps > 0:
message3 = f"{ticker} position is already opened. please check your account! {osig} {side}"
bot.sendMessage(TELEGRAM_CHAT_ID, text=message3)
print(message3)
elif side == 'HOLD' or osig == 'HOLD':
message4 = f"{ticker} is not ready. \n{msg7} {osig} {side}"
bot.sendMessage(TELEGRAM_CHAT_ID, text=message4)
print(message4)
밑에 함수로 이제 15분마다 이 모듈이 작동하게 구현했습니다. 결국 여기 함수들이 모두 ticker에 값이 들어가야 작동하는데 이것을 15분 동안 비워놨다가 다시 15분 뒤에 넣어서 돌리고 지우고 이런 방식입니다.
# Make it work every 15m
now = dt.datetime.now()
mod = divmod(now.minute, 15)[1]
if mod == 0:
is_multiple_15 = True
else: is_multiple_15 = False
if(is_multiple_15 == True and now.second > 5 and now.second < 10):
notified_tickers.clear()
time.sleep(SLEEP_TIME)
except Exception as e:
print(e)
time.sleep(1)
오늘 보신 코드는 이제 몸통이 되는 코드입니다. 이것 이외에 이제 실제 주문을 request를 통해 넣는 함수들도 있고, 전략을 구현하는 함수들도 있습니다. 이것들도 나중에 한번 따로 설명들 드리겠습니다.
저는 이 글을 작성하면서 제 생각도 정리하고 전략을 바꾸거나 좀 더 코드를 깔끔하게 바꾸거나 그렇게 하려고 합니다. 다른 분들에게 도움이 된다면 더욱 좋구요.
그냥 재미로 봐주셔도 좋습니다.
다들 수고하세요
8
Comments
글쓰기 |