Skip to content

Commit 7a8ad9c

Browse files
authored
Add files via upload
1 parent 5a0b754 commit 7a8ad9c

File tree

12 files changed

+1691
-0
lines changed

12 files changed

+1691
-0
lines changed

.gitattributes

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Basic .gitattributes for a python repo.
2+
3+
# Source files
4+
# ============
5+
*.pxdtext
6+
*.py text
7+
*.py3 text
8+
*.pyw text
9+
*.pyx text
10+
11+
# Binary files
12+
# ============
13+
*.dbbinary
14+
*.p binary
15+
*.pkl binary
16+
*.pyc binary
17+
*.pydbinary
18+
*.pyo binary
19+
*.ipynb linguist-language=Python
20+
# Note: .db, .p, and .pkl files are associated
21+
# with the python modules ``pickle``, ``dbm.*``,
22+
# ``shelve``, ``marshal``, ``anydbm``, & ``bsddb``
23+
# (among others).

TraderEnv.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import process_data
2+
import pandas as pd
3+
import random
4+
import gym
5+
from gym import spaces
6+
from gym.utils import seeding
7+
import numpy as np
8+
import math
9+
from pathlib import Path
10+
11+
# position constant
12+
LONG = 0
13+
SHORT = 1
14+
FLAT = 2
15+
16+
# action constant
17+
BUY = 0
18+
SELL = 1
19+
HOLD = 2
20+
21+
class OhlcvEnv(gym.Env):
22+
23+
def __init__(self, window_size, path, show_trade=True):
24+
self.show_trade = show_trade
25+
self.path = path
26+
self.actions = ["LONG", "SHORT", "FLAT"]
27+
self.fee = 0.0005
28+
self.seed()
29+
self.file_list = []
30+
# load_csv
31+
self.load_from_csv()
32+
33+
# n_features
34+
self.window_size = window_size
35+
self.n_features = self.df.shape[1]
36+
self.shape = (self.window_size, self.n_features+4)
37+
38+
# defines action space
39+
self.action_space = spaces.Discrete(len(self.actions))
40+
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=self.shape, dtype=np.float32)
41+
42+
def load_from_csv(self):
43+
if(len(self.file_list) == 0):
44+
self.file_list = [x.name for x in Path(self.path).iterdir() if x.is_file()]
45+
self.file_list.sort()
46+
self.rand_episode = self.file_list.pop()
47+
raw_df= pd.read_csv(self.path + self.rand_episode)
48+
extractor = process_data.FeatureExtractor(raw_df)
49+
self.df = extractor.add_bar_features() # bar features o, h, l, c ---> C(4,2) = 4*3/2*1 = 6 features
50+
51+
## selected manual fetuares
52+
feature_list = [
53+
'bar_hc',
54+
'bar_ho',
55+
'bar_hl',
56+
'bar_cl',
57+
'bar_ol',
58+
'bar_co', 'close']
59+
self.df.dropna(inplace=True) # drops Nan rows
60+
self.closingPrices = self.df['close'].values
61+
self.df = self.df[feature_list].values
62+
63+
def render(self, mode='human', verbose=False):
64+
return None
65+
66+
def seed(self, seed=None):
67+
self.np_random, seed = seeding.np_random(seed)
68+
return [seed]
69+
70+
def step(self, action):
71+
72+
if self.done:
73+
return self.state, self.reward, self.done, {}
74+
self.reward = 0
75+
76+
# action comes from the agent
77+
# 0 buy, 1 sell, 2 hold
78+
# single position can be opened per trade
79+
# valid action sequence would be
80+
# LONG : buy - hold - hold - sell
81+
# SHORT : sell - hold - hold - buy
82+
# invalid action sequence is just considered hold
83+
# (e.g.) "buy - buy" would be considred "buy - hold"
84+
self.action = HOLD # hold
85+
if action == BUY: # buy
86+
if self.position == FLAT: # if previous position was flat
87+
self.position = LONG # update position to long
88+
self.action = BUY # record action as buy
89+
self.entry_price = self.closingPrice # maintain entry price
90+
elif self.position == SHORT: # if previous position was short
91+
self.position = FLAT # update position to flat
92+
self.action = BUY # record action as buy
93+
self.exit_price = self.closingPrice
94+
self.reward += ((self.entry_price - self.exit_price)/self.exit_price + 1)*(1-self.fee)**2 - 1 # calculate reward
95+
self.krw_balance = self.krw_balance * (1.0 + self.reward) # evaluate cumulative return in krw-won
96+
self.entry_price = 0 # clear entry price
97+
self.n_short += 1 # record number of short
98+
elif action == 1: # vice versa for short trade
99+
if self.position == FLAT:
100+
self.position = SHORT
101+
self.action = 1
102+
self.entry_price = self.closingPrice
103+
elif self.position == LONG:
104+
self.position = FLAT
105+
self.action = 1
106+
self.exit_price = self.closingPrice
107+
self.reward += ((self.exit_price - self.entry_price)/self.entry_price + 1)*(1-self.fee)**2 - 1
108+
self.krw_balance = self.krw_balance * (1.0 + self.reward)
109+
self.entry_price = 0
110+
self.n_long += 1
111+
112+
# [coin + krw_won] total value evaluated in krw won
113+
if(self.position == LONG):
114+
temp_reward = ((self.closingPrice - self.entry_price)/self.entry_price + 1)*(1-self.fee)**2 - 1
115+
new_portfolio = self.krw_balance * (1.0 + temp_reward)
116+
elif(self.position == SHORT):
117+
temp_reward = ((self.entry_price - self.closingPrice)/self.closingPrice + 1)*(1-self.fee)**2 - 1
118+
new_portfolio = self.krw_balance * (1.0 + temp_reward)
119+
else:
120+
temp_reward = 0
121+
new_portfolio = self.krw_balance
122+
123+
self.portfolio = new_portfolio
124+
self.current_tick += 1
125+
if(self.show_trade and self.current_tick%100 == 0):
126+
print("Tick: {0}/ Portfolio (USD): {1}".format(self.current_tick, self.portfolio))
127+
print("Long: {0}/ Short: {1}".format(self.n_long, self.n_short))
128+
self.history.append((self.action, self.current_tick, self.closingPrice, self.portfolio, self.reward))
129+
self.updateState()
130+
if (self.current_tick > (self.df.shape[0]) - self.window_size-1):
131+
self.done = True
132+
self.reward = self.get_profit() # return reward at end of the game
133+
return self.state, self.reward, self.done, {'portfolio':np.array([self.portfolio]),
134+
"history":self.history,
135+
"n_trades":{'long':self.n_long, 'short':self.n_short}}
136+
137+
def get_profit(self):
138+
if(self.position == LONG):
139+
profit = ((self.closingPrice - self.entry_price)/self.entry_price + 1)*(1-self.fee)**2 - 1
140+
elif(self.position == SHORT):
141+
profit = ((self.entry_price - self.closingPrice)/self.closingPrice + 1)*(1-self.fee)**2 - 1
142+
else:
143+
profit = 0
144+
return profit
145+
146+
def reset(self):
147+
# self.current_tick = random.randint(0, self.df.shape[0]-1000)
148+
self.current_tick = 0
149+
print("start episode ... {0} at {1}" .format(self.rand_episode, self.current_tick))
150+
151+
# positions
152+
self.n_long = 0
153+
self.n_short = 0
154+
155+
# clear internal variables
156+
self.history = [] # keep buy, sell, hold action history
157+
self.krw_balance = 1000 # initial balance, u can change it to whatever u like
158+
self.portfolio = float(self.krw_balance) # (coin * current_price + current_krw_balance) == portfolio
159+
self.profit = 0
160+
161+
self.action = HOLD
162+
self.position = FLAT
163+
self.done = False
164+
165+
self.updateState() # returns observed_features + opened position(LONG/SHORT/FLAT) + profit_earned(during opened position)
166+
return self.state
167+
168+
169+
def updateState(self):
170+
def one_hot_encode(x, n_classes):
171+
return np.eye(n_classes)[x]
172+
self.closingPrice = float(self.closingPrices[self.current_tick])
173+
prev_position = self.position
174+
one_hot_position = one_hot_encode(prev_position,3)
175+
profit = self.get_profit()
176+
# append two
177+
self.state = np.concatenate((self.df[self.current_tick], one_hot_position, [profit]))
178+
return self.state

