Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed support for BinanceFutures Backtesting + added support for several new indicators #254

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions blankly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@
from blankly.utils import time_builder

from blankly.enums import Side, OrderType, OrderStatus, TimeInForce
from blankly.exchanges.interfaces.binance_futures.binance_futures import BinanceFutures
from blankly.exchanges.interfaces.ftx_futures.ftx_futures import FTXFutures
from blankly.frameworks.strategy import FuturesStrategy
from blankly.frameworks.strategy import FuturesStrategyState

from blankly.deployment.reporter_headers import Reporter as __Reporter_Headers

Expand Down
50 changes: 7 additions & 43 deletions blankly/data/data_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import pandas as pd
from enum import Enum

from blankly.utils import convert_epochs
from blankly.exchanges.interfaces.futures_exchange_interface import FuturesExchangeInterface


Expand All @@ -42,7 +41,6 @@ class DataTypes(Enum):
class FileTypes(Enum):
csv = 'csv'
json = 'json'
df = 'df'


class DataReader:
Expand Down Expand Up @@ -85,37 +83,19 @@ def _convert_to_list(file_path, symbol):
Primarily for CSV prices, this allows single or multi sets of symbols to be converted into matching arrays
"""
# Turn it into a list
if isinstance(file_path, str) or isinstance(file_path, pd.DataFrame):
if isinstance(file_path, str):
file_paths = [file_path]
else:
file_paths = file_path

if isinstance(symbol, str) or isinstance(file_path, pd.DataFrame):
if isinstance(symbol, str):
symbols = [symbol]
else:
# Could be None still
symbols = symbol

return file_paths, symbols

def _parse_df_prices(self, file_paths: list, symbols: list, columns: set) -> None:

if symbols is None:
raise LookupError("Must pass one or more symbols to identify the DataFrame")
if len(file_paths) != len(symbols):
raise LookupError(f"Mismatching symbol & file path lengths, got {len(file_paths)} and {len(symbols)} for "
f"file paths and symbol lengths.")

for index in range(len(file_paths)):

self._check_length(file_paths[index], file_paths[index])

# Check if its contained
assert (columns.issubset(file_paths[index].columns)), f"{columns} not subset of {file_paths[index].columns}"

# Now push it directly into the dataset and sort by time
self._internal_dataset[symbols[index]] = file_paths[index].sort_values('time')

def _parse_csv_prices(self, file_paths: list, symbols: list, columns: set) -> None:
if symbols is None:
raise LookupError("Must pass one or more symbols to identify the csv files")
Expand All @@ -130,7 +110,7 @@ def _parse_csv_prices(self, file_paths: list, symbols: list, columns: set) -> No
self._check_length(contents, file_paths[index])

# Check if its contained
assert (columns.issubset(contents.columns)), f"{columns} not subset of {contents.columns}"
assert (columns.issubset(contents.columns))

# Now push it directly into the dataset and sort by time
self._internal_dataset[symbols[index]] = contents.sort_values('time')
Expand Down Expand Up @@ -162,9 +142,7 @@ def complain_if_different(ending_: str, type_: str):
raise LookupError("Cannot pass both csv files and json files into a single constructor.")

for file_path in file_paths:
if isinstance(file_path, pd.DataFrame):
complain_if_different('df', FileTypes.df.value)
elif file_path[-3:] == 'csv':
if file_path[-3:] == 'csv':
complain_if_different('csv', FileTypes.csv.value)
elif file_path[-4:] == 'json':
# In this instance the symbols should be None
Expand All @@ -178,23 +156,12 @@ def _guess_resolutions(self):
for symbol in self._internal_dataset:
# Get the time diff
time_series: pd.Series = self._internal_dataset[symbol]['time']

# Convert all epochs using the convert epoch function
time_series = time_series.apply(lambda x: convert_epochs(x))

time_dif = time_series.diff()

# Now find the most common difference and use that
if symbol not in self.prices_info:
self.prices_info[symbol] = {}

guessed_resolution = int(time_dif.value_counts().idxmax())

# If the resolution is 0, then we have a problem
if guessed_resolution == 0:
raise LookupError(f"Resolution is 0 for {symbol}, this is not allowed. Please check your data."
f" This commonly occurs when the data is in exponential format or too few datapoints")

# Store the resolution start time and end time of each dataset
self.prices_info[symbol]['resolution'] = int(time_dif.value_counts().idxmax())
self.prices_info[symbol]['start_time'] = time_series.iloc[0]
Expand Down Expand Up @@ -228,9 +195,7 @@ def __init__(self, file_path: [str, list], symbol: [str, list]):

super().__init__(data_type)

if data_type == FileTypes.df.value:
self._parse_df_prices(file_paths, symbols, {'open', 'high', 'low', 'close', 'volume', 'time'})
elif data_type == FileTypes.json.value:
if data_type == FileTypes.json.value:
self._parse_json_prices(file_paths, ('open', 'high', 'low', 'close', 'volume', 'time'))
elif data_type == FileTypes.csv.value:
self._parse_csv_prices(file_paths, symbols, {'open', 'high', 'low', 'close', 'volume', 'time'})
Expand All @@ -243,9 +208,8 @@ def __init__(self, file_path: [str, list], symbol: [str, list]):
class EventReader(DataReader):
def __init__(self, event_type: str, events: dict):
super().__init__(DataTypes.event_json)
if events:
time, data = zip(*events.items())
self._write_dataset({'time': time, 'data': data}, event_type, ('time', 'data'))
time, data = zip(*events.items())
self._write_dataset({'time': time, 'data': data}, event_type, ('time', 'data'))


class JsonEventReader(DataReader):
Expand Down
2 changes: 1 addition & 1 deletion blankly/data/templates/keyless.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ def init(symbol, state: blankly.StrategyState):
strategy.add_price_event(price_event, symbol='BTC-USD', resolution='1d', init=init)

# Backtest the strategy
results = strategy.backtest(start_date=1598377600, end_date=1650067200, initial_values={'USD': 10000})
results = strategy.backtest(start_date=1588377600, end_date=1650067200, initial_values={'USD': 10000})
print(results)
2 changes: 1 addition & 1 deletion blankly/data/templates/none.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
if blankly.is_deployed:
strategy.start()
else:
strategy.backtest(to='1y', initial_values={'QUOTE_ASSET': 10000})
strategy.backtest(to='1y', initial_values={'USD': 10000})
2 changes: 1 addition & 1 deletion blankly/data/templates/rsi_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ def init(symbol, state: blankly.StrategyState):
if blankly.is_deployed:
strategy.start()
else:
strategy.backtest(to='1y', initial_values={'QUOTE_ASSET': 10000})
strategy.backtest(to='1y', initial_values={'USD': 10000})
4 changes: 0 additions & 4 deletions blankly/deployment/exchange_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ def kucoin_test_func(auth, tld):
base_url=(alpaca_api.live_url,
alpaca_api.paper_url)[auth['sandbox']]
).get_account()),

Exchange('binance', ['BTC-USDT', 'ETH-USDT', 'SOL-USDT'],
lambda auth, tld: BinanceClient(api_key=auth['API_KEY'], api_secret=auth['API_SECRET'],
tld=tld, testnet=auth['sandbox']).get_account(),
tlds=['com', 'us'], currency='USDT'),

Exchange('coinbase_pro', ['BTC-USD', 'ETH-USD', 'SOL-USD'],
lambda auth, tld: CoinbaseProAPI(api_key=auth['API_KEY'], api_secret=auth['API_SECRET'],
api_pass=auth['API_PASS'],
Expand All @@ -83,12 +81,10 @@ def kucoin_test_func(auth, tld):
Exchange('ftx', ['BTC-USD', 'ETH-USD', 'SOL-USD'],
lambda auth, tld: FTXAPI(auth['API_KEY'], auth['API_SECRET'], tld).get_account_info(),
tlds=['com', 'us'], python_class='FTX', display_name='FTX'),

Exchange('oanda', ['BTC-USD', 'ETH-USD', 'SOL-USD'],
lambda auth, tld: OandaAPI(personal_access_token=auth['PERSONAL_ACCESS_TOKEN'],
account_id=auth['ACCOUNT_ID'], sandbox=auth['sandbox']).get_account(),
key_info=['ACCOUNT_ID', 'PERSONAL_ACCESS_TOKEN']),

Exchange('kucoin', ['BTC-USDT', 'ETH-USDT', 'SOL-USDT'], kucoin_test_func,
key_info=['API_KEY', 'API_SECRET', 'API_PASS'], currency='USDT'),
]
Expand Down
Loading