Skip to content

Commit 871a693

Browse files
Merge pull request #1 from pythonlessons/develop
Develop
2 parents 4c42e09 + 22f43fe commit 871a693

17 files changed

+851
-120
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## [0.4.0] - 2024-01-02
2+
### Added:
3+
- Created `indicators` file, where I added `BolingerBands`, `RSI`, `PSAR`, `SMA` indicators
4+
- Added `SharpeRatio` and `MaxDrawdown` metrics to `metrics`
5+
- Included indicators handling into `data_feeder.PdDataFeeder` object
6+
- Included indicators handling into `state.State` object
7+
8+
### Changed:
9+
- Changed `finrock` package dependency from `0.0.4` to `0.4.1`
10+
- Refactored `render.PygameRender` object to handle indicators rendering (getting very messy)
11+
- Updated `scalers.MinMaxScaler` to handle indicators scaling
12+
- Updated `trading_env.TradingEnv` to raise an error with `np.nan` data and skip `None` states
13+
14+
115
## [0.3.0] - 2023-12-05
216
### Added:
317
- Added `DifferentActions` and `AccountValue` as metrics. Metrics are the main way to evaluate the performance of the agent.

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Reinforcement Learning package for Finance
33

44
# Environment Structure:
55
<p align="center">
6-
<img src="Tutorials\Documents\03_FinRock.jpg">
6+
<img src="Tutorials\Documents\04_FinRock.jpg">
77
</p>
88

99
### Install requirements:
@@ -30,10 +30,21 @@ experiments/testing_ppo_sinusoid.py
3030

3131
### Environment Render:
3232
<p align="center">
33-
<img src="Tutorials\Documents\03_FinRock_render.png">
33+
<img src="Tutorials\Documents\04_FinRock_render.png">
3434
</p>
3535

3636
## Links to YouTube videos:
3737
- [Introduction to FinRock package](https://youtu.be/xU_YJB7vilA)
3838
- [Complete Trading Simulation Backbone](https://youtu.be/1z5geob8Yho)
39-
- [Training RL agent on Sinusoid data](https://youtu.be/JkA4BuYvWyE)
39+
- [Training RL agent on Sinusoid data](https://youtu.be/JkA4BuYvWyE)
40+
- [Included metrics and indicators into environment](https://youtu.be/bGpBEnKzIdo)
41+
42+
# TODO:
43+
- [ ] Train model on `continuous` actions (control allocation percentage)
44+
- [ ] Add more indicators
45+
- [ ] Add more metrics
46+
- [ ] Add more reward functions
47+
- [ ] Add more scalers
48+
- [ ] Train RL agent on real data
49+
- [ ] Add more RL algorithms
50+
- [ ] Refactor rendering, maybe move to browser?
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Complete Trading Simulation Backbone
2+
3+
### Environment Structure:
4+
<p align="center">
5+
<img src="Documents\04_FinRock.jpg">
6+
</p>
7+
8+
### Link to YouTube video:
9+
https://youtu.be/bGpBEnKzIdo
10+
11+
### Link to tutorial code:
12+
https://github.com/pythonlessons/FinRock/tree/0.4.0
13+
14+
### Download tutorial code:
15+
https://github.com/pythonlessons/FinRock/archive/refs/tags/0.4.0.zip
16+
17+
18+
### Install requirements:
19+
```
20+
pip install -r requirements.txt
21+
pip install pygame
22+
pip install .
23+
```
24+
25+
### Create sinusoid data:
26+
```
27+
python bin/create_sinusoid_data.py
28+
```
29+
30+
### Train RL (PPO) agent on discrete actions:
31+
```
32+
experiments/training_ppo_sinusoid.py
33+
```
34+
35+
### Test trained agent (Change path to the saved model):
36+
```
37+
experiments/testing_ppo_sinusoid.py
38+
```
39+
40+
### Environment Render:
41+
<p align="center">
42+
<img src="Documents\04_FinRock_render.png">
43+
</p>

Tutorials/Documents/04_FinRock.jpg

84.7 KB
Loading
249 KB
Loading

experiments/playing_random_sinusoid.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66
from finrock.render import PygameRender
77
from finrock.scalers import MinMaxScaler
88
from finrock.reward import simpleReward
9+
from finrock.indicators import BolingerBands, SMA, RSI, PSAR
910

1011
df = pd.read_csv('Datasets/random_sinusoid.csv')
1112

12-
pd_data_feeder = PdDataFeeder(df)
13-
13+
pd_data_feeder = PdDataFeeder(
14+
df = df,
15+
indicators = [
16+
BolingerBands(data=df, period=20, std=2),
17+
RSI(data=df, period=14),
18+
PSAR(data=df),
19+
SMA(data=df, period=7),
20+
SMA(data=df, period=25),
21+
SMA(data=df, period=99),
22+
]
23+
)
1424

1525
env = TradingEnv(
1626
data_feeder = pd_data_feeder,
@@ -21,10 +31,10 @@
2131
reward_function = simpleReward
2232
)
2333
action_space = env.action_space
34+
input_shape = env.observation_space.shape
2435

2536
pygameRender = PygameRender(frame_rate=60)
2637

27-
2838
state, info = env.reset()
2939
pygameRender.render(info)
3040
rewards = 0.0

experiments/testing_ppo_sinusoid.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@
1010
from finrock.render import PygameRender
1111
from finrock.scalers import MinMaxScaler
1212
from finrock.reward import simpleReward
13-
from finrock.metrics import DifferentActions, AccountValue
13+
from finrock.metrics import DifferentActions, AccountValue, MaxDrawdown, SharpeRatio
14+
from finrock.indicators import BolingerBands, RSI, PSAR, SMA
1415

1516

1617
df = pd.read_csv('Datasets/random_sinusoid.csv')
1718
df = df[-1000:]
1819

19-
pd_data_feeder = PdDataFeeder(df)
20+
pd_data_feeder = PdDataFeeder(
21+
df,
22+
indicators = [
23+
BolingerBands(data=df, period=20, std=2),
24+
RSI(data=df, period=14),
25+
PSAR(data=df),
26+
SMA(data=df, period=7),
27+
SMA(data=df, period=25),
28+
SMA(data=df, period=99),
29+
]
30+
)
2031

2132
env = TradingEnv(
2233
data_feeder = pd_data_feeder,
@@ -28,14 +39,16 @@
2839
metrics = [
2940
DifferentActions(),
3041
AccountValue(),
42+
MaxDrawdown(),
43+
SharpeRatio(),
3144
]
3245
)
3346

3447
action_space = env.action_space
3548
input_shape = env.observation_space.shape
3649
pygameRender = PygameRender(frame_rate=120)
3750

38-
agent = tf.keras.models.load_model('runs/1701698276/ppo_sinusoid_actor.h5')
51+
agent = tf.keras.models.load_model('runs/1702982487/ppo_sinusoid_actor.h5')
3952

4053
state, info = env.reset()
4154
pygameRender.render(info)
@@ -51,7 +64,9 @@
5164
pygameRender.render(info)
5265

5366
if terminated or truncated:
54-
print(rewards, info["metrics"]['account_value'])
67+
print(rewards)
68+
for metric, value in info['metrics'].items():
69+
print(metric, value)
5570
state, info = env.reset()
5671
rewards = 0.0
5772
pygameRender.reset()

experiments/training_ppo_sinusoid.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from finrock.trading_env import TradingEnv
1212
from finrock.scalers import MinMaxScaler
1313
from finrock.reward import simpleReward
14-
from finrock.metrics import DifferentActions, AccountValue
14+
from finrock.metrics import DifferentActions, AccountValue, MaxDrawdown, SharpeRatio
15+
from finrock.indicators import BolingerBands, RSI, PSAR, SMA
1516

1617
from rockrl.utils.misc import MeanAverage
1718
from rockrl.utils.memory import Memory
@@ -20,8 +21,17 @@
2021
df = pd.read_csv('Datasets/random_sinusoid.csv')
2122
df = df[:-1000] # leave 1000 for testing
2223

23-
pd_data_feeder = PdDataFeeder(df)
24-
24+
pd_data_feeder = PdDataFeeder(
25+
df,
26+
indicators = [
27+
BolingerBands(data=df, period=20, std=2),
28+
RSI(data=df, period=14),
29+
PSAR(data=df),
30+
SMA(data=df, period=7),
31+
SMA(data=df, period=25),
32+
SMA(data=df, period=99),
33+
]
34+
)
2535

2636
env = TradingEnv(
2737
data_feeder = pd_data_feeder,
@@ -33,6 +43,8 @@
3343
metrics = [
3444
DifferentActions(),
3545
AccountValue(),
46+
MaxDrawdown(),
47+
SharpeRatio(),
3648
]
3749
)
3850

@@ -63,15 +75,14 @@
6375
agent = PPOAgent(
6476
actor = actor_model,
6577
critic = critic_model,
66-
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0002),
78+
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
6779
batch_size=512,
6880
lamda=0.95,
6981
kl_coeff=0.5,
7082
c2=0.01,
7183
writer_comment='ppo_sinusoid',
7284
)
7385

74-
7586
memory = Memory()
7687
meanAverage = MeanAverage(best_mean_score_episode=1000)
7788
state, info = env.reset()
@@ -98,6 +109,5 @@
98109
memory.reset()
99110
state, info = env.reset()
100111

101-
102112
if agent.epoch >= 10000:
103113
break

finrock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.0"
1+
__version__ = "0.4.0"

finrock/data_feeder.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import pandas as pd
2-
32
from finrock.state import State
3+
from finrock.indicators import Indicator
4+
45

56
class PdDataFeeder:
67
def __init__(
78
self,
89
df: pd.DataFrame,
10+
indicators: list = [],
911
min: float = None,
1012
max: float = None
1113
) -> None:
1214
self._df = df
1315
self._min = min
1416
self._max = max
17+
self._indicators = indicators
18+
self._cache = {}
1519

1620
assert isinstance(self._df, pd.DataFrame) == True, "df must be a pandas.DataFrame"
1721
assert 'timestamp' in self._df.columns, "df must have 'timestamp' column"
@@ -20,6 +24,9 @@ def __init__(
2024
assert 'low' in self._df.columns, "df must have 'low' column"
2125
assert 'close' in self._df.columns, "df must have 'close' column"
2226

27+
assert isinstance(self._indicators, list) == True, "indicators must be an iterable"
28+
assert all(isinstance(indicator, Indicator) for indicator in self._indicators) == True, "indicators must be a list of Indicator objects"
29+
2330
@property
2431
def min(self) -> float:
2532
return self._min or self._df['low'].min()
@@ -32,16 +39,30 @@ def __len__(self) -> int:
3239
return len(self._df)
3340

3441
def __getitem__(self, idx: int, args=None) -> State:
35-
data = self._df.iloc[idx]
42+
# Use cache to speed up training
43+
if idx in self._cache:
44+
return self._cache[idx]
45+
46+
indicators = []
47+
for indicator in self._indicators:
48+
results = indicator(idx)
49+
if results is None:
50+
self._cache[idx] = None
51+
return None
52+
53+
indicators.append(results)
3654

55+
data = self._df.iloc[idx]
3756
state = State(
3857
timestamp=data['timestamp'],
3958
open=data['open'],
4059
high=data['high'],
4160
low=data['low'],
4261
close=data['close'],
43-
volume=data.get('volume', 0.0)
62+
volume=data.get('volume', 0.0),
63+
indicators=indicators
4464
)
65+
self._cache[idx] = state
4566

4667
return state
4768

0 commit comments

Comments
 (0)