Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f718249a49 | ||
|
|
f5eec79a33 | ||
|
|
23207abe49 | ||
|
|
95b123b605 | ||
|
|
dd1dab1c5c | ||
|
|
8cf1e57d50 | ||
|
|
004cd79738 | ||
|
|
028c297f72 | ||
|
|
dc79f49ef4 | ||
|
|
5b156fe0e6 | ||
|
|
d2f33bef7c | ||
|
|
c369064fd7 | ||
|
|
df6d49d7de | ||
|
|
371b3aeaca | ||
|
|
c0ef623887 | ||
|
|
3187e8104e | ||
|
|
82a323791c |
@@ -10,7 +10,7 @@ config = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
'caching': {
|
'caching': {
|
||||||
'driver': 'pickle'
|
'driver': None
|
||||||
},
|
},
|
||||||
|
|
||||||
'logging': {
|
'logging': {
|
||||||
@@ -96,6 +96,9 @@ config = {
|
|||||||
{'asset': 'BTC', 'balance': 0},
|
{'asset': 'BTC', 'balance': 0},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'Polygon': {
|
||||||
|
'api_key': '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
# changes the metrics output of the backtest
|
# changes the metrics output of the backtest
|
||||||
|
|||||||
@@ -251,6 +251,10 @@ def get_config(keys: str, default=None):
|
|||||||
return CACHED_CONFIG[keys]
|
return CACHED_CONFIG[keys]
|
||||||
|
|
||||||
|
|
||||||
|
def get_nan_indices(array):
|
||||||
|
return np.where(np.isnan(array))[0]
|
||||||
|
|
||||||
|
|
||||||
def get_strategy_class(strategy_name):
|
def get_strategy_class(strategy_name):
|
||||||
from pydoc import locate
|
from pydoc import locate
|
||||||
|
|
||||||
@@ -520,6 +524,12 @@ def readable_duration(seconds, granularity=2):
|
|||||||
return ', '.join(result[:granularity])
|
return ', '.join(result[:granularity])
|
||||||
|
|
||||||
|
|
||||||
|
def reinsert_nan(array, nan_indices):
|
||||||
|
for i in range(nan_indices.shape[0]):
|
||||||
|
array = np.concatenate((array[:nan_indices[i]], [np.nan], array[nan_indices[i]:]))
|
||||||
|
return array
|
||||||
|
|
||||||
|
|
||||||
def relative_to_absolute(path: str) -> str:
|
def relative_to_absolute(path: str) -> str:
|
||||||
return os.path.abspath(path)
|
return os.path.abspath(path)
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ from jesse.services.db import db
|
|||||||
|
|
||||||
|
|
||||||
class Candle(peewee.Model):
|
class Candle(peewee.Model):
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
id = peewee.UUIDField(primary_key=True)
|
id = peewee.UUIDField(primary_key=True)
|
||||||
timestamp = peewee.BigIntegerField()
|
timestamp = peewee.BigIntegerField()
|
||||||
open = peewee.FloatField()
|
open = peewee.FloatField(null = True)
|
||||||
close = peewee.FloatField()
|
close = peewee.FloatField(null = True)
|
||||||
high = peewee.FloatField()
|
high = peewee.FloatField(null = True)
|
||||||
low = peewee.FloatField()
|
low = peewee.FloatField(null = True)
|
||||||
volume = peewee.FloatField()
|
volume = peewee.FloatField(null = True)
|
||||||
symbol = peewee.CharField()
|
symbol = peewee.CharField()
|
||||||
exchange = peewee.CharField()
|
exchange = peewee.CharField()
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from jesse.models import Candle
|
|||||||
from jesse.modes.import_candles_mode.drivers import drivers
|
from jesse.modes.import_candles_mode.drivers import drivers
|
||||||
from jesse.modes.import_candles_mode.drivers.interface import CandleExchange
|
from jesse.modes.import_candles_mode.drivers.interface import CandleExchange
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation=False):
|
def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation=False):
|
||||||
try:
|
try:
|
||||||
@@ -108,8 +110,11 @@ def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation=False
|
|||||||
run(exchange, symbol, jh.timestamp_to_time(first_existing_timestamp)[:10], True)
|
run(exchange, symbol, jh.timestamp_to_time(first_existing_timestamp)[:10], True)
|
||||||
return
|
return
|
||||||
|
|
||||||
# fill absent candles (if there's any)
|
if driver.stock_mode:
|
||||||
candles = _fill_absent_candles(candles, temp_start_timestamp, temp_end_timestamp)
|
candles = _fill_absent_candles_with_nan(candles, temp_start_timestamp, temp_end_timestamp)
|
||||||
|
else:
|
||||||
|
# fill absent candles (if there's any)
|
||||||
|
candles = _fill_absent_candles(candles, temp_start_timestamp, temp_end_timestamp)
|
||||||
|
|
||||||
# store in the database
|
# store in the database
|
||||||
if skip_confirmation:
|
if skip_confirmation:
|
||||||
@@ -245,6 +250,46 @@ def _get_candles_from_backup_exchange(
|
|||||||
return total_candles
|
return total_candles
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_absent_candles_with_nan(temp_candles, start_timestamp, end_timestamp):
|
||||||
|
if len(temp_candles) == 0:
|
||||||
|
raise CandleNotFoundInExchange(
|
||||||
|
'No candles exists in the market for this day: {} \n'
|
||||||
|
'Try another start_date'.format(
|
||||||
|
jh.timestamp_to_time(start_timestamp)[:10],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
symbol = temp_candles[0]['symbol']
|
||||||
|
exchange = temp_candles[0]['exchange']
|
||||||
|
candles = []
|
||||||
|
loop_length = ((end_timestamp - start_timestamp) / 60000) + 1
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < loop_length:
|
||||||
|
candle_for_timestamp = pydash.find(
|
||||||
|
temp_candles, lambda c: c['timestamp'] == start_timestamp)
|
||||||
|
|
||||||
|
if candle_for_timestamp is None:
|
||||||
|
candles.append({
|
||||||
|
'id': jh.generate_unique_id(),
|
||||||
|
'symbol': symbol,
|
||||||
|
'exchange': exchange,
|
||||||
|
'timestamp': start_timestamp,
|
||||||
|
'open': np.nan,
|
||||||
|
'high': np.nan,
|
||||||
|
'low': np.nan,
|
||||||
|
'close': np.nan,
|
||||||
|
'volume': np.nan
|
||||||
|
})
|
||||||
|
# candle is present
|
||||||
|
else:
|
||||||
|
candles.append(candle_for_timestamp)
|
||||||
|
|
||||||
|
start_timestamp += 60000
|
||||||
|
i += 1
|
||||||
|
return candles
|
||||||
|
|
||||||
|
|
||||||
def _fill_absent_candles(temp_candles, start_timestamp, end_timestamp):
|
def _fill_absent_candles(temp_candles, start_timestamp, end_timestamp):
|
||||||
if len(temp_candles) == 0:
|
if len(temp_candles) == 0:
|
||||||
raise CandleNotFoundInExchange(
|
raise CandleNotFoundInExchange(
|
||||||
@@ -305,3 +350,4 @@ def _fill_absent_candles(temp_candles, start_timestamp, end_timestamp):
|
|||||||
|
|
||||||
def _insert_to_database(candles):
|
def _insert_to_database(candles):
|
||||||
Candle.insert_many(candles).on_conflict_ignore().execute()
|
Candle.insert_many(candles).on_conflict_ignore().execute()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from .binance_futures import BinanceFutures
|
|||||||
from .bitfinex import Bitfinex
|
from .bitfinex import Bitfinex
|
||||||
from .coinbase import Coinbase
|
from .coinbase import Coinbase
|
||||||
from .testnet_binance_futures import TestnetBinanceFutures
|
from .testnet_binance_futures import TestnetBinanceFutures
|
||||||
|
from .polygon import Polygon
|
||||||
|
|
||||||
drivers = {
|
drivers = {
|
||||||
'Binance': Binance,
|
'Binance': Binance,
|
||||||
@@ -10,4 +11,5 @@ drivers = {
|
|||||||
'Testnet Binance Futures': TestnetBinanceFutures,
|
'Testnet Binance Futures': TestnetBinanceFutures,
|
||||||
'Bitfinex': Bitfinex,
|
'Bitfinex': Bitfinex,
|
||||||
'Coinbase': Coinbase,
|
'Coinbase': Coinbase,
|
||||||
|
'Polygon': Polygon,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ class CandleExchange(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, count, sleep_time):
|
def __init__(self, name, count, sleep_time, stock_mode = False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.count = count
|
self.count = count
|
||||||
self.sleep_time = sleep_time
|
self.sleep_time = sleep_time
|
||||||
self.backup_exchange: CandleExchange = None
|
self.backup_exchange: CandleExchange = None
|
||||||
|
self.stock_mode = stock_mode
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def init_backup_exchange(self):
|
def init_backup_exchange(self):
|
||||||
|
|||||||
64
jesse/modes/import_candles_mode/drivers/polygon.py
Normal file
64
jesse/modes/import_candles_mode/drivers/polygon.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from polygon import RESTClient
|
||||||
|
from requests import HTTPError
|
||||||
|
|
||||||
|
import jesse.helpers as jh
|
||||||
|
from .interface import CandleExchange
|
||||||
|
|
||||||
|
|
||||||
|
class Polygon(CandleExchange):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('Polygon', 5000, 0.5, stock_mode=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_key = jh.get_config('env.exchanges.Polygon.api_key')
|
||||||
|
except:
|
||||||
|
raise ValueError("Polygon api_key missing in config.py")
|
||||||
|
|
||||||
|
self.restclient = RESTClient(api_key)
|
||||||
|
|
||||||
|
def init_backup_exchange(self):
|
||||||
|
self.backup_exchange = None
|
||||||
|
|
||||||
|
def get_starting_time(self, symbol):
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch(self, symbol, start_timestamp):
|
||||||
|
|
||||||
|
base = jh.base_asset(symbol)
|
||||||
|
# Check if symbol exists. Raises HTTP 404 if it doesn't.
|
||||||
|
try:
|
||||||
|
details = self.restclient.reference_ticker_details(base)
|
||||||
|
except HTTPError:
|
||||||
|
raise ValueError("Symbol ({}) probably doesn't exist.".format(base))
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'unadjusted': 'false',
|
||||||
|
'sort': 'asc',
|
||||||
|
'limit': self.count,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Polygon takes string dates not timestamps
|
||||||
|
start = jh.timestamp_to_date(start_timestamp)
|
||||||
|
end = jh.timestamp_to_date(start_timestamp + (self.count) * 60000)
|
||||||
|
response = self.restclient.stocks_equities_aggregates(base, 1, 'minute', start, end, **payload)
|
||||||
|
|
||||||
|
data = response.results
|
||||||
|
|
||||||
|
candles = []
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
candles.append({
|
||||||
|
'id': jh.generate_unique_id(),
|
||||||
|
'symbol': symbol,
|
||||||
|
'exchange': self.name,
|
||||||
|
'timestamp': int(d['t']),
|
||||||
|
'open': float(d['o']),
|
||||||
|
'close': float(d['c']),
|
||||||
|
'high': float(d['h']),
|
||||||
|
'low': float(d['l']),
|
||||||
|
'volume': int(d['v'])
|
||||||
|
})
|
||||||
|
|
||||||
|
return candles
|
||||||
@@ -21,11 +21,11 @@ def generate_candle_from_one_minutes(timeframe,
|
|||||||
|
|
||||||
return np.array([
|
return np.array([
|
||||||
candles[0][0],
|
candles[0][0],
|
||||||
candles[0][1],
|
candles[0][1] if not np.isnan(candles[:, 1]).any() else np.nan,
|
||||||
candles[-1][2],
|
candles[-1][2] if not np.isnan(candles[:, 2]).any() else np.nan,
|
||||||
candles[:, 3].max(),
|
candles[:, 3].max() if not np.isnan(candles[:, 3]).any() else np.nan,
|
||||||
candles[:, 4].min(),
|
candles[:, 4].min() if not np.isnan(candles[:, 4]).any() else np.nan,
|
||||||
candles[:, 5].sum(),
|
candles[:, 5].sum() if not np.isnan(candles[:, 5]).any() else np.nan,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ config = {
|
|||||||
{'asset': 'BTC', 'balance': 0},
|
{'asset': 'BTC', 'balance': 0},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'Polygon': {
|
||||||
|
'api_key': '',
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ newtulipy~=0.4.2
|
|||||||
numpy~=1.19.4
|
numpy~=1.19.4
|
||||||
pandas==1.2.0
|
pandas==1.2.0
|
||||||
peewee~=3.14.0
|
peewee~=3.14.0
|
||||||
|
polygon-api-client~=0.1.9
|
||||||
psycopg2-binary~=2.8.6
|
psycopg2-binary~=2.8.6
|
||||||
pydash~=4.9.0
|
pydash~=4.9.0
|
||||||
pytest==6.2.1
|
pytest==6.2.1
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -13,6 +13,7 @@ REQUIRED_PACKAGES = [
|
|||||||
'numpy',
|
'numpy',
|
||||||
'pandas',
|
'pandas',
|
||||||
'peewee',
|
'peewee',
|
||||||
|
'polygon-api-client',
|
||||||
'psycopg2-binary',
|
'psycopg2-binary',
|
||||||
'pydash',
|
'pydash',
|
||||||
'pytest',
|
'pytest',
|
||||||
|
|||||||
@@ -211,6 +211,12 @@ def test_get_config():
|
|||||||
assert jh.get_config('env.logging.order_submission', 2020) is True
|
assert jh.get_config('env.logging.order_submission', 2020) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_nan_indices():
|
||||||
|
arr = np.array([0, 11, 22, np.nan, 33, 44, 54, 55, np.nan, np.nan, 20])
|
||||||
|
ind = jh.get_nan_indices(arr)
|
||||||
|
assert (ind == np.array([3, 8, 9])).all()
|
||||||
|
|
||||||
|
|
||||||
def test_get_strategy_class():
|
def test_get_strategy_class():
|
||||||
from jesse.strategies import Strategy
|
from jesse.strategies import Strategy
|
||||||
assert issubclass(jh.get_strategy_class("Test01"), Strategy)
|
assert issubclass(jh.get_strategy_class("Test01"), Strategy)
|
||||||
@@ -405,6 +411,14 @@ def test_readable_duration():
|
|||||||
assert jh.readable_duration(604312) == "6 days, 23 hours"
|
assert jh.readable_duration(604312) == "6 days, 23 hours"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reinsert_nan():
|
||||||
|
arr = np.array([0, 11, 22, np.nan, 33, 44, 54, 55, np.nan, np.nan, 20])
|
||||||
|
arr_without_nan = arr[~np.isnan(arr)]
|
||||||
|
ind = jh.get_nan_indices(arr)
|
||||||
|
arr_with_nan = jh.reinsert_nan(arr_without_nan, ind)
|
||||||
|
assert ((arr == arr_with_nan) | (np.isnan(arr) & np.isnan(arr_with_nan))).all()
|
||||||
|
|
||||||
|
|
||||||
def test_relative_to_absolute():
|
def test_relative_to_absolute():
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
assert jh.relative_to_absolute("tests/test_helpers.py") == str(Path(__file__).absolute())
|
assert jh.relative_to_absolute("tests/test_helpers.py") == str(Path(__file__).absolute())
|
||||||
|
|||||||
Reference in New Issue
Block a user