deep_dqn_rl_trader.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import numpy as np
2+
3+
# import keras
4+
from keras.models import Sequential
5+
from keras.layers import Dense, Activation, Flatten, CuDNNLSTM,LSTM
6+
from keras.optimizers import Adam
7+
8+
# keras-rl agent
9+
from rl.agents.dqn import DQNAgent
10+
from rl.policy import BoltzmannQPolicy, EpsGreedyQPolicy
11+
from rl.memory import SequentialMemory
12+
13+
# trader environment
14+
from TraderEnv import OhlcvEnv
15+
# custom normalizer
16+
from util import NormalizerProcessor
17+
18+
def create_model(shape, nb_actions):
19+
model = Sequential()
20+
model.add(LSTM(64, input_shape=shape, return_sequences=True))
21+
model.add(LSTM(64))
22+
model.add(Dense(32))
23+
model.add(Activation('relu'))
24+
model.add(Dense(nb_actions, activation='linear'))
25+
return model
26+
27+
def main():
28+
# OPTIONS
29+
ENV_NAME = 'OHLCV-v0'
30+
TIME_STEP = 30
31+
32+
# Get the environment and extract the number of actions.
33+
PATH_TRAIN = "./data/train/"
34+
PATH_TEST = "./data/test/"
35+
env = OhlcvEnv(TIME_STEP, path=PATH_TRAIN)
36+
env_test = OhlcvEnv(TIME_STEP, path=PATH_TEST)
37+
38+
# random seed
39+
np.random.seed(456)
40+
env.seed(562)
41+
42+
nb_actions = env.action_space.n
43+
model = create_model(shape=env.shape, nb_actions=nb_actions)
44+
print(model.summary())
45+
46+
# Finally, we configure and compile our agent. You can use every built-in Keras optimizer and even the metrics!
47+
memory = SequentialMemory(limit=50000, window_length=TIME_STEP)
48+
# policy = BoltzmannQPolicy()
49+
policy = EpsGreedyQPolicy()
50+
# enable the dueling network
51+
# you can specify the dueling_type to one of {'avg','max','naive'}-+
52+
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=200,
53+
enable_dueling_network=True, dueling_type='avg', target_model_update=1e-2, policy=policy,
54+
processor=NormalizerProcessor())
55+
dqn.compile(Adam(lr=1e-3), metrics=['mae'])
56+
57+
while True:
58+
# train
59+
dqn.load_weights('./model/duel__LSTM_dqn_OHLCV-v0_weights_1044LS_0_19_0.04339342066576202.h5f')
60+
dqn.fit(env, nb_steps=5500, nb_max_episode_steps=10000, visualize=True, verbose=2)
61+
# validate
62+
info = dqn.test(env_test, nb_episodes=1, visualize=True)
63+
n_long, n_short, total_reward, portfolio = info['n_trades']['long'], info['n_trades']['short'], info[
64+
'total_reward'], int(info['portfolio'])
65+
np.array([info]).dump(
66+
'./info/duel_dqn_{0}_weights_{1}LS_{2}_{3}_{4}.info'.format(ENV_NAME, portfolio, n_long, n_short,
67+
total_reward))
68+
dqn.save_weights(
69+
'./model/duel__LSTM_dqn_{0}_weights_{1}LS_{2}_{3}_{4}.h5f'.format(ENV_NAME, portfolio, n_long, n_short, total_reward),
70+
overwrite=True)
71+
72+
if __name__ == '__main__':
73+
main()

