Skip to content

Commit 49e9b0b

Browse files
committed
Initial Release
1 parent 1d1a6bf commit 49e9b0b

File tree

15 files changed

+466
-1
lines changed

15 files changed

+466
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__pycache__/
22
*.egg-info
33
api.json
4-
venv
4+
venv*
55
Datasets
66
runs
77
Models

.vscode/launch.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python: Current File",
9+
"type": "python",
10+
"request": "launch",
11+
"program": "${file}",
12+
"console": "integratedTerminal",
13+
"justMyCode": true
14+
}
15+
]
16+
}

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## [0.1.0] -
2+
### Initial Release:
3+
- created the project
4+
- created code to create random sinusoidal price data
5+
- created `state.State` object, which holds the state of the market
6+
- created `render.PygameRender` object, which renders the state of the market using `pygame` library
7+
- created `trading_env.TradingEnv` object, which is the environment for the agent to interact with
8+
- created `data_feeder.PdDataFeeder` object, which feeds the environment with data from a pandas dataframe

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
global-exclude *.pyc
2+
include requirements.txt

Tutorials/01_Creating_trading_environment.md

Whitespace-only changes.

bin/create_sinusoid_data.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
import numpy as np
3+
import matplotlib.pyplot as plt
4+
import pandas as pd
5+
from datetime import datetime, timedelta
6+
7+
def create_sinusoidal_df(
8+
amplitude = 2000.0, # Amplitude of the price variations
9+
frequency = 0.01, # Frequency of the price variations
10+
phase = 0.0, # Phase shift of the price variations
11+
num_samples = 10000, # Number of data samples
12+
data_shift = 20000, # shift the data up
13+
trendline_down = 5000, # shift the data down
14+
plot = False,
15+
):
16+
"""Create a dataframe with sinusoidal data"""
17+
18+
# Generate the time axis
19+
t = np.linspace(0, 2 * np.pi * frequency * num_samples, num_samples)
20+
21+
# Get the current datetime
22+
now = datetime.now()
23+
24+
# Set hours, minutes, and seconds to zero
25+
now = now.replace(hour=0, minute=0, second=0, microsecond=0)
26+
27+
# Generate timestamps for each day
28+
# timestamps = [now - timedelta(days=i) for i in range(num_samples)]
29+
timestamps = [now - timedelta(hours=i*4) for i in range(num_samples)]
30+
31+
# Convert datetime objects to strings
32+
timestamps = [timestamps.strftime('%Y-%m-%d %H:%M:%S') for timestamps in timestamps]
33+
34+
# Invert the order of the timestamps
35+
timestamps = timestamps[::-1]
36+
37+
# Generate the sinusoidal data for prices
38+
sin_data = amplitude * np.sin(t + phase)
39+
sin_data += data_shift # shift the data up
40+
41+
# shiwft sin_data up, to create trendline up
42+
sin_data -= np.linspace(0, trendline_down, num_samples)
43+
44+
# Add random noise
45+
noise = np.random.uniform(0.95, 1.05, len(t)) # generate random noise
46+
noisy_sin_data = sin_data * noise # add noise to the original data
47+
48+
price_range = np.max(noisy_sin_data) - np.min(noisy_sin_data)
49+
50+
# Generate random low and close prices
51+
low_prices = noisy_sin_data - np.random.uniform(0, 0.1 * price_range, len(noisy_sin_data))
52+
close_prices = noisy_sin_data + np.random.uniform(-0.05 * price_range, 0.05 * price_range, len(noisy_sin_data))
53+
54+
# open prices usually are close to the close prices of the previous day
55+
open_prices = np.zeros(len(close_prices))
56+
open_prices[0] = close_prices[0]
57+
open_prices[1:] = close_prices[:-1]
58+
59+
# high prices are always above open and close prices
60+
high_prices = np.maximum(open_prices, close_prices) + np.random.uniform(0, 0.1 * price_range, len(close_prices))
61+
62+
# low prices are always below open and close prices
63+
low_prices = np.minimum(open_prices, close_prices) - np.random.uniform(0, 0.1 * price_range, len(close_prices))
64+
65+
if plot:
66+
# Plot the price data
67+
plt.figure(figsize=(10, 6))
68+
plt.plot(t, noisy_sin_data, label='Noisy Sinusoidal Data')
69+
plt.plot(t, open_prices, label='Open')
70+
plt.plot(t, low_prices, label='Low')
71+
plt.plot(t, close_prices, label='Close')
72+
plt.plot(t, high_prices, label='High')
73+
plt.xlabel('Time')
74+
plt.ylabel('Price')
75+
plt.title('Fake Price Data')
76+
plt.legend()
77+
plt.grid(True)
78+
plt.show()
79+
80+
# save the data to a CSV file with matplotlib as df[['open', 'high', 'low', 'close']
81+
df = pd.DataFrame({'timestamp': timestamps, 'open': open_prices, 'high': high_prices, 'low': low_prices, 'close': close_prices})
82+
83+
return df
84+
85+
if __name__ == '__main__':
86+
# Create a dataframe with sinusoidal data
87+
df = create_sinusoidal_df()
88+
89+
# Create a directory to store the datasets
90+
os.makedirs('Datasets', exist_ok=True)
91+
92+
# Save the dataframe to a CSV file
93+
df.to_csv(f'Datasets/random_sinusoid.csv')

bin/plot_data.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pandas as pd
2+
import matplotlib.pyplot as plt
3+
4+
df = pd.read_csv('Datasets/random_sinusoid.csv', index_col='timestamp', parse_dates=True)
5+
df = df[['open', 'high', 'low', 'close']]
6+
# limit to last 1000 data points
7+
df = df[-1000:]
8+
9+
# plot the data
10+
plt.figure(figsize=(10, 6))
11+
plt.plot(df['close'])
12+
plt.xlabel('Time')
13+
plt.ylabel('Price')
14+
plt.title('random_sinusoid.csv')
15+
plt.grid(True)
16+
plt.show()

finrock/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.1.0"

finrock/data_feeder.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pandas as pd
2+
3+
from finrock.state import State
4+
5+
class PdDataFeeder:
6+
def __init__(self, df: pd.DataFrame):
7+
self._df = df
8+
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
15+
16+
def __len__(self) -> int:
17+
return len(self._df)
18+
19+
def __getitem__(self, idx: int) -> State:
20+
data = self._df.iloc[idx]
21+
22+
state = State(
23+
timestamp=data['timestamp'],
24+
open=data['open'],
25+
high=data['high'],
26+
low=data['low'],
27+
close=data['close'],
28+
volume=data.get('volume', 0.0)
29+
)
30+
31+
return state
32+
33+
def __iter__(self) -> State:
34+
""" Create a generator that iterate over the Sequence."""
35+
for index in range(len(self)):
36+
yield self[index]

finrock/render.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
from .state import State
2+
3+
class ColorTheme:
4+
black = (0, 0, 0)
5+
white = (255, 255, 255)
6+
red = (255, 10, 0)
7+
lightblue = (100, 100, 255)
8+
green = (0, 240, 0)
9+
10+
background = black
11+
up_candle = green
12+
down_candle = red
13+
wick = white
14+
text = white
15+
16+
class PygameRender:
17+
def __init__(
18+
self,
19+
window_size: int=100,
20+
screen_width: int=1024,
21+
screen_height: int=768,
22+
top_bottom_offset: int=25,
23+
candle_spacing: int=1,
24+
color_theme = ColorTheme(),
25+
frame_rate: int=30,
26+
):
27+
28+
# pygame window settings
29+
self.screen_width = screen_width
30+
self.screen_height = screen_height
31+
self.top_bottom_offset = top_bottom_offset
32+
self.candle_spacing = candle_spacing
33+
self.window_size = window_size
34+
self.color_theme = color_theme
35+
self.frame_rate = frame_rate
36+
37+
self.candle_width = self.screen_width // self.window_size - self.candle_spacing
38+
self.chart_height = self.screen_height - 2 * self.top_bottom_offset
39+
40+
self._states = []
41+
42+
try:
43+
import pygame
44+
self.pygame = pygame
45+
except ImportError:
46+
raise ImportError('Please install pygame (pip install pygame)')
47+
48+
self.pygame.init()
49+
self.pygame.display.init()
50+
self.screen_shape = (self.screen_width, self.screen_height)
51+
self.window = self.pygame.display.set_mode(self.screen_shape, self.pygame.RESIZABLE)
52+
self.clock = self.pygame.time.Clock()
53+
54+
def reset(self):
55+
self._states = []
56+
57+
def _map_price_to_window(self, price, max_low, max_high):
58+
max_range = max_high - max_low
59+
value = int(self.chart_height - (price - max_low) / max_range * self.chart_height) + self.top_bottom_offset
60+
return value
61+
62+
def _prerender(func):
63+
""" Decorator for input data validation and pygame window rendering"""
64+
def wrapper(self, info: dict, rgb_array: bool=False):
65+
self._states += info.get('states', [])
66+
67+
if not self._states or not bool(self.window._pixels_address):
68+
return
69+
70+
for event in self.pygame.event.get():
71+
if event.type == self.pygame.QUIT:
72+
self.pygame.quit()
73+
return
74+
75+
if event.type == self.pygame.VIDEORESIZE:
76+
self.screen_shape = (event.w, event.h)
77+
78+
# self.screen.fill(self.color_theme.background)
79+
canvas = func(self, info)
80+
canvas = self.pygame.transform.scale(canvas, self.screen_shape)
81+
# The following line copies our drawings from `canvas` to the visible window
82+
self.window.blit(canvas, canvas.get_rect())
83+
self.pygame.display.update()
84+
self.clock.tick(self.frame_rate)
85+
86+
if rgb_array:
87+
return self.pygame.surfarray.array3d(canvas)
88+
89+
return wrapper
90+
91+
@_prerender
92+
def render(self, info: dict):
93+
94+
canvas = self.pygame.Surface((self.screen_width , self.screen_height))
95+
canvas.fill(self.color_theme.background)
96+
97+
max_high = max([state.high for state in self._states])
98+
max_low = min([state.low for state in self._states])
99+
100+
candle_offset = self.candle_spacing
101+
102+
for state in self._states[-self.window_size:]:
103+
104+
assert isinstance(state, State) == True # check if state is a State object
105+
106+
# Calculate candle coordinates
107+
candle_y_open = self._map_price_to_window(state.open, max_low, max_high)
108+
candle_y_close = self._map_price_to_window(state.close, max_low, max_high)
109+
candle_y_high = self._map_price_to_window(state.high, max_low, max_high)
110+
candle_y_low = self._map_price_to_window(state.low, max_low, max_high)
111+
112+
# Determine candle color
113+
if state.open < state.close:
114+
# up candle
115+
candle_color = self.color_theme.up_candle
116+
candle_body_y = candle_y_close
117+
candle_body_height = candle_y_open - candle_y_close
118+
else:
119+
# down candle
120+
candle_color = self.color_theme.down_candle
121+
candle_body_y = candle_y_open
122+
candle_body_height = candle_y_close - candle_y_open
123+
124+
# Draw candlestick wicks
125+
self.pygame.draw.line(canvas, self.color_theme.wick, (candle_offset + self.candle_width // 2, candle_y_high), (candle_offset + self.candle_width // 2, candle_y_low))
126+
127+
# Draw candlestick body
128+
self.pygame.draw.rect(canvas, candle_color, (candle_offset, candle_body_y, self.candle_width, candle_body_height))
129+
130+
# Move to the next candle
131+
candle_offset += self.candle_width + self.candle_spacing
132+
133+
return canvas

0 commit comments

Comments
 (0)