2020년 9월 20일 일요일

주가 예측 (3) - xgboost reg:squarederror, python, scale, normalize


앞선 글에서 -6%의 수익률을 맛보았습니다.

좀 더  높이기 위한 방법에 대해서 연구 중입니다. 지난번에도 언급한 학습 데이터 부족으로 인한 학습이 안된는 현상입니다. 현실적으로 없는 데이터를 어디서 구하면 될까요?

정답은 없습니다. 그래서 이번에는 학습이 안되는 과거 데이터와 현재 데이터의 괴리를 해결 해보겠습니다. 즉, 과거 10년전에 100 원 하던 주식이 지금 1000 원 하는 현상입니다. 그렇기 때문에 과거 데이터로 학습이 되지 않는 현상입니다.

데이터를 일정 scale로 만들어주는 정규화를 해보겠습니다.

일반적인 정규화 관련 코드는 아래와 같습니다.

if NOR_ALL == True:
 names = allX.columns
 scaler = preprocessing.MinMaxScaler()
 allX = scaler.fit_transform(allX)
 allX = pd.DataFrame(allX, columns=names)

전체 dataframe에 대해서 정규화를 하면 어떻게 되는지 알아 보겠습니다.


정규화를 하면 0~1 사이값을 가지게 됩니다. Time 부분을 보면 예전값은 0이 되고 최근값은 1이 됩니다. Price값과 관계없게 되고, 결론은 row(세로) 전체에서 최대값과 최소값으로부터 column scale 을 새로 하게 되는 것입니다. 이렇게 변화된다고 해서 예전 주가가 낮아진것에 대한 처리를 해주지 못하게 됩니다.

어떻게 하는게 좋을지 생각해보면 우리는 lag데이터를 생성했으므로 가로 기준을 scale을 하면 좋을 듯합니다. 이렇게 되면 거래량이 있어서 이 부분은 따로 처리를 해주어야합니다.

다음 3개 파일을 만들고 순차적으로 실행할 것입니다.
1. predict_feature_normal_reg.py ( feature 처리 담당 )
2. predict_xgboost_reg.py ( 예측 ) 기존 파일 사용
3. predict_simul_normal_reg.py ( 예측 결과로 시뮬레이션 )


predict_feature_normal_reg.py ( feature 처리 담당 )
이전 작업한 코드와 비교

전체 코드 : predict_feature_normal_reg.py ( feature 처리 담당 )
import config
import platform
import sqlite3
import pandas as pd
from sklearn import preprocessing

CSV_PREDICT_FNAME = "predict.csv"
CSV_TEST_FNAME = "test.csv"
CSV_TRAIN_FNAME = "train.csv"

print(platform.architecture())

if platform.architecture()[0]!='64bit' :
	print("Please use 64bit python")
	exit(-1)


"""
DB
날짜(N-0) 가격(N-0) 거래량(N-0)
날짜(N-1) 가격(N-1) 거래량(N-1)
날짜(N-2) 가격(N-2) 거래량(N-2)
날짜(N-3) 가격(N-3) 거래량(N-3)
날짜(N-4) 가격(N-4) 거래량(N-4)
날짜(N-5) 가격(N-5) 거래량(N-5)
...

갯수는 X_M_PREDAYES : 예측할때 앞의 몇일을 가지고 학습 데이터를 만들지 결정하는 값
price(N-0),vol(N-0),price(N-1),vol(N-1),price(N-2),vol(N-2),price(N-3),vol(N-3)...N-M
가격(N-0) 거래량(N-0)|가격(N-1) 거래량(N-1)|가격(N-2) 거래량(N-2)|가격(N-3) 거래량(N-3)...N-M

갯수는 Y_O_POSTDAYES : 예측할때 뒤의 몇일을 가지고 결과를 만들지 결정하는 값
Y(N) : price(N+1),price(N+1),price(N+2),price(N+3)...N+O  이값중 price(N) 보다 목표% 보다 커지면 Y(N):1(구매해야함) 이 된다.

"""
def make_input_data(stockname,type):
	query = cur.execute("SELECT * FROM "+GetDBRealTableName(stockname,type)+" ORDER BY Time DESC")
	cols = [column[0] for column in query.description]
	df = pd.DataFrame.from_records(data=query.fetchall(), columns=cols)
	return df

def GetDBRealTableName(stockname,type):
	if type=="MIN" :
		return '"'+stockname+"_MIN"+'"'
	if type=="DAY" :
		return '"'+stockname+"_DAY"+'"'
	return ""

def date_to_int(datestr):
	return int(datestr)

def make_lag_df(train_df,laglabel,step):
	train_df[laglabel+"_lag"+str(step)]=train_df[laglabel].shift(step*-1)
	return train_df

def join_with_prev(df,prev_df,how):
	df = df.merge(prev_df,how=how)
	return df

# 날짜 날짜-1 날짜-2 가격 ...
#갯수는 Y_O_POSTDAYES : 예측할때 뒤의 몇일을 가지고 결과를 만들지 결정하는 값
#Y(N) : price(N+1),price(N+1),price(N+2),price(N+3)...N+O  이값중 price(N) 보다 목표% 보다 커지면 Y(N):1(구매해야함) 이 된다.
#  O = 2
#  price(N+2),price(N+1),price(0),price(N-1),price(N-2),price(N-3)...
#  price(0),price(N-1),price(N-2),price(N-3),price(N-4),price(N-5)...
#      2        1
def resultx(x):
	sum=0
	for i in range(config.Y_O_POSTDAYES-1,1-1-1,-1):
		sum = sum + x[4*i+0] #0 Price_lag
	return sum/config.Y_O_POSTDAYES # mean

def resultnormalx(x):
	scaler = preprocessing.MinMaxScaler()
	x = x.values
	y=scaler.fit_transform(x.reshape(-1,1))
	return pd.Series(y.reshape(1,-1).flatten())

def resulty(y):
	# y price
	return y[1]/y[0]

if __name__ == '__main__':
	
	con = sqlite3.connect(config.db_name)
	cur = con.cursor()
	for stockname in config.predict_stock_list_for_db :
		df = make_input_data(stockname,"DAY")
		df_base = df #df[['Time','Price','Vol']]
		#df_base["int_time"] = df_base["Time"].map(date_to_int).astype(int)
		
		lag_want_list = df.columns.tolist()
		lag_want_list.remove("Time")
		for step in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			for lagcolname in lag_want_list:
				df_base = make_lag_df(df_base,lagcolname, step)
			
		print(df_base)
		
		# set Y
		check_col = []
		for i in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			check_col.append("Price_lag"+str(i)) #0
			check_col.append("SPrice_lag"+str(i))#1
			check_col.append("HPrice_lag"+str(i))#2
			check_col.append("LPrice_lag"+str(i))#3
		df_base['Y'] = df_base[check_col].apply(resultx, axis=1)
		df_base['Y'] = df_base['Y'].shift(config.Y_O_POSTDAYES)
		
		# normalize
		check_col = []
		for i in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			check_col.append("Price_lag"+str(i)) #0
			check_col.append("SPrice_lag"+str(i))#1
			check_col.append("HPrice_lag"+str(i))#2
			check_col.append("LPrice_lag"+str(i))#3
		df_base[check_col] = df_base[check_col].apply(resultnormalx, axis=1)
		check_col = []
		for i in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			check_col.append("Vol_lag"+str(i)) #0
		df_base[check_col] = df_base[check_col].apply(resultnormalx, axis=1)
		df_base['Y'] = df_base[['Y','Price']].apply(resulty, axis=1)
		
		# Save
		#df_base = df_base.drop(['Time','Price','Vol','SPrice','HPrice','LPrice'],axis=1)
		
		#예측데이터
		predict = df_base.head(config.Y_O_POSTDAYES).drop(['Y'],axis=1)
		predict.to_csv(CSV_PREDICT_FNAME, encoding='utf-8', index=False)
		df_base.drop(df_base.head(config.Y_O_POSTDAYES).index, inplace=True)
		#검증데이터
		test = df_base.head(config.COUNT_TEST_DATA)
		test.to_csv(CSV_TEST_FNAME, encoding='utf-8', index=False)
		df_base.drop(df_base.head(config.COUNT_TEST_DATA).index, inplace=True)
		#훈련데이터
		df_base.to_csv(CSV_TRAIN_FNAME, encoding='utf-8', index=False)
	con.close()