load_test.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import numpy as np
2+
3+
# import keras
4+
from keras.models import Sequential
5+
from keras.layers import Dense, Activation, Flatten, CuDNNLSTM, LSTM
6+
from keras.optimizers import Adam
7+
8+
# keras-rl agent
9+
from rl.agents.dqn import DQNAgent
10+
from rl.policy import BoltzmannQPolicy, EpsGreedyQPolicy
11+
from rl.memory import SequentialMemory
12+
13+
# trader environment
14+
from TraderEnv import OhlcvEnv
15+
# custom normalizer
16+
from util import NormalizerProcessor
17+
18+
19+
def main():
20+
# OPTIONS
21+
ENV_NAME = 'OHLCV-v0'
22+
TIME_STEP = 30
23+
WINDOW_LENGTH = TIME_STEP
24+
ADDITIONAL_STATE = 4
25+
26+
# Get the environment and extract the number of actions.
27+
PATH_TRAIN = "./data/train/"
28+
PATH_TEST = "./data/test/"
29+
env = OhlcvEnv(TIME_STEP, path=PATH_TRAIN)
30+
env_test = OhlcvEnv(TIME_STEP, path=PATH_TEST)
31+
32+
# random seed
33+
np.random.seed(123)
34+
env.seed(123)
35+
36+
nb_actions = env.action_space.n
37+
38+
model = Sequential()
39+
model.add(LSTM(64, input_shape=env.shape, return_sequences=True))
40+
model.add(LSTM(64))
41+
model.add(Dense(32))
42+
model.add(Activation('relu'))
43+
model.add(Dense(nb_actions, activation='linear'))
44+
print(model.summary())
45+
46+
# Finally, we configure and compile our agent. You can use every built-in Keras optimizer and even the metrics!
47+
memory = SequentialMemory(limit=50000, window_length=TIME_STEP)
48+
# policy = BoltzmannQPolicy()
49+
policy = EpsGreedyQPolicy()
50+
# enable the dueling network
51+
# you can specify the dueling_type to one of {'avg','max','naive'}
52+
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=200,
53+
enable_dueling_network=True, dueling_type='avg', target_model_update=1e-2, policy=policy,
54+
processor=NormalizerProcessor())
55+
dqn.compile(Adam(lr=1e-3), metrics=['mae'])
56+
57+
### now only test
58+
dqn.load_weights("./model/duel__LSTM_dqn_OHLCV-v0_weights_1083LS_15_22_0.08602019548898943.h5f")
59+
# validate
60+
info = dqn.test(env_test, nb_episodes=1, visualize=True)
61+
n_long, n_short, total_reward, portfolio = info['n_trades']['long'], info['n_trades']['short'], info[
62+
'total_reward'], int(info['portfolio'])
63+
np.array([info]).dump(
64+
'./model/duel_dqn_{0}_weights_{1}LS_{2}_{3}_{4}.info'.format(ENV_NAME, portfolio, n_long, n_short,
65+
total_reward))
66+
dqn.save_weights(
67+
'./info/duel_dqn_{0}_weights_{1}LS_{2}_{3}_{4}.h5f'.format(ENV_NAME, portfolio, n_long, n_short, total_reward),
68+
overwrite=True)
69+
70+
if __name__ == '__main__':
71+
main()
72+

0 commit comments

Comments
 (0)