Skip to content

Commit 0325e4b

Browse files
committed
Merge branch 'develop'
2 parents 1c51af7 + 28cc532 commit 0325e4b

File tree

13 files changed

+338
-28
lines changed

13 files changed

+338
-28
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
## [0.1.0] -
1+
## [0.2.0] - 2023-11-..
2+
### Added:
3+
- Created `reward.simpleReward` function to calculate reward based on the action and the difference between the current price and the previous price
4+
- Created `scalers.MinMaxScaler` object to transform the price data to a range between 0 and 1 and prepare it for the neural networks input
5+
- Created `state.Observations` object to hold the observations of the agent with set window size
6+
- Updated `render.PygameRender` object to render the agent's actions
7+
- Updated `state.State` to hold current state `assets`, `balance` and `allocation_percentage` on specific State
8+
9+
10+
## [0.1.0] - 2023-10-17
211
### Initial Release:
312
- created the project
413
- created code to create random sinusoidal price data

README.md

Lines changed: 11 additions & 2 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\01_FinRock.jpg">
6+
<img src="Tutorials\Documents\02_FinRock.jpg">
77
</p>
88

99
### Install requirements
@@ -20,4 +20,13 @@ python bin/create_sinusoid_data.py
2020
### Run environment:
2121
```
2222
python playing.py
23-
```
23+
```
24+
25+
### Environment Render:
26+
<p align="center">
27+
<img src="Tutorials\Documents\02_FinRock_render.png">
28+
</p>
29+
30+
## Links to YouTube videos:
31+
- [Introduction to FinRock package](https://youtu.be/xU_YJB7vilA)
32+
- [Complete Trading Simulation Backbone](https://youtu.be/1z5geob8Yho)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Complete Trading Simulation Backbone
2+
3+
### Environment Structure:
4+
<p align="center">
5+
<img src="Documents\02_FinRock.jpg">
6+
</p>
7+
8+
### Link to YouTube video:
9+
https://youtu.be/1z5geob8Yho
10+
11+
### Link to tutorial code:
12+
https://github.com/pythonlessons/FinRock/tree/0.2.0
13+
14+
### Download tutorial code:
15+
https://github.com/pythonlessons/FinRock/archive/refs/tags/0.2.0.zip
16+
17+
18+
### Install requirements:
19+
```
20+
pip install -r requirements.txt
21+
pip install pygame
22+
```
23+
24+
### Create sinusoid data:
25+
```
26+
python bin/create_sinusoid_data.py
27+
```
28+
29+
### Run environment:
30+
```
31+
python playing.py
32+
```
33+
34+
### Environment Render:
35+
<p align="center">
36+
<img src="Documents\02_FinRock_render.png">
37+
</p>

Tutorials/Documents/02_FinRock.jpg

60.2 KB
Loading
60.2 KB
Loading

finrock/__init__.py

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

finrock/data_feeder.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,35 @@
33
from finrock.state import State
44

55
class PdDataFeeder:
6-
def __init__(self, df: pd.DataFrame):
6+
def __init__(
7+
self,
8+
df: pd.DataFrame,
9+
min: float = None,
10+
max: float = None
11+
) -> None:
712
self._df = df
13+
self._min = min
14+
self._max = max
815

9-
assert isinstance(self._df, pd.DataFrame) == True
10-
assert 'timestamp' in self._df.columns
11-
assert 'open' in self._df.columns
12-
assert 'high' in self._df.columns
13-
assert 'low' in self._df.columns
14-
assert 'close' in self._df.columns
16+
assert isinstance(self._df, pd.DataFrame) == True, "df must be a pandas.DataFrame"
17+
assert 'timestamp' in self._df.columns, "df must have 'timestamp' column"
18+
assert 'open' in self._df.columns, "df must have 'open' column"
19+
assert 'high' in self._df.columns, "df must have 'high' column"
20+
assert 'low' in self._df.columns, "df must have 'low' column"
21+
assert 'close' in self._df.columns, "df must have 'close' column"
22+
23+
@property
24+
def min(self) -> float:
25+
return self._min or self._df['low'].min()
26+
27+
@property
28+
def max(self) -> float:
29+
return self._max or self._df['high'].max()
1530

1631
def __len__(self) -> int:
1732
return len(self._df)
1833

19-
def __getitem__(self, idx: int) -> State:
34+
def __getitem__(self, idx: int, args=None) -> State:
2035
data = self._df.iloc[idx]
2136

2237
state = State(

finrock/render.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class ColorTheme:
1212
down_candle = red
1313
wick = white
1414
text = white
15+
buy = green
16+
sell = red
1517

1618
class PygameRender:
1719
def __init__(
@@ -127,7 +129,33 @@ def render(self, info: dict):
127129
# Draw candlestick body
128130
self.pygame.draw.rect(canvas, candle_color, (candle_offset, candle_body_y, self.candle_width, candle_body_height))
129131

132+
# Compare with previous state to determine whether buy or sell action was taken and draw arrow
133+
index = self._states.index(state)
134+
if index > 0:
135+
last_state = self._states[index - 1]
136+
if last_state.allocation_percentage < state.allocation_percentage:
137+
# buy
138+
self.pygame.draw.polygon(canvas, self.color_theme.buy, [
139+
(candle_offset + self.candle_width // 2, candle_y_low + 10),
140+
(candle_offset + self.candle_width // 2 - 5, candle_y_low + 20),
141+
(candle_offset + self.candle_width // 2 + 5, candle_y_low + 20)
142+
])
143+
elif last_state.allocation_percentage > state.allocation_percentage:
144+
# sell
145+
self.pygame.draw.polygon(canvas, self.color_theme.sell, [
146+
(candle_offset + self.candle_width // 2, candle_y_high - 10),
147+
(candle_offset + self.candle_width // 2 - 5, candle_y_high - 20),
148+
(candle_offset + self.candle_width // 2 + 5, candle_y_high - 20)
149+
])
150+
130151
# Move to the next candle
131152
candle_offset += self.candle_width + self.candle_spacing
132153

154+
# Draw max and min ohlc values on the chart
155+
font = self.pygame.font.SysFont('Noto Sans', 15)
156+
label_y_low = font.render(str(max_low), True, self.color_theme.text)
157+
label_y_high = font.render(str(max_high), True, self.color_theme.text)
158+
canvas.blit(label_y_low, (self.candle_spacing + 5, self.chart_height + 35))
159+
canvas.blit(label_y_high, (self.candle_spacing + 5, 5))
160+
133161
return canvas

finrock/reward.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from .state import Observations
2+
3+
4+
def simpleReward(observations: Observations) -> float:
5+
6+
assert isinstance(observations, Observations) == True, "observations must be an instance of Observations"
7+
8+
last_state, next_state = observations[-2:]
9+
10+
# buy
11+
if next_state.allocation_percentage > last_state.allocation_percentage:
12+
# check whether it was good or bad to buy
13+
order_size = next_state.allocation_percentage - last_state.allocation_percentage
14+
reward = (next_state.close - last_state.close) / last_state.close * order_size
15+
16+
# sell
17+
elif next_state.allocation_percentage < last_state.allocation_percentage:
18+
# check whether it was good or bad to sell
19+
order_size = last_state.allocation_percentage - next_state.allocation_percentage
20+
reward = -1 * (next_state.close - last_state.close) / last_state.close * order_size
21+
22+
# hold
23+
else:
24+
# check whether it was good or bad to hold
25+
ratio = -1 if not last_state.allocation_percentage else last_state.allocation_percentage
26+
reward = (next_state.close - last_state.close) / last_state.close * ratio
27+
28+
return reward

finrock/scalers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import numpy as np
2+
from .state import Observations
3+
4+
class MinMaxScaler:
5+
def __init__(self, min: float, max: float):
6+
self._min = min
7+
self._max = max
8+
9+
def transform(self, observations: Observations) -> np.ndarray:
10+
11+
assert isinstance(observations, Observations) == True, "observations must be an instance of Observations"
12+
13+
transformed_data = []
14+
for state in observations:
15+
open = (state.open - self._min) / (self._max - self._min)
16+
high = (state.high - self._min) / (self._max - self._min)
17+
low = (state.low - self._min) / (self._max - self._min)
18+
close = (state.close - self._min) / (self._max - self._min)
19+
20+
transformed_data.append([open, high, low, close, state.allocation_percentage])
21+
22+
return np.array(transformed_data)
23+
24+
def __call__(self, observations) -> np.ndarray:
25+
return self.transform(observations)

0 commit comments

Comments
 (0)