코드 설명

핵심코드는 normalize 하는 부분으로 apply 함수를 이용하여 가격을 부분을 먼저 normalize 합니다.

		check_col = []
		for i in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			check_col.append("Price_lag"+str(i)) #0
			check_col.append("SPrice_lag"+str(i))#1
			check_col.append("HPrice_lag"+str(i))#2
			check_col.append("LPrice_lag"+str(i))#3
		df_base[check_col] = df_base[check_col].apply(resultnormalx, axis=1)



def resultnormalx(x):
	scaler = preprocessing.MinMaxScaler()
	x = x.values
	y=scaler.fit_transform(x.reshape(-1,1))
	return pd.Series(y.reshape(1,-1).flatten())

x로 넘어오는 값은 table의 세로열 값이 됩니다. 즉 lag의 연속이 되고 lag값중에 Price되는 부분만 reshape을 이용해서 2차원 배열로 만듭니다. 이렇게 하는 이유는 scaler가 2차원 numpy를 입력 변수를 받기 때문입니다. 결과가 나오면 이것을 다시 reshape해서 flatten 한뒤 Series로 만들게 됩니다. 이렇게 해야만 dataframe에 다시 넣을 수 있습니다.

또한 거래양도 거래량끼리 정규화를 해야합니다. 그래서 아래코드로 따로 분리되어있습니다.

		check_col = []
		for i in range(0,config.X_M_PREDAYES+config.Y_O_POSTDAYES+1):
			check_col.append("Vol_lag"+str(i)) #0
		df_base[check_col] = df_base[check_col].apply(resultnormalx, axis=1)

마지막으로 Y 값은 Price 대비 변화량으로 처리를 합니다. 그래서 현재 Price로 나누어 주면 됩니다.

df_base['Y'] = df_base[['Y','Price']].apply(resulty, axis=1)

def resulty(y):
	# y price
	return y[1]/y[0]

결론적으로 아래와 같이 train 데이터가 변경됩니다.


predict_simul_normal_reg.py ( 예측 결과로 시뮬레이션 )

차이점


전체코드

#

import pandas as pd
import config

BASE_PATH = "./"

def resultx(x):
	pprice = x[0]+x[0] * config.P_SELLP/100.0# Price  팔아야 하는 목표값을 계산한다
	if pprice <= x[2] : return 1 # 목표값보다 예상값(PreY)이 커지면 구매표시를 한다.
	return 0

def result_changeY(y):
	# y price
	return y[0]*y[1]

test_df = pd.read_csv(BASE_PATH+'predict_xgboost_regtest_prey.csv', header=0, encoding='utf8')
test_df['Y'] = test_df[['Price','Y']].apply(result_changeY, axis=1)
test_df['PreY'] = test_df[['Price','PreY']].apply(result_changeY, axis=1)
test_df['Buy'] = test_df[['Price','Y','PreY']].apply(resultx, axis=1)

test_df.to_csv("buy.csv", encoding='utf-8', index=False)

# 실제 구매했을때 얼마나 손익이 날 수 있는지 시뮬레이션을 하도록한다
rowcount = test_df.shape[0]

havetobuy = False
price = 0
buylist=[]
total = 0
for i in range(rowcount-1,0-1,-1):
	LPrice = test_df.iloc[i]['LPrice']
	HPrice = test_df.iloc[i]['HPrice']
	Time = test_df.iloc[i]['Time']
	# 이미 보유 하고 있으면
	if len(buylist) > 0 :
		# 팔 항목 있는지 살펴본다.
		removelist = []
		for buiedprice in buylist:
			pprice = buiedprice+buiedprice * config.P_SELLP/100.0
			mprice = buiedprice-buiedprice * config.M_SELLP/100.0
			if LPrice < mprice :
				total=total-config.M_SELLP
				removelist.append(buiedprice)
				print(Time,"sell",i,buiedprice,"-",LPrice)
			elif HPrice > pprice :
				total=total+config.P_SELLP
				removelist.append(buiedprice)
				print(Time,"sell",i,buiedprice,"+",HPrice)
		
		if len(removelist)>0:
			for item in removelist:
				buylist.remove(item)
				
	if havetobuy == True and len(buylist) < config.MONEY_TOTAL:
		LPrice = test_df.iloc[i]['LPrice']
		if price >= LPrice :
			buylist.append(price)
			pprice = price+price * config.P_SELLP/100.0
			mprice = price-price * config.M_SELLP/100.0
			print(Time,"havetobuy",i,price,"buy",mprice,pprice)
		else:
			print(Time,"havetobuy",i,price,LPrice,"dont buy")
	
	if test_df.iloc[i]['Buy'] > 0.5 :
		havetobuy = True
		price = test_df.iloc[i]['Price']
	else:
		havetobuy = False
		price = test_df.iloc[i]['Price']
	
print(total)
	
	


이전대비 차이나는 곳은 2곳입니다. 예측된 결과가 Price대비 얼마나 변동되어있는지 기록되므로 Y,PreY 값을 원복하는 코드를 넣었습니다.

해당 코드는 아래 코드 입니다. 이전에 나누어 주었으므로 곱해주면 됩니다.

test_df['Y'] = test_df[['Price','Y']].apply(result_changeY, axis=1)
test_df['PreY'] = test_df[['Price','PreY']].apply(result_changeY, axis=1)

def result_changeY(y):
	# y price
	return y[0]*y[1]

config.py 소스

 # 예측할때 과거 몇일을 가지고 학습 데이터를 만들지 결정하는 값
#price(N-0),vol(N-0),price(N-1),vol(N-1),price(N-2),vol(N-2),price(N-3),vol(N-3)...N-M
#가격(N-0) 거래량(N-0)|가격(N-1) 거래량(N-1)|가격(N-2) 거래량(N-2)|가격(N-3) 거래량(N-3)...N-M

Y_O_POSTDAYES=5# 예측할때 앞으로의 몇일을 가지고 결과Y를 만들지 결정하는 값
#Y(N) : price(N+1),price(N+1),price(N+2),price(N+3)...N+O  이값중 price(N) 보다 목표% 보다 커지면 Y(N):1(구매해야함) 이 된다.

#이익이 났을때 판매 가격 %
P_SELLP=3
#손해가 났을태 판매 가격 %
M_SELLP=3

COUNT_TEST_DATA = 600 # 테스트 검증를 위한 데이터 갯수

# simulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

MONEY_TOTAL = 2

기준을 3%로 잡고 시험해봤습니다.

결과

C:\Users\USER\Documents\python\stock>python predict_simul_normal_reg.py
20180525.0 havetobuy 545 51400.0 buy 49858.0 52942.0
20180528.0 sell 544 51400.0 + 53000.0
20180705.0 havetobuy 518 46250.0 buy 44862.5 47637.5
20180706.0 sell 517 46250.0 - 44650.0
20180813.0 havetobuy 491 45400.0 buy 44038.0 46762.0
20180816.0 sell 489 45400.0 - 43700.0
20190625.0 havetobuy 281 45500.0 buy 44135.0 46865.0
20190628.0 sell 278 45500.0 + 47000.0
20190829.0 havetobuy 235 44150.0 buy 42825.5 45474.5
20190905.0 sell 230 44150.0 + 46100.0
20200424.0 havetobuy 74 49850.0 buy 48354.5 51345.5
20200511.0 sell 66 49850.0 - 48300.0
20200605.0 havetobuy 47 54600.0 buy 52962.0 56238.0
20200608.0 sell 46 54600.0 + 56500.0
20200709.0 havetobuy 23 53000.0 buy 51410.0 54590.0
20200714.0 havetobuy 20 53400.0 buy 51798.0 55002.0
20200715.0 sell 19 53000.0 + 55000.0
20200716.0 havetobuy 18 54700.0 buy 53059.0 56341.0
20200721.0 sell 15 53400.0 + 55400.0
20200728.0 sell 10 54700.0 + 58800.0
20200805.0 havetobuy 4 57300.0 buy 55581.0 59019.0
20200811.0 sell 0 57300.0 + 59500.0
15

15%의 이익률이 나옵니다.


정규화를 안하고 config 같은 3%조건에서 다시 시험해보았습니다.

결과는....

C:\Users\USER\Documents\python\stock>python predict_simul_reg.py
20180515.0 havetobuy 552 50100.0 buy 48597.0 51603.0
20180516.0 havetobuy 551 49200.0 buy 47724.0 50676.0
20180523.0 sell 547 50100.0 + 52000.0
20180523.0 sell 547 49200.0 + 52000.0
20180629.0 havetobuy 522 46800.0 buy 45396.0 48204.0
20180702.0 havetobuy 521 46650.0 buy 45250.5 48049.5
20180706.0 sell 517 46800.0 - 44650.0
20180706.0 sell 517 46650.0 - 44650.0
20180709.0 havetobuy 516 44900.0 45200.0 dont buy
20180710.0 havetobuy 515 45600.0 46100.0 dont buy
20180713.0 havetobuy 512 45500.0 45750.0 dont buy
20180718.0 havetobuy 509 45850.0 46450.0 dont buy
20180803.0 havetobuy 497 45550.0 buy 44183.5 46916.5
20180806.0 havetobuy 496 45750.0 buy 44377.5 47122.5
20180808.0 sell 494 45550.0 + 47000.0
20180813.0 havetobuy 491 45400.0 buy 44038.0 46762.0
20180816.0 sell 489 45750.0 - 43700.0
20180816.0 sell 489 45400.0 - 43700.0
20180816.0 havetobuy 489 45150.0 buy 43795.5 46504.5
20180817.0 havetobuy 488 44250.0 buy 42922.5 45577.5
20180820.0 sell 487 45150.0 - 43500.0
20180820.0 havetobuy 487 44100.0 buy 42777.0 45423.0
20180822.0 sell 485 44250.0 + 46200.0
20180822.0 sell 485 44100.0 + 46200.0
20180906.0 havetobuy 474 46600.0 buy 45202.0 47998.0
20180907.0 sell 473 46600.0 - 44400.0
20180907.0 havetobuy 473 46100.0 buy 44717.0 47483.0
20180910.0 havetobuy 472 44900.0 45000.0 dont buy
20180911.0 havetobuy 471 45500.0 buy 44135.0 46865.0
20180912.0 sell 470 46100.0 - 44500.0
20180912.0 havetobuy 470 45050.0 buy 43698.5 46401.5
20180913.0 sell 469 45500.0 - 44000.0
20180913.0 havetobuy 469 44550.0 buy 43213.5 45886.5
20180918.0 sell 466 44550.0 + 45900.0
20180920.0 sell 464 45050.0 + 47600.0
20181005.0 havetobuy 457 44700.0 buy 43359.0 46041.0
20181008.0 havetobuy 456 44700.0 buy 43359.0 46041.0
20181011.0 sell 454 44700.0 - 43100.0
20181011.0 sell 454 44700.0 - 43100.0
20181011.0 havetobuy 454 45300.0 buy 43941.0 46659.0
20181012.0 sell 453 45300.0 - 43200.0
20181017.0 havetobuy 450 43600.0 44000.0 dont buy
20181026.0 havetobuy 443 41000.0 buy 39770.0 42230.0
20181029.0 havetobuy 442 41000.0 buy 39770.0 42230.0
20181030.0 sell 441 41000.0 + 43000.0
20181030.0 sell 441 41000.0 + 43000.0
20181204.0 havetobuy 416 43250.0 buy 41952.5 44547.5
20181205.0 sell 415 43250.0 - 40850.0
20181207.0 havetobuy 413 40500.0 40850.0 dont buy
20181211.0 havetobuy 411 40200.0 buy 38994.0 41406.0
20181212.0 havetobuy 410 40250.0 buy 39042.5 41457.5
20181214.0 sell 408 40200.0 - 38700.0
20181214.0 sell 408 40250.0 - 38700.0
20181214.0 havetobuy 408 40000.0 buy 38800.0 41200.0
20181217.0 sell 407 40000.0 - 38650.0
20181217.0 havetobuy 407 38950.0 buy 37781.5 40118.5
20190103.0 sell 397 38950.0 - 37450.0
20190107.0 havetobuy 395 37450.0 37800.0 dont buy
20190115.0 havetobuy 389 40050.0 buy 38848.5 41251.5
20190116.0 sell 388 40050.0 + 41450.0
20190228.0 havetobuy 360 46750.0 buy 45347.5 48152.5
20190304.0 sell 359 46750.0 - 44800.0
20190304.0 havetobuy 359 45100.0 buy 43747.0 46453.0
20190305.0 havetobuy 358 44850.0 buy 43504.5 46195.5
20190306.0 sell 357 45100.0 - 43700.0
20190306.0 havetobuy 357 44250.0 buy 42922.5 45577.5
20190307.0 sell 356 44850.0 - 43400.0
20190307.0 havetobuy 356 44000.0 buy 42680.0 45320.0
20190321.0 sell 346 44250.0 + 46250.0
20190321.0 sell 346 44000.0 + 46250.0
20190419.0 havetobuy 325 45600.0 buy 44232.0 46968.0
20190422.0 havetobuy 324 45300.0 buy 43941.0 46659.0
20190424.0 sell 322 45600.0 - 44150.0
20190424.0 havetobuy 322 45200.0 buy 43844.0 46556.0
20190426.0 sell 320 45300.0 - 43800.0
20190426.0 sell 320 45200.0 - 43800.0
20190509.0 havetobuy 313 44250.0 buy 42922.5 45577.5
20190510.0 sell 312 44250.0 - 42450.0
20190523.0 havetobuy 303 43500.0 buy 42195.0 44805.0
20190524.0 havetobuy 302 43850.0 buy 42534.5 45165.5
20190527.0 sell 301 43850.0 - 42350.0
20190528.0 sell 300 43500.0 - 42150.0
20190605.0 havetobuy 294 43450.0 43700.0 dont buy
20190709.0 havetobuy 271 44400.0 44700.0 dont buy
20190801.0 havetobuy 254 45350.0 buy 43989.5 46710.5
20190802.0 havetobuy 253 45200.0 buy 43844.0 46556.0
20190805.0 sell 252 45350.0 - 43600.0
20190805.0 sell 252 45200.0 - 43600.0
20190805.0 havetobuy 252 44950.0 buy 43601.5 46298.5
20190806.0 sell 251 44950.0 - 42500.0
20190806.0 havetobuy 251 43950.0 buy 42631.5 45268.5
20190807.0 havetobuy 250 43500.0 buy 42195.0 44805.0
20190902.0 sell 233 43500.0 + 44850.0
20190904.0 havetobuy 231 43250.0 buy 41952.5 44547.5
20190905.0 sell 230 43950.0 + 46100.0
20190905.0 sell 230 43250.0 + 46100.0
20190926.0 havetobuy 217 48900.0 buy 47433.0 50367.0
20190930.0 havetobuy 215 48400.0 buy 46948.0 49852.0
20191004.0 sell 212 48900.0 - 47350.0
20191014.0 sell 207 48400.0 + 50300.0
20191115.0 havetobuy 183 52800.0 buy 51216.0 54384.0
20191121.0 sell 179 52800.0 - 50600.0
20191121.0 havetobuy 179 52000.0 buy 50440.0 53560.0
20191122.0 havetobuy 178 51000.0 buy 49470.0 52530.0
20191125.0 sell 177 51000.0 + 52600.0
20191125.0 havetobuy 177 51600.0 51700.0 dont buy
20191129.0 sell 173 52000.0 - 50200.0
20191202.0 havetobuy 172 50300.0 50400.0 dont buy
20191203.0 havetobuy 171 50400.0 buy 48888.0 51912.0
20191205.0 havetobuy 169 49450.0 49500.0 dont buy
20191206.0 havetobuy 168 49500.0 49950.0 dont buy
20191211.0 sell 165 50400.0 + 52200.0
20200319.0 havetobuy 99 45600.0 buy 44232.0 46968.0
20200320.0 sell 98 45600.0 - 43550.0
20200414.0 havetobuy 81 48300.0 buy 46851.0 49749.0
20200417.0 sell 79 48300.0 + 52000.0
20200710.0 havetobuy 22 52800.0 buy 51216.0 54384.0
20200713.0 havetobuy 21 52700.0 53100.0 dont buy
20200715.0 sell 19 52800.0 + 55000.0
-36

-36% 엄청난 적자네요



댓글 없음:

댓글 쓰기