#명 세 서 ##################################################################################################
"""
1. 사용전략
gma39_XAA
https://[Log in to view URL]
0 15 * * 1-5 python3 /var/autobot/XAA_Us_Bot.py
0분 / 15시 /
[변경내용]
"""
##########################################################################################################
# 뉴욕장 안열렸으면 종료 #####################################################################################
import exchange_calendars as ecals
import datetime as dt
import line_alert
XNYS = ecals.get_calendar("XNYS") # 한국 코드
ismarketopen = XNYS.is_session(dt.date.today().strftime("%Y-%m-%d")) # 오늘은 개장일인지 확인
if ismarketopen == False:
print(f'개장일이 아니어서 종료')
exit(0)
else :
line_alert.SendMessage(f'Gen_01_USA_part_03 : XAA 전략 시작합니다')
#########################################################################################################
#########################################################################################################
### import 수행 #########################################################################################
import KIS_Common as Common
import KIS_API_Helper_US as KisUS
import json, yaml,atexit,requests, time,pprint
import line_alert
from Util_discord_message import send_message
from pytz import timezone
import datetime as dt
import pandas as pd
from Util_get_access_token import get_access_token_daily
import Util_Hankook_USA_NAS as hk_nas
import Util_Hankook_USA_AMX as hk_amx
#######################################################################################################
# 사용계좌 선택 / 디스코드 선택##############################################################################
with open('00_config.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
app_key = _cfg['APP_KEY']
app_secret = _cfg['APP_SECRET']
acc_no = _cfg['CANO']
acc_flag = _cfg['ACNT_PRDT_CD']
url_base = _cfg['URL_BASE']
discord_url = _cfg['DISCORD_WEBHOOK_URL']
#########################################################################################################
#########################################################################################################
###토큰발급 및 클래스선언 ###########################################################################################
access_token = get_access_token_daily()
Common.MakeToken("REAL")
Common.MakeToken("VIRTUAL")
Common.SetChangeMode("REAL")
sd_nas = hk_nas.Stock_data(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
buy_nas = hk_nas.Order_buy(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
sell_nas = hk_nas.Order_sell(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
sd_amx = hk_amx.Stock_data(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
buy_amx = hk_amx.Order_buy(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
sell_amx = hk_amx.Order_sell(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
acc = hk_nas.Account_Info(app_key, app_secret, acc_no, acc_flag, discord_url, access_token)
#########################################################################################################
#########################################################################################################
### 공통함수 ###########################################################################################
def make_money(required_money):
shv_code = "SHV"
hold_qty, hold_amt, yield_rate = acc.get_one_stock_balance(shv_code)
if hold_amt > required_money:
send_message(f'SHV 매도하여 돈을 만듭니다.')
current_price = sd_nas.get_current_price(market="NAS", code=shv_code)
sell_qty = int(required_money/current_price)+1
sell_nas.지정가(market="NASD",code="SHV",qty=sell_qty)
elif hold_amt < required_money:
send_message(f'보유금액이 부족합니다')
return
#########################################################################################################
#########################################################################################################
if __name__ == '__main__':
"""# 프로그램 종료시 알림메시지 #"""
now = dt.datetime.now()
atexit.register(line_alert.SendMessage, f"[{now}]=프로그램 종료됨=")
while True:
try:
t_now = dt.datetime.now(timezone('America/New_York')) # 뉴욕 기준 현재 시간
t_0830 = t_now.replace(hour=8, minute=30, second=0, microsecond=0)
t_0835 = t_now.replace(hour=8, minute=35, second=0, microsecond=0)
t_0905 = t_now.replace(hour=9, minute=5, second=0, microsecond=0)
t_0930 = t_now.replace(hour=9, minute=30, second=0, microsecond=0)
t_0935 = t_now.replace(hour=9, minute=35, second=0, microsecond=0)
t_1130 = t_now.replace(hour=11, minute=30, second=0, microsecond=0)
t_1450 = t_now.replace(hour=14, minute=50, second=0, microsecond=0)
t_1500 = t_now.replace(hour=15, minute=00, second=0, microsecond=0)
t_1530 = t_now.replace(hour=15, minute=30, second=0, microsecond=0)
t_1600 = t_now.replace(hour=16, minute=00, second=0, microsecond=0)
print(t_now)
if t_now < t_1530:
time.sleep(60)
if t_1530< t_now :
BOT_NAME = Common.GetNowDist() + "_MyXAABotUs"
# 시간 정보를 읽는다
time_info = time.gmtime()
# 년월 문자열을 만든다 즉 2022년 9월이라면 2022_9 라는 문자열이 만들어져 strYM에 들어간다!
strYM = str(time_info.tm_year) + "_" + str(time_info.tm_mon)
print("ym_st: ", strYM)
# 포트폴리오 이름
PortfolioName = "XAA전략_US"
#####################################################################################################################################
#####################################################################################################################################
#####################################################################################################################################
# 리밸런싱이 가능한지 여부를 판단!
Is_Rebalance_Go = False
# 파일에 저장된 년월 문자열 (ex> 2022_9)를 읽는다
YMDict = dict()
# 파일 경로입니다.
asset_tym_file_path = BOT_NAME + ".json"
try:
with open(asset_tym_file_path, 'r') as json_file:
YMDict = json.load(json_file)
except Exception as e:
print("Exception by First")
# 만약 키가 존재 하지 않는다 즉 아직 한번도 매매가 안된 상태라면
if YMDict.get("ym_st") == None:
# 리밸런싱 가능! (리밸런싱이라기보다 첫 매수해야 되는 상황!)
Is_Rebalance_Go = True
# 매매가 된 상태라면! 매매 당시 혹은 리밸런싱 당시 년월 정보(ex> 2022_9) 가 들어가 있다.
else:
# 그럼 그 정보랑 다를때만 즉 달이 바뀌었을 때만 리밸런싱을 해야 된다
if YMDict['ym_st'] != strYM:
# 리밸런싱 가능!
Is_Rebalance_Go = True
# 강제 리밸런싱 수행!
# Is_Rebalance_Go = True
# 마켓이 열렸는지 여부~!
IsMarketOpen = KisUS.IsMarketOpen()
if IsMarketOpen == True:
print("Market Is Open!!!!!!!!!!!")
# 영상엔 없지만 리밸런싱이 가능할때만 내게 메시지를 보내자!
if Is_Rebalance_Go == True:
line_alert.SendMessage(PortfolioName + " (" + strYM + ") 장이 열려서 포트폴리오 리밸런싱 가능!!")
else:
print("Market Is Close!!!!!!!!!!!")
# 영상엔 없지만 리밸런싱이 가능할때만 내게 메시지를 보내자!
if Is_Rebalance_Go == True:
line_alert.SendMessage(PortfolioName + " (" + strYM + ") 장이 닫혀서 포트폴리오 리밸런싱 불가능!!")
#####################################################################################################################################
#####################################################################################################################################
#####################################################################################################################################
# 계좌 잔고를 가지고 온다!
Balance = KisUS.GetBalance()
print("--------------내 보유 잔고---------------------")
pprint.pprint(Balance)
print("--------------------------------------------")
# 총 평가금액에서 해당 봇에게 할당할 총 금액비율 1.0 = 100% 0.5 = 50%
InvestRate = 0.2
# 기준이 되는 내 총 평가금액에서 투자비중을 곱해서 나온 포트폴리오에 할당된 돈!!
TotalMoney = float(Balance['TotalMoney']) * InvestRate
print("총 포트폴리오에 할당된 투자 가능 금액 : $", TotalMoney)
##########################################################
# 투자 주식 리스트
MyPortfolioList = list()
StockCodeList = ["QQQ", "IYK", "IWM", "VWO", "VEA", "TLT", "IEF", "PDBC", "VNQ", "TIP", "BIL", "HYG", "LQD",
"EMB", "BNDX", "BWX"]
for stock_code in StockCodeList:
asset = dict()
asset['stock_code'] = stock_code # 종목코드
asset['stock_target_rate'] = 0 # 비중..
asset['stock_rebalance_amt'] = 0 # 리밸런싱 수량
MyPortfolioList.append(asset)
##########################################################
print("--------------내 보유 주식---------------------")
# 그리고 현재 이 계좌에서 보유한 주식 리스트를 가지고 옵니다!
MyStockList = KisUS.GetMyStockList()
pprint.pprint(MyStockList)
print("--------------------------------------------")
##########################################################
print("--------------리밸런싱 계산 ---------------------")
stock_df_list = []
for stock_code in StockCodeList:
#################################################################
#################################################################
df = Common.GetOhlcv("US", stock_code, 300)
#################################################################
#################################################################
df['prevClose'] = df['close'].shift(1)
df['1month'] = df['close'].shift(20)
df['3month'] = df['close'].shift(60)
df['6month'] = df['close'].shift(120)
df['12month'] = df['close'].shift(240)
# 1개월 수익률 + 3개월 수익률 + 6개월 수익률 + 12개월 수익률
df['Momentum'] = (((df['prevClose'] - df['1month']) / df['1month']) + (
(df['prevClose'] - df['3month']) / df['3month']) + (
(df['prevClose'] - df['6month']) / df['6month']) + (
(df['prevClose'] - df['12month']) / df['12month'])) / 4.0
# 6개월 수익률
df['Momentum6'] = ((df['prevClose'] - df['6month']) / df['6month'])
df.dropna(inplace=True) # 데이터 없는건 날린다!
data_dict = {stock_code: df}
stock_df_list.append(data_dict)
print("---stock_code---", stock_code, " len ", len(df))
pprint.pprint(df)
combined_df = pd.concat(
[list(data_dict.values())[0].assign(stock_code=stock_code) for data_dict in stock_df_list for stock_code in
data_dict])
combined_df.sort_index(inplace=True)
pprint.pprint(combined_df)
print(" len(combined_df) ", len(combined_df))
date = combined_df.iloc[-1].name
tip_data = combined_df[(combined_df.index == date) & (combined_df['stock_code'] == "TIP")]
bil_data = combined_df[(combined_df.index == date) & (combined_df['stock_code'] == "BIL")]
# 채권을 제외한 공격자산의 모멘텀 스코어가 높은거 상위 TOP 4개를 리턴!
pick_momentum_top = combined_df.loc[(combined_df.index == date) & (combined_df['stock_code'] != "BWX") & (
combined_df['stock_code'] != "BNDX") & (combined_df['stock_code'] != "EMB") & (
combined_df['stock_code'] != "LQD") & (
combined_df['stock_code'] != "HYG") & (
combined_df['stock_code'] != "TIP") & (
combined_df['stock_code'] != "BIL")].groupby('stock_code')[
'Momentum'].max().nlargest(4)
# 공격자산을 제외한 채권들중 모멘텀 스코어가 높은거 상위 TOP 3개를 리턴!
pick_bond_momentum_top = combined_df.loc[(combined_df.index == date) & (combined_df['stock_code'] != "QQQ") & (
combined_df['stock_code'] != "IYK") & (combined_df['stock_code'] != "SPY") & (
combined_df['stock_code'] != "IWM") & (
combined_df['stock_code'] != "VWO") & (
combined_df['stock_code'] != "VEA") & (
combined_df['stock_code'] != "DBC") & (
combined_df['stock_code'] != "PDBC") & (
combined_df['stock_code'] != "VNQ")].groupby('stock_code')[
'Momentum6'].max().nlargest(3)
checkall = combined_df.loc[(combined_df.index == date)].groupby('stock_code')['close'].max().nlargest(
len(StockCodeList))
if len(checkall) == len(StockCodeList):
# 안전 자산 비중 정하기!
SafeRate = 0
AtkRate = 0
AtkOkList = list()
IsTopCheck = False
Top1Code = ""
# TIP 모멘텀 양수 장이 좋다!
if tip_data['Momentum'].values[0] > 0:
for stock_code in pick_momentum_top.index:
stock_data = combined_df[(combined_df.index == date) & (combined_df['stock_code'] == stock_code)]
if len(stock_data) == 1:
# 공격 자산의 모멘텀이 0보다 크면 투자한다!!
if stock_data['Momentum'].values[0] >= 0:
AtkOkList.append(stock_code)
# 제일 먼저 체크한 것이 가장 모멘텀 스코어가 큰 자산이니 그 종목 코드를 따로 저장해 둔다!
if IsTopCheck == False:
IsTopCheck = True
Top1Code = stock_code
# 아니면 투자하지 않는다. 남는 비중을 저장!
else:
AtkRate += 0.25
# TIP 모멘텀 음수 장이 안좋아!
else:
# 안전자산에 100% 투자한다!
SafeRate = 1.0
# 공격 자산중 투자안한 비중이 있다면
if AtkRate > 0:
HalfAtkRate = AtkRate * 0.5
SafeRate += HalfAtkRate # 안전 비중에 절반을 나눠준다.
AtkRate -= HalfAtkRate
# 리밸런싱 수량을 확정한다!
for stock_info in MyPortfolioList:
stock_code = stock_info['stock_code']
stock_data = combined_df[(combined_df.index == date) & (combined_df['stock_code'] == stock_code)]
if len(stock_data) == 1:
IsRebalanceGo = False
NowClosePrice = stock_data['close'].values[0]
# 안전 자산 비중이 있는 경우!! 안전자산에 투자!!!
if SafeRate > 0:
for stock_code_b in pick_bond_momentum_top.index:
if stock_code_b == stock_code:
# BIL보다 높은 것만 투자!
if stock_data['Momentum6'].values[0] >= bil_data['Momentum6'].values[0]:
stock_info['stock_target_rate'] += (SafeRate / len(pick_bond_momentum_top.index))
break
# 선택된 공격자산이라면!! 0.25%씩 투자해준다!
if stock_code in AtkOkList:
# 단 가장 모멘텀 좋은 자산은 아까 위에서 계산한 추가 비중이 있다면 더해준다!
if stock_code == Top1Code:
stock_info['stock_target_rate'] += (0.25 + AtkRate)
else:
stock_info['stock_target_rate'] += 0.25
strResult = "-- 현재 포트폴리오 상황 --\n"
# 매수된 자산의 총합!
total_stock_money = 0
# 현재 평가금액 기준으로 각 자산이 몇 주씩 매수해야 되는지 계산한다 (포트폴리오 비중에 따라) 이게 바로 리밸런싱 목표치가 됩니다.
for stock_info in MyPortfolioList:
# 내주식 코드
stock_code = stock_info['stock_code']
# 매수할 자산 보유할 자산의 비중을 넣어준다!
stock_target_rate = float(stock_info['stock_target_rate'])
# 현재가!
CurrentPrice = KisUS.GetCurrentPrice(stock_code)
stock_name = ""
stock_amt = 0 # 수량
stock_avg_price = 0 # 평단
stock_eval_totalmoney = 0 # 총평가금액!
stock_revenue_rate = 0 # 종목 수익률
stock_revenue_money = 0 # 종목 수익금
# 내가 보유한 주식 리스트에서 매수된 잔고 정보를 가져온다
for my_stock in MyStockList:
if my_stock['StockCode'] == stock_code:
stock_name = my_stock['StockName']
stock_amt = int(my_stock['StockAmt'])
stock_avg_price = float(my_stock['StockAvgPrice'])
stock_eval_totalmoney = float(my_stock['StockNowMoney'])
stock_revenue_rate = float(my_stock['StockRevenueRate'])
stock_revenue_money = float(my_stock['StockRevenueMoney'])
break
print("##### stock_code: ", stock_code)
# 주식의 총 평가금액을 더해준다
total_stock_money += stock_eval_totalmoney
# 현재 비중
stock_now_rate = 0
# 잔고에 있는 경우 즉 이미 매수된 주식의 경우
if stock_amt > 0:
stock_now_rate = round((stock_eval_totalmoney / TotalMoney), 3)
print("---> NowRate:", round(stock_now_rate * 100.0, 2), "%")
# 목표한 비중이 다르다면!!
if stock_now_rate != stock_target_rate:
# 갭을 구한다!!!
GapRate = stock_target_rate - stock_now_rate
# 그래서 그 갭만큼의 금액을 구한다
GapMoney = TotalMoney * abs(GapRate)
# 현재가로 나눠서 몇주를 매매해야 되는지 계산한다
GapAmt = GapMoney / CurrentPrice
# 수량이 1보다 커야 리밸러싱을 할 수 있다!! 즉 그 전에는 리밸런싱 불가
if GapAmt >= 1.0:
GapAmt = int(GapAmt)
# 갭이 음수라면! 비중이 더 많으니 팔아야 되는 상황!!!
if GapRate < 0:
print("this!!!")
stock_info['stock_rebalance_amt'] = -GapAmt
# 갭이 양수라면 비중이 더 적으니 사야되는 상황!
else:
stock_info['stock_rebalance_amt'] = GapAmt
# 잔고에 없는 경우
else:
print("---> NowRate: 0%")
# 잔고가 없다면 첫 매수다! 비중대로 매수할 총 금액을 계산한다
BuyMoney = TotalMoney * stock_target_rate
# 매수할 수량을 계산한다!
BuyAmt = int(BuyMoney / CurrentPrice)
stock_info['stock_rebalance_amt'] = BuyAmt
# 라인 메시지랑 로그를 만들기 위한 문자열
line_data = (">> " + stock_code + " << \n비중: " + str(round(stock_now_rate * 100.0, 2)) + "/" + str(
round(stock_target_rate * 100.0, 2))
+ "% \n수익: $" + str(stock_revenue_money) + "(" + str(round(stock_revenue_rate, 2))
+ "%) \n총평가금액: $" + str(round(stock_eval_totalmoney, 2))
+ "\n현재보유수량: " + str(stock_amt)
+ "\n리밸런싱수량: " + str(stock_info['stock_rebalance_amt']) + "\n----------------------\n")
if Is_Rebalance_Go == True:
line_alert.SendMessage(line_data)
strResult += line_data
##########################################################
print("--------------리밸런싱 해야 되는 수량-------------")
data_str = "\n" + PortfolioName + "\n" + strResult + "\n포트폴리오할당금액: $" + str(
round(TotalMoney, 2)) + "\n매수한자산총액: $" + str(round(total_stock_money, 2))
# 결과를 출력해 줍니다!
print(data_str)
# 영상엔 없지만 리밸런싱이 가능할때만 내게 메시지를 보내자!
# if Is_Rebalance_Go == True:
# line_alert.SendMessage(data_str)
# 만약 위의 한번에 보내는 라인메시지가 짤린다면 아래 주석을 해제하여 개별로 보내면 됩니다
if Is_Rebalance_Go == True:
line_alert.SendMessage(
"\n포트폴리오할당금액: $" + str(round(TotalMoney, 2)) + "\n매수한자산총액: $" + str(round(total_stock_money, 2)))
print("--------------------------------------------")
##########################################################
# '''
# 리밸런싱이 가능한 상태여야 하고 매수 매도는 장이 열려있어야지만 가능하다!!!
Is_Rebalance_Go=True
IsMarketOpen=True
if Is_Rebalance_Go == True and IsMarketOpen == True:
line_alert.SendMessage(PortfolioName + " (" + strYM + ") 리밸런싱 시작!!")
print("------------------리밸런싱 시작 ---------------------")
# 이제 목표치에 맞게 포트폴리오를 조정하면 되는데
# 매도를 해야 돈이 생겨 매수를 할 수 있을 테니
# 먼저 매도를 하고
# 그 다음에 매수를 해서 포트폴리오를 조정합니다!
print("--------------매도 (리밸런싱 수량이 마이너스인거)---------------------")
for stock_info in MyPortfolioList:
# 내주식 코드
stock_code = stock_info['stock_code']
rebalance_amt = stock_info['stock_rebalance_amt']
# 리밸런싱 수량이 마이너스인 것을 찾아 매도 한다!
if rebalance_amt < 0:
# 현재가!
CurrentPrice = KisUS.GetCurrentPrice(stock_code)
# 현재가보다 아래에 매도 주문을 넣음으로써 시장가로 매도
CurrentPrice *= 0.99
pprint.pprint(KisUS.MakeSellLimitOrder(stock_code, abs(rebalance_amt), CurrentPrice))
#######################################################
# 지정가로 하려면 아래 함수 활용! 단 클래스 수강 필요!
# Common.AutoLimitDoAgain(BOT_NAME,"US",stock_code,CurrentPrice,rebalance_amt,"DAY_END")
print("--------------------------------------------")
# 3초 정도 쉬어준다
time.sleep(3.0)
print("--------------매수 ---------------------")
for stock_info in MyPortfolioList:
# 내주식 코드
stock_code = stock_info['stock_code']
rebalance_amt = stock_info['stock_rebalance_amt']
current_cash = acc.get_cash_balance()
# 리밸런싱 수량이 플러스인 것을 찾아 매수 한다!
if rebalance_amt > 0:
# 현재가!
CurrentPrice = KisUS.GetCurrentPrice(stock_code)
if current_cash > int(rebalance_amt*CurrentPrice):
# 현재가보다 위에 매수 주문을 넣음으로써 시장가로 매수
CurrentPrice *= 1.01
pprint.pprint(KisUS.MakeBuyLimitOrder(stock_code, rebalance_amt, CurrentPrice))
#######################################################
# 지정가로 하려면 아래 코드로 매수! 단 클래스 수강 필요!
# Common.AutoLimitDoAgain(BOT_NAME,"US",stock_code,CurrentPrice,rebalance_amt,"DAY_END")
#돈없으면 돈 만들고 산다.
elif current_cash < int(rebalance_amt*CurrentPrice):
make_money(rebalance_amt*CurrentPrice-current_cash)
# 현재가보다 위에 매수 주문을 넣음으로써 시장가로 매수
CurrentPrice *= 1.01
pprint.pprint(KisUS.MakeBuyLimitOrder(stock_code, rebalance_amt, CurrentPrice))
#######################################################
# 지정가로 하려면 아래 코드로 매수! 단 클래스 수강 필요!
# Common.AutoLimitDoAgain(BOT_NAME,"US",stock_code,CurrentPrice,rebalance_amt,"DAY_END")
print("--------------------------------------------")
#########################################################################################################################
# 첫 매수던 리밸런싱이던 매매가 끝났으면 이달의 리밸런싱은 끝이다. 해당 달의 년달 즉 22년 9월이라면 '2022_9' 라는 값을 파일에 저장해 둔다!
# 파일에 저장하는 부분은 여기가 유일!!!!
YMDict['ym_st'] = strYM
with open(asset_tym_file_path, 'w') as outfile:
json.dump(YMDict, outfile)
#########################################################################################################################
line_alert.SendMessage(PortfolioName + " (" + strYM + ") 리밸런싱 완료!!")
print("------------------리밸런싱 끝---------------------")
#남은금액은 SHV 사기
current_cash = acc.get_cash_balance()
shv_code = "SHV"
if current_cash > 500:
buy_nas.지정가(market="NASD",code=shv_code,buy_amount=current_cash)
send_message(f'리밸런싱 마치고 종료합니다.')
exit(0)
# 에러사항 발생시 조치사항 ################################################################################
except requests.packages.urllib3.exceptions.MaxRetryError:
send_message(f'Max Retry Error 60초 대기 후 재실행')
time.sleep(60)
except requests.packages.urllib3.exceptions.ConnectionError:
send_message(f'Connection 에러발생 60초 대기 후 재실행')
time.sleep(60)
# 바로 위에거랑 차이가 뭐지
except requests.exceptions.ConnectionError:
send_message(f'Connection 에러 2번째 발생 60초 대기 후 재실행')
time.sleep(60)
# 최상위 에러는 마지막에 놔둬야한다. 그리고 위 에러말고는 종료하게 만든다.
except Exception as e:
send_message(f"[오류 발생] : {e}")
exit(0)
To embed this project on your website, copy the following code and paste it into your website's HTML: