In [1]:
import numpy as np
import datetime
from btgym import BTgymEnv, BTgymDataset
from btgym import BTgymRandomDataDomain, BTgymSequentialDataDomain
from btgym import DataSampleConfig, EnvResetConfig
from logbook import WARNING, INFO, DEBUG
BTGym data is basically discrete timeflow of equitype records. For the sake of defining episodic MDP over such data and setting formal problem objective it should be somehow structured.
From the other hand, all train data can laso be seen as big external replay memory. Giving algorithms some degree of control over data sampling parameters can be beneficial for overal performance.
The idea is to pass all desirable sample properties as kwargs to env.rest() method.
This notebook is brief introduction to API realisation of formal definitions introduced in Section 1 (Data) in this draft: https://github.com/Kismuz/btgym/blob/master/docs/papers/btgym_formalism_draft.pdf
objects described here can be thought as nested data containers with bult-in properties like sampling and splitting data to train and test subsets.
In [2]:
# Make data domain - top-level data structure:
domain = BTgymRandomDataDomain(
filename='../examples/data/DAT_ASCII_EURUSD_M1_2016.csv',
target_period={'days': 50, 'hours': 0, 'minutes': 0}, # use last 50 days of one year data as 'target domain'
# so we get [360 - holidays gaps - 50]
# days of train data (exclude holidays)
trial_params=dict(
start_weekdays={0, 1, 2, 3, 4, 5, 6},
sample_duration={'days': 30, 'hours': 0, 'minutes': 0}, # let each trial be 10 days long
start_00=True, # ajust trial beginning to the beginning of the day
time_gap={'days': 15, 'hours': 0}, # tolerance param
test_period={'days': 6, 'hours': 0, 'minutes': 0}, # from those 10 reserve last 2 days for trial test data
),
episode_params=dict(
start_weekdays={0, 1, 2, 3, 4, 5, 6},
sample_duration={'days': 0, 'hours': 23, 'minutes': 55}, # make every episode duration be 23:55
start_00=False, # do not ajust beginning time
time_gap={'days': 0, 'hours': 10},
),
log_level=INFO, # Set to DEBUG to see more output
)
Here Domain instanse is defined such as:
For convinience currently implemented control options are referenced in btgym.datafeed.base.DataSampleConfig
:
DataSampleConfig = dict(
get_new=True,
sample_type=0,
b_alpha=1,
b_beta=1
)
...which simply mirrors base data sample() kwargs:
Args:
get_new (bool): sample new (True) or reuse (False) last made sample;
sample_type (int or bool): 0 (train) or 1 (test) - get sample from train or test data subsets
respectively.
b_alpha (float): beta-distribution sampling alpha > 0, valid for train episodes.
b_beta (float): beta-distribution sampling beta > 0, valid for train episodes.
btgym.datafeed.base.EnvResetConfig
:
EnvResetConfig = dict(
episode_config=DataSampleConfig,
trial_config=DataSampleConfig,
)
In [3]:
domain.reset()
In [5]:
trial= domain.sample(
get_new=True,
sample_type=0,
b_alpha=1,
b_beta=1
)
trial.reset()
Here new Trial object from Source
(or train) domaini have been requested as uniform sample from entire source data interval
If target period duration is set to 0:0:0, trying to get target sample will rise an exeption.
Need to reset before sampling as well:
Implementatdion detail:
during real BTgym operation domain instance is held by btgym_data_server
;
Different Trial samples are sent to every environment instance, so corresponding btgym_server
can sample multiple episodes 'in place'.
In [6]:
episode = trial.sample(
get_new=True,
sample_type=1,
b_alpha=1,
b_beta=1
)
train
or test
subsets, just like Trials from Source/Target domains. Here new Episode from test subset of the Trial is requested. Since it is test subset, alpha and beta params doesnt count - it is always uniform (just a common sense heuristic, one can always change it)
In [7]:
data_feed = episode.to_btfeed()
In [14]:
print('Got instance of: {}\nholding data: {}\nmetadata: {}'.
format(type(domain), domain.filename, domain.metadata))
print(' |\nsample()\n |')
print('got instance of: {}\nholding data: {}\nmetadata: {}'.
format(type(trial), trial.filename, trial.metadata))
print(' |\nsample()\n |')
print('got instance of: {}\nholding data: {}\nmetadata: {}'.
format(type(episode), episode.filename, episode.metadata))
print(' |\nto_btfeed()\n |')
print('got instance of: {}\n...holding default data line: {}, wi ready to be fed to bt.Cerebro.'.format(type(data_feed), data_feed))
In [15]:
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
In [100]:
alpha, beta, size = 2, 1, 500
y = np.random.beta(alpha, beta, size)
plt.figure(0)
plt.suptitle(
'Beta-distribution with parameters: alpha={}, beta={}'.format(alpha, beta), fontsize=12
)
plt.grid(True)
fig = plt.hist(y, histtype='stepfilled', bins=500)
In [107]:
# Suppose Trial is ready, if no - use cells above to get one
trial.set_logger(level=13) # make it qiuet
train_num_samples = 600
test_num_samples = 300
# Beta distribution paramters:
alpha = 3 # give priority to recent samples
beta = 1
train_start = np.zeros(train_num_samples)
test_start = np.zeros(test_num_samples)
# Sample train episode and get time of first record as some realtive number:
for i in range(train_num_samples):
train_start[i] = trial.sample(True, 0, alpha, beta).data[0:1].index[0].value / 1e18
# Sample test episode and get time of first record as number:
for i in range(test_num_samples):
test_start[i] = trial.sample(True, 1, alpha, beta).data[0:1].index[0].value / 1e18
fig = plt.figure(1, figsize=(18,8))
plt.suptitle('Begining times distribution for episodes within single trial', fontsize=16)
plt.xlabel('Realtive trial time', fontsize=14)
plt.ylabel('Number of episodes', fontsize=14)
fig.text(0.25, 0.8, 'Train interval, alpha={}, beta={}'.format(alpha, beta), fontsize=14)
fig.text(0.75, 0.8, 'Test interval, uniform', fontsize=14)
fig.text(0.25, 0.5, 'Note holidays empty gaps', fontsize=12)
plt.grid(True)
fig = plt.hist(train_start, histtype='stepfilled', bins=train_num_samples)
fig = plt.hist(test_start, histtype='stepfilled', bins=test_num_samples)
In [ ]: