Merge branch 'master' of github.com:jesse-ai/jesse

This commit is contained in:
Saleh Mir
2022-02-14 13:53:06 +01:00
60 changed files with 239 additions and 382 deletions

View File

@@ -212,11 +212,7 @@ def run() -> None:
# read port from .env file, if not found, use default
from jesse.services.env import ENV_VALUES
if 'APP_PORT' in ENV_VALUES:
port = int(ENV_VALUES['APP_PORT'])
else:
port = 9000
port = int(ENV_VALUES['APP_PORT']) if 'APP_PORT' in ENV_VALUES else 9000
# run the main application
uvicorn.run(fastapi_app, host="0.0.0.0", port=port, log_level="info")

View File

@@ -77,9 +77,7 @@ class Sandbox(Exchange):
store.orders.get_order_by_id(self.name, symbol, order_id).cancel()
def get_exec_inst(self, flags: list) -> Union[str, None]:
if flags:
return flags[0]
return None
return flags[0] if flags else None
def _fetch_precisions(self) -> None:
pass

View File

@@ -113,7 +113,7 @@ def dashy_symbol(symbol: str) -> str:
if compare_symbol == symbol:
return s
return f"{symbol[0:3]}-{symbol[3:]}"
return f'{symbol[:3]}-{symbol[3:]}'
def date_diff_in_days(date1: arrow.arrow.Arrow, date2: arrow.arrow.Arrow) -> int:
@@ -314,10 +314,7 @@ def insert_list(index: int, item, arr: list) -> list:
"""
helper to insert an item in a Python List without removing the item
"""
if index == -1:
return arr + [item]
return arr[:index] + [item] + arr[index:]
return arr + [item] if index == -1 else arr[:index] + [item] + arr[index:]
def is_backtesting() -> bool:
@@ -743,7 +740,7 @@ def timeframe_to_one_minutes(timeframe: str) -> int:
try:
return dic[timeframe]
except KeyError:
all_timeframes = [timeframe for timeframe in class_iter(timeframes)]
all_timeframes = list(class_iter(timeframes))
raise InvalidTimeframe(
f'Timeframe "{timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}.')
@@ -881,10 +878,7 @@ def float_or_none(item):
"""
Return the float of the value if it's not None
"""
if item is None:
return None
else:
return float(item)
return None if item is None else float(item)
def str_or_none(item, encoding='utf-8'):
@@ -895,9 +889,7 @@ def str_or_none(item, encoding='utf-8'):
return None
else:
# return item if it's str, if not, decode it using encoding
if isinstance(item, str):
return item
return str(item, encoding)
return item if isinstance(item, str) else str(item, encoding)
def get_settlement_currency_from_exchange(exchange: str):
@@ -921,14 +913,7 @@ def is_notebook():
try:
shell = get_ipython().__class__.__name__
# Jupyter notebook or qtconsole
if shell == 'ZMQInteractiveShell':
return True
elif shell == 'TerminalInteractiveShell':
# Terminal running IPython
return False
else:
# Other type (?)
return False
return shell == 'ZMQInteractiveShell'
except NameError:
# Probably standard Python interpreter
return False

View File

@@ -25,7 +25,4 @@ def acosc(candles: np.ndarray, sequential: bool = False) -> AC:
res = ao - talib.SMA(ao, 5)
mom = talib.MOM(res, timeperiod=1)
if sequential:
return AC(res, mom)
else:
return AC(res[-1], mom[-1])
return AC(res, mom) if sequential else AC(res[-1], mom[-1])

View File

@@ -24,7 +24,4 @@ def ao(candles: np.ndarray, sequential: bool = False) -> AO:
mom = talib.MOM(res, timeperiod=1)
if sequential:
return AO(res, mom)
else:
return AO(res[-1], mom[-1])
return AO(res, mom) if sequential else AO(res[-1], mom[-1])

View File

@@ -34,8 +34,5 @@ def cksp(candles: np.ndarray, p: int = 10, x: float = 1.0, q: int = 9, sequenti
SS0 = talib.MIN(candles_low, q) + x * atr
SS = talib.MIN(SS0, q)
if sequential:
return CKSP(LS, SS)
else:
return CKSP(LS[-1], SS[-1])
return CKSP(LS, SS) if sequential else CKSP(LS[-1], SS[-1])

View File

@@ -23,7 +23,4 @@ def di(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DI:
MINUS_DI = talib.MINUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
PLUS_DI = talib.PLUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
if sequential:
return DI(PLUS_DI, MINUS_DI)
else:
return DI(PLUS_DI[-1], MINUS_DI[-1])
return DI(PLUS_DI, MINUS_DI) if sequential else DI(PLUS_DI[-1], MINUS_DI[-1])

View File

@@ -23,7 +23,4 @@ def dm(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DM:
MINUS_DI = talib.MINUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
PLUS_DI = talib.PLUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
if sequential:
return DM(PLUS_DI, MINUS_DI)
else:
return DM(PLUS_DI[-1], MINUS_DI[-1])
return DM(PLUS_DI, MINUS_DI) if sequential else DM(PLUS_DI[-1], MINUS_DI[-1])

View File

@@ -29,7 +29,4 @@ def eri(candles: np.ndarray, period: int = 13, matype: int = 1, source_type: str
bull = candles[:, 3] - ema
bear = candles[:, 4] - ema
if sequential:
return ERI(bull, bear)
else:
return ERI(bull[-1], bear[-1])
return ERI(bull, bear) if sequential else ERI(bull[-1], bear[-1])

View File

@@ -33,10 +33,7 @@ def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, seq
res = frame_fast(candles, n, SC, FC)
if sequential:
return res
else:
return res[-1]
return res if sequential else res[-1]
@njit

View File

@@ -36,9 +36,7 @@ def fwma(candles: np.ndarray, period: int = 5, source_type: str = "close", seque
def fibonacci(n: int = 2) -> np.ndarray:
"""Fibonacci Sequence as a numpy array"""
n = int(fabs(n)) if n >= 0 else 2
n -= 1
n = (int(fabs(n)) if n >= 0 else 2) - 1
a, b = 1, 1
result = np.array([a])
@@ -48,7 +46,4 @@ def fibonacci(n: int = 2) -> np.ndarray:
result = np.append(result, a)
fib_sum = np.sum(result)
if fib_sum > 0:
return result / fib_sum
else:
return result
return result / fib_sum if fib_sum > 0 else result

View File

@@ -37,7 +37,4 @@ def kdj(candles: np.ndarray, fastk_period: int = 9, slowk_period: int = 3, slowk
d = ma(k, period=slowd_period, matype=slowd_matype, sequential=True)
j = 3 * k - 2 * d
if sequential:
return KDJ(k, d, j)
else:
return KDJ(k[-1], d[-1], j[-1])
return KDJ(k, d, j) if sequential else KDJ(k[-1], d[-1], j[-1])

View File

@@ -42,7 +42,4 @@ def kst(candles: np.ndarray, sma_period1: int = 10, sma_period2: int = 10, sma_p
3 * aroc3[aroc3.size - aroc4.size:] + 4 * aroc4
signal = talib.SMA(line, signal_period)
if sequential:
return KST(line, signal)
else:
return KST(line[-1], signal[-1])
return KST(line, signal) if sequential else KST(line[-1], signal[-1])

View File

@@ -30,7 +30,4 @@ def mama(candles: np.ndarray, fastlimit: float = 0.5, slowlimit: float = 0.05, s
mama_val, fama = talib.MAMA(source, fastlimit=fastlimit, slowlimit=slowlimit)
if sequential:
return MAMA(mama_val, fama)
else:
return MAMA(mama_val[-1], fama[-1])
return MAMA(mama_val, fama) if sequential else MAMA(mama_val[-1], fama[-1])

View File

@@ -27,7 +27,4 @@ def msw(candles: np.ndarray, period: int = 5, source_type: str = "close", sequen
s = same_length(candles, msw_sine)
l = same_length(candles, msw_lead)
if sequential:
return MSW(s, l)
else:
return MSW(s[-1], l[-1])
return MSW(s, l) if sequential else MSW(s[-1], l[-1])

View File

@@ -31,10 +31,7 @@ def pma(candles: np.ndarray, source_type: str = "hl2", sequential: bool = False)
predict, trigger = pma_fast(source)
if sequential:
return PMA(predict, trigger)
else:
return PMA(predict[-1], trigger[-1])
return PMA(predict, trigger) if sequential else PMA(predict[-1], trigger[-1])
@njit

View File

@@ -41,8 +41,5 @@ def rsmk(candles: np.ndarray, candles_compare: np.ndarray, lookback: int = 90, p
signal = ma(res, period=signal_period, matype=signal_matype, sequential=True)
if sequential:
return RSMK(res, signal)
else:
return RSMK(res[-1], signal[-1])
return RSMK(res, signal) if sequential else RSMK(res[-1], signal[-1])

View File

@@ -37,7 +37,4 @@ def stoch(candles: np.ndarray, fastk_period: int = 14, slowk_period: int = 3, sl
k = ma(stoch_val, period=slowk_period, matype=slowk_matype, sequential=True)
d = ma(k, period=slowd_period, matype=slowd_matype, sequential=True)
if sequential:
return Stochastic(k, d)
else:
return Stochastic(k[-1], d[-1])
return Stochastic(k, d) if sequential else Stochastic(k[-1], d[-1])

View File

@@ -34,7 +34,4 @@ def stochf(candles: np.ndarray, fastk_period: int = 5, fastd_period: int = 3, fa
k = 100 * (candles_close - ll) / (hh - ll)
d = ma(k, period=fastd_period, matype=fastd_matype, sequential=True)
if sequential:
return StochasticFast(k, d)
else:
return StochasticFast(k[-1], d[-1])
return StochasticFast(k, d) if sequential else StochasticFast(k[-1], d[-1])

View File

@@ -32,7 +32,4 @@ def vpci(candles: np.ndarray, short_range: int = 5, long_range: int = 25, sequen
VPCIS = talib.SMA(VPCI_val * candles[:, 5], short_range) / talib.SMA(candles[:, 5], short_range)
if sequential:
return VPCI(VPCI_val, VPCIS)
else:
return VPCI(VPCI_val[-1], VPCIS[-1])
return VPCI(VPCI_val, VPCIS) if sequential else VPCI(VPCI_val[-1], VPCIS[-1])

View File

@@ -13,4 +13,4 @@ class NpEncoder(json.JSONEncoder):
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
return super(NpEncoder, self).default(obj)
return super().default(obj)

View File

@@ -40,24 +40,15 @@ class Position:
@property
def mark_price(self) -> float:
if not jh.is_live():
return self.current_price
return self._mark_price
return self.current_price if not jh.is_live() else self._mark_price
@property
def funding_rate(self) -> float:
if not jh.is_live():
return 0
return self._funding_rate
return 0 if not jh.is_live() else self._funding_rate
@property
def next_funding_timestamp(self) -> Union[int, None]:
if not jh.is_live():
return None
return self._next_funding_timestamp
return None if not jh.is_live() else self._next_funding_timestamp
@property
def value(self) -> float:
@@ -118,10 +109,7 @@ class Position:
if self.exchange.type == 'spot':
return 1
if self.strategy:
return self.strategy.leverage
else:
return np.nan
return self.strategy.leverage if self.strategy else np.nan
@property
def entry_margin(self) -> float:

View File

@@ -211,7 +211,7 @@ def load_candles(start_date_str: str, finish_date_str: str) -> Dict[str, Dict[st
f'There are missing candles between {start_date_str} => {finish_date_str}')
# cache it for near future calls
cache.set_value(cache_key, tuple(candles_tuple), expire_seconds=60 * 60 * 24 * 7)
cache.set_value(cache_key, tuple(candles_tuple), expire_seconds=60**2 * 24 * 7)
candles[key] = {
'exchange': exchange,

View File

@@ -189,7 +189,7 @@ def download_file(mode: str, file_type: str, session_id: str = None):
path = f'storage/trading-view-pine-editor/{session_id}.txt'
filename = f'backtest-{session_id}.txt'
elif mode == 'optimize' and file_type == 'log':
path = f'storage/logs/optimize-mode.txt'
path = 'storage/logs/optimize-mode.txt'
# filename should be "optimize-" + current timestamp
filename = f'optimize-{jh.timestamp_to_date(jh.now(True))}.txt'
else:

View File

@@ -54,10 +54,11 @@ class BybitPerpetual(CandleExchange):
payload = {
'interval': 1,
'symbol': dashless_symbol,
'from': int(start_timestamp / 1000),
'from': start_timestamp // 1000,
'limit': self.count,
}
response = requests.get(self.endpoint, params=payload)
# Exchange In Maintenance

View File

@@ -1,4 +1,3 @@
import requests
import jesse.helpers as jh

View File

@@ -54,10 +54,11 @@ class TestnetBybitPerpetual(CandleExchange):
payload = {
'interval': 1,
'symbol': dashless_symbol,
'from': int(start_timestamp / 1000),
'from': start_timestamp // 1000,
'limit': self.count,
}
response = requests.get(self.endpoint, params=payload)
# Exchange In Maintenance

View File

@@ -183,7 +183,7 @@ class Optimizer(ABC):
population_len = len(self.population)
if population_len == 0:
raise IndexError('population is empty')
count = int(population_len / 100)
count = population_len // 100
if count == 0:
count = 1
random_index = np.random.choice(population_len, count, replace=False)

View File

@@ -22,8 +22,6 @@ def run(
start_date: str,
finish_date: str,
optimal_total: int,
csv: bool,
json: bool
) -> None:
from jesse.config import config, set_config
config['app']['trading_mode'] = 'optimize'
@@ -60,7 +58,7 @@ def run(
click.clear()
optimizer = Optimizer(
training_candles, testing_candles, optimal_total, cpu_cores, csv, json, start_date, finish_date
training_candles, testing_candles, optimal_total, cpu_cores, start_date, finish_date
)
# start the process
@@ -83,9 +81,10 @@ def _get_training_and_testing_candles(start_date_str: str, finish_date_str: str)
training_candles[key] = {
'exchange': candles[key]['exchange'],
'symbol': candles[key]['symbol'],
'candles': candles[key]['candles'][0:divider_index],
'candles': candles[key]['candles'][:divider_index],
}
testing_candles[key] = {
'exchange': candles[key]['exchange'],
'symbol': candles[key]['symbol'],

View File

@@ -98,7 +98,7 @@ def get_fitness(
score = total_effect_rate * ratio_normalized
# if score is numpy nan, replace it with 0.0001
if np.isnan(score):
logger.log_optimize_mode(f'Score is nan. DNA is invalid')
logger.log_optimize_mode('Score is nan. DNA is invalid')
score = 0.0001
# elif jh.is_debugging():
else:
@@ -120,7 +120,7 @@ def get_fitness(
'PNL': round(testing_data_metrics['net_profit_percentage'], 2)
}
else:
logger.log_optimize_mode(f'Less than 5 trades in the training data. DNA is invalid')
logger.log_optimize_mode('Less than 5 trades in the training data. DNA is invalid')
score = 0.0001
return score, training_log, testing_log

View File

@@ -82,9 +82,7 @@ class RouterClass:
self.routes.append(Route(r["exchange"], r["symbol"], r["timeframe"], r["strategy"], None))
def set_market_data(self, routes: List[Any]) -> None:
self.market_data = []
for r in routes:
self.market_data.append(Route(*r))
self.market_data = [Route(*r) for r in routes]
def set_extra_candles(self, extra_candles: list) -> None:
self.extra_candles = extra_candles

View File

@@ -28,7 +28,7 @@ class Cache:
else:
self.db = {}
def set_value(self, key: str, data: Any, expire_seconds: int = 60 * 60) -> None:
def set_value(self, key: str, data: Any, expire_seconds: int = 60**2) -> None:
if self.driver is None:
return
@@ -92,7 +92,7 @@ def cached(method):
def decorated(self, *args, **kwargs):
cached_method = self._cached_methods.get(method)
if cached_method is None:
cached_method = lru_cache()(method)
cached_method = lru_cache(method)
self._cached_methods[method] = cached_method
return cached_method(self, *args, **kwargs)

View File

@@ -15,14 +15,10 @@ class Database:
self.db: PostgresqlExtDatabase = None
def is_closed(self) -> bool:
if self.db is None:
return True
return self.db.is_closed()
return True if self.db is None else self.db.is_closed()
def is_open(self) -> bool:
if self.db is None:
return False
return not self.db.is_closed()
return False if self.db is None else not self.db.is_closed()
def close_connection(self) -> None:
if self.db:

View File

@@ -40,4 +40,4 @@ if jh.is_jesse_project():
# raise FileNotFoundError('.env file is missing from within your local project. This usually happens when you\'re in the wrong directory. You can create one by running "cp .env.example .env"')
if not jh.is_unit_testing() and ENV_VALUES['PASSWORD'] == '':
raise EnvironmentError('You forgot to set the PASSWORD in your .env file')
raise OSError('You forgot to set the PASSWORD in your .env file')

View File

@@ -46,7 +46,7 @@ def install(is_live_plugin_already_installed: bool, strict: bool):
is_64_bit = platform.machine().endswith('64')
print('is_64_bit', is_64_bit)
if not is_64_bit:
raise NotImplementedError(f'Only 64-bit machines are supported')
raise NotImplementedError('Only 64-bit machines are supported')
is_arm = platform.machine().startswith('arm')
print('is_arm', is_arm)

View File

@@ -175,10 +175,6 @@ def hyperparameters(routes_arr: list) -> list:
if routes_arr[0].strategy.hp is None:
return []
hp = []
# only for the first route
for key in routes_arr[0].strategy.hp:
hp.append([
return [[
key, routes_arr[0].strategy.hp[key]
])
return hp
] for key in routes_arr[0].strategy.hp]

View File

@@ -34,34 +34,34 @@ def run():
def _candle(migrator):
fields = []
if 'candle' in database.db.get_tables():
candle_columns = database.db.get_columns('candle')
fields = []
_migrate(migrator, fields, candle_columns, 'candle')
def _completed_trade(migrator):
fields = []
if 'completedtrade' in database.db.get_tables():
completedtrade_columns = database.db.get_columns('completedtrade')
fields = []
_migrate(migrator, fields, completedtrade_columns, 'completedtrade')
def _daily_balance(migrator):
fields = []
if 'dailybalance' in database.db.get_tables():
dailybalance_columns = database.db.get_columns('dailybalance')
fields = []
_migrate(migrator, fields, dailybalance_columns, 'dailybalance')
def _log(migrator):
fields = []
if 'log' in database.db.get_tables():
log_columns = database.db.get_columns('log')
fields = []
_migrate(migrator, fields, log_columns, 'log')
@@ -79,26 +79,26 @@ def _order(migrator):
def _orderbook(migrator):
fields = []
if 'orderbook' in database.db.get_tables():
orderbook_columns = database.db.get_columns('orderbook')
fields = []
_migrate(migrator, fields, orderbook_columns, 'orderbook')
def _ticker(migrator):
fields = []
if 'ticker' in database.db.get_tables():
ticker_columns = database.db.get_columns('ticker')
fields = []
_migrate(migrator, fields, ticker_columns, 'ticker')
def _trade(migrator):
fields = []
if 'trade' in database.db.get_tables():
trade_columns = database.db.get_columns('trade')
fields = []
_migrate(migrator, fields, trade_columns, 'trade')
@@ -136,12 +136,10 @@ def _migrate(migrator, fields, columns, table):
)
print(
f"'{field['name']}' field successfully updated to accept to reject nullable values in the '{table}' table.")
# if column name doesn't not already exist
elif field['action'] == 'add':
migrate(
migrator.add_column(table, field['name'], field['type'])
)
print(f"'{field['name']}' field successfully added to '{table}' table.")
else:
if field['action'] == 'add':
migrate(
migrator.add_column(table, field['name'], field['type'])
)
print(f"'{field['name']}' field successfully added to '{table}' table.")
else:
print(f"'{field['name']}' field does not exist in '{table}' table.")
print(f"'{field['name']}' field does not exist in '{table}' table.")

View File

@@ -28,9 +28,7 @@ class Progressbar:
@property
def current(self):
if self.is_finished:
return 100
return round(self.index / self.length * 100, 1)
return 100 if self.is_finished else round(self.index / self.length * 100, 1)
@property
def average_execution_seconds(self):
@@ -38,9 +36,7 @@ class Progressbar:
@property
def remaining_index(self):
if self.is_finished:
return 0
return self.length - self.index
return 0 if self.is_finished else self.length - self.index
@property
def estimated_remaining_seconds(self):

View File

@@ -28,7 +28,7 @@ if jh.is_jesse_project():
def sync_publish(event: str, msg):
if jh.is_unit_testing():
raise EnvironmentError('sync_publish() should be NOT called during testing. There must be something wrong')
raise OSError('sync_publish() should be NOT called during testing. There must be something wrong')
sync_redis.publish(
f"{ENV_VALUES['APP_PORT']}:channel:1", json.dumps({
@@ -51,7 +51,7 @@ async def async_publish(event: str, msg):
def process_status(pid=None) -> str:
if jh.is_unit_testing():
raise EnvironmentError('process_status() is not meant to be called in unit tests')
raise OSError('process_status() is not meant to be called in unit tests')
if pid is None:
pid = jh.get_pid()

View File

@@ -144,15 +144,12 @@ def portfolio_metrics() -> dict:
def info() -> List[List[Union[str, Any]]]:
return [
[
return [[
jh.timestamp_to_time(w['time'])[11:19],
f"{w['message'][:70]}.."
if len(w['message']) > 70
else w['message'],
]
for w in store.logs.info[::-1][0:5]
]
] for w in store.logs.info[::-1][:5]]
def watch_list() -> List[List[Union[str, str]]]:
@@ -184,15 +181,12 @@ def watch_list() -> List[List[Union[str, str]]]:
def errors() -> List[List[Union[str, Any]]]:
return [
[
return [[
jh.timestamp_to_time(w['time'])[11:19],
f"{w['message'][:70]}.."
if len(w['message']) > 70
else w['message'],
]
for w in store.logs.errors[::-1][0:5]
]
] for w in store.logs.errors[::-1][:5]]
def orders():
@@ -219,4 +213,4 @@ def orders():
'created_at': o.created_at,
'canceled_at': o.canceled_at,
'executed_at': o.executed_at,
} for o in route_orders[::-1][0:5]]
} for o in route_orders[::-1][:5]]

View File

@@ -42,7 +42,6 @@ def load_required_candles(exchange: str, symbol: str, start_date_str: str, finis
# if cache exists
if cached_value:
candles_tuple = cached_value
# not cached, get and cache for later calls in the next 5 minutes
else:
# fetch from database
candles_tuple = tuple(
@@ -57,7 +56,7 @@ def load_required_candles(exchange: str, symbol: str, start_date_str: str, finis
)
# cache it for near future calls
cache.set_value(cache_key, candles_tuple, expire_seconds=60 * 60 * 24 * 7)
cache.set_value(cache_key, candles_tuple, expire_seconds=60**2 * 24 * 7)
candles = np.array(candles_tuple)

View File

@@ -20,7 +20,7 @@ def generate(name: str) -> JSONResponse:
shutil.copytree(f'{dirname}/ExampleStrategy', path)
# replace 'ExampleStrategy' with the name of the new strategy
with open(f"{path}/__init__.py", "rt") as fin:
with open(f"{path}/__init__.py") as fin:
data = fin.read()
data = data.replace('ExampleStrategy', name)
with open(f"{path}/__init__.py", "wt") as fin:

View File

@@ -2,7 +2,7 @@ from tabulate import tabulate
from typing import Union
def key_value(data, title: str, alignments: Union[list, tuple] = None, uppercase_title: bool = True) -> None:
table = [d for d in data]
table = list(data)
if alignments is None:
print(tabulate(table, headers=[title.upper() if uppercase_title else title, ''], tablefmt="presto"))

View File

@@ -9,6 +9,5 @@ def validate_routes(router) -> None:
'No routes found. Please add at least one route at: routes.py\nMore info: https://docs.jesse.trade/docs/routes.html#routing')
# validation for number of routes in the live mode
if jh.is_live():
if len(router.routes) > 5:
logger.broadcast_error_without_logging('Too many routes (not critical, but use at your own risk): Using that more than 5 routes in live/paper trading is not recommended because exchange WS connections are often not reliable for handling that much traffic.')
if jh.is_live() and len(router.routes) > 5:
logger.broadcast_error_without_logging('Too many routes (not critical, but use at your own risk): Using that more than 5 routes in live/paper trading is not recommended because exchange WS connections are often not reliable for handling that much traffic.')

View File

@@ -291,11 +291,7 @@ class CandlesState:
# no need to worry for forming candles when timeframe == 1m
if timeframe == '1m':
arr: DynamicNumpyArray = self.get_storage(exchange, symbol, '1m')
if len(arr) == 0:
return np.zeros((0, 6))
else:
return arr[:]
return np.zeros((0, 6)) if len(arr) == 0 else arr[:]
# other timeframes
dif, long_key, short_key = self.forming_estimation(exchange, symbol, timeframe)
long_count = len(self.get_storage(exchange, symbol, timeframe))
@@ -328,11 +324,7 @@ class CandlesState:
# no need to worry for forming candles when timeframe == 1m
if timeframe == '1m':
arr: DynamicNumpyArray = self.get_storage(exchange, symbol, '1m')
if len(arr) == 0:
return np.zeros((0, 6))
else:
return arr[-1]
return np.zeros((0, 6)) if len(arr) == 0 else arr[-1]
# other timeframes
dif, long_key, short_key = self.forming_estimation(exchange, symbol, timeframe)
long_count = len(self.get_storage(exchange, symbol, timeframe))
@@ -344,7 +336,4 @@ class CandlesState:
timeframe, self.storage[short_key][short_count - dif:short_count],
True
)
if long_count == 0:
return np.zeros((0, 6))
else:
return self.storage[long_key][-1]
return np.zeros((0, 6)) if long_count == 0 else self.storage[long_key][-1]

View File

@@ -73,9 +73,7 @@ class Strategy(ABC):
self.broker = Broker(self.position, self.exchange, self.symbol, self.timeframe)
if self.hp is None and len(self.hyperparameters()) > 0:
self.hp = {}
for dna in self.hyperparameters():
self.hp[dna['name']] = dna['default']
self.hp = {dna['name']: dna['default'] for dna in self.hyperparameters()}
@property
def _price_precision(self) -> int:
@@ -1210,14 +1208,9 @@ class Strategy(ABC):
@property
def all_positions(self) -> Dict[str, Position]:
positions_dict = {}
for r in self.routes:
positions_dict[r.symbol] = r.strategy.position
return positions_dict
return {r.symbol: r.strategy.position for r in self.routes}
@property
def portfolio_value(self) -> float:
total_position_values = 0
for key, p in self.all_positions.items():
total_position_values += p.pnl
total_position_values = sum(p.pnl for key, p in self.all_positions.items())
return (total_position_values + self.capital) * self.leverage

View File

@@ -4,28 +4,21 @@ from jesse.strategies import Strategy
class TestCanCancelEntryOrdersAfterOpenPositionLong1(Strategy):
def before(self) -> None:
if self.price == 12:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'ACTIVE'
assert self.orders[1].price == 9
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'ACTIVE'
assert self.orders[2].price == 8
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'LIMIT', 'ACTIVE', 9)
self._fake_order(2, 'LIMIT', 'ACTIVE', 8)
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'CANCELED'
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'CANCELED'
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return self.price == 10

View File

@@ -4,28 +4,21 @@ from jesse.strategies import Strategy
class TestCanCancelEntryOrdersAfterOpenPositionLong2(Strategy):
def before(self) -> None:
if self.price == 12:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'ACTIVE'
assert self.orders[1].price == 9
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'ACTIVE'
assert self.orders[2].price == 8
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'LIMIT', 'ACTIVE', 9)
self._fake_order(2, 'LIMIT', 'ACTIVE', 8)
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'CANCELED'
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'CANCELED'
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return self.price == 10

View File

@@ -4,28 +4,21 @@ from jesse.strategies import Strategy
class TestCanCancelEntryOrdersAfterOpenPositionShort1(Strategy):
def before(self) -> None:
if self.price == 12:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
assert self.orders[1].type == 'STOP'
assert self.orders[1].status == 'ACTIVE'
assert self.orders[1].price == 9
assert self.orders[2].type == 'STOP'
assert self.orders[2].status == 'ACTIVE'
assert self.orders[2].price == 8
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'STOP', 'ACTIVE', 9)
self._fake_order(2, 'STOP', 'ACTIVE', 8)
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
assert self.orders[1].type == 'STOP'
assert self.orders[1].status == 'CANCELED'
assert self.orders[2].type == 'STOP'
assert self.orders[2].status == 'CANCELED'
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return False

View File

@@ -4,28 +4,21 @@ from jesse.strategies import Strategy
class TestCanCancelEntryOrdersAfterOpenPositionShort2(Strategy):
def before(self) -> None:
if self.price == 12:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
assert self.orders[1].type == 'STOP'
assert self.orders[1].status == 'ACTIVE'
assert self.orders[1].price == 9
assert self.orders[2].type == 'STOP'
assert self.orders[2].status == 'ACTIVE'
assert self.orders[2].price == 8
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'STOP', 'ACTIVE', 9)
self._fake_order(2, 'STOP', 'ACTIVE', 8)
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
assert self.orders[1].type == 'STOP'
assert self.orders[1].status == 'CANCELED'
assert self.orders[2].type == 'STOP'
assert self.orders[2].status == 'CANCELED'
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return False

View File

@@ -10,23 +10,21 @@ class TestMultipleEntryOrdersUpdateEntryLongPositions(Strategy):
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'ACTIVE'
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'CANCELED'
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'CANCELED'
assert self.orders[3].type == 'MARKET'
assert self.orders[3].status == 'EXECUTED'
assert self.orders[3].price == 13
self._fake_order(3, 'MARKET', 'EXECUTED', 13)
self._fake_order(4, 'LIMIT', 'ACTIVE', 10)
assert self.orders[4].type == 'LIMIT'
assert self.orders[4].status == 'ACTIVE'
assert self.orders[4].price == 10
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return self.price == 10

View File

@@ -6,33 +6,19 @@ import jesse.helpers as jh
class TestMultipleEntryOrdersUpdateEntryShortPositions(Strategy):
def before(self) -> None:
if self.price == 12:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'LIMIT', 'ACTIVE', 20)
elif self.price == 15:
self._fake_order(0, 'MARKET', 'EXECUTED', 10)
self._fake_order(1, 'LIMIT', 'CANCELED', 20)
self._fake_order(2, 'LIMIT', 'CANCELED', 21)
self._fake_order(3, 'MARKET', 'EXECUTED', 13)
self._fake_order(4, 'LIMIT', 'ACTIVE', 22)
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'ACTIVE'
assert self.orders[1].price == 20
if self.price == 15:
assert self.orders[0].type == 'MARKET'
assert self.orders[0].status == 'EXECUTED'
assert self.orders[0].price == 10
assert self.orders[1].type == 'LIMIT'
assert self.orders[1].status == 'CANCELED'
assert self.orders[1].price == 20
assert self.orders[2].type == 'LIMIT'
assert self.orders[2].status == 'CANCELED'
assert self.orders[2].price == 21
assert self.orders[3].type == 'MARKET'
assert self.orders[3].status == 'EXECUTED'
assert self.orders[3].price == 13
assert self.orders[4].type == 'LIMIT'
assert self.orders[4].status == 'ACTIVE'
assert self.orders[4].price == 22
def _fake_order(self, i, type, status, price):
assert self.orders[i].type == type
assert self.orders[i].status == status
assert self.orders[i].price == price
def should_long(self) -> bool:
return False

View File

@@ -9,7 +9,7 @@ class TestPortfolioValue(Strategy):
# print('\nstarting: self.portfolio_value', self.portfolio_value)
assert self.portfolio_value == 10_000
if self.index == 10:
elif self.index == 10:
# print('=========')
# print(self.symbol, 'value', self.available_margin, self.positions['ETH-USDT'].value, self.positions['BTC-USDT'].value)
# print(self.symbol, 'PNL', self.positions['ETH-USDT'].pnl, self.positions['BTC-USDT'].pnl)

View File

@@ -79,10 +79,7 @@ def crossed(series1: np.ndarray, series2: Union[float, int, np.ndarray], directi
if direction is None:
return cross_above or cross_below
if direction == "above":
return cross_above
else:
return cross_below
return cross_above if direction == "above" else cross_below
def estimate_risk(entry_price: float, stop_price: float) -> float:

View File

@@ -37,7 +37,7 @@ REQUIRED_PACKAGES = [
'aiofiles'
]
with open("README.md", "r", encoding="utf-8") as f:
with open("README.md", encoding="utf-8") as f:
long_description = f.read()
setup(

View File

@@ -16,12 +16,13 @@ def test_backtesting_one_route():
]
config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'futures'
candles = {}
key = jh.key(exchanges.SANDBOX, 'BTC-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': range_candles(5 * 20)
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': range_candles(5 * 20),
}
}
# run backtest (dates are fake just to pass)

View File

@@ -45,7 +45,7 @@ def test_fill_absent_candles_beginning_middle_end():
assert candles[-1]['timestamp'] == smaller_data_set[-1]['timestamp']
# Should fill if candles in the middle are absent
candles = smaller_data_set[0:3] + smaller_data_set[5:7]
candles = smaller_data_set[:3] + smaller_data_set[5:7]
assert len(candles) == 5
candles = importer._fill_absent_candles(candles, start, end)
assert len(candles) == 7
@@ -53,7 +53,7 @@ def test_fill_absent_candles_beginning_middle_end():
assert candles[-1]['timestamp'] == smaller_data_set[-1]['timestamp']
# Should fill if candles in the ending are absent
candles = smaller_data_set[0:5]
candles = smaller_data_set[:5]
assert len(candles) == 5
candles = importer._fill_absent_candles(candles, start, end)
assert len(candles) == 7

View File

@@ -128,18 +128,29 @@ def test_filters():
def test_forming_candles():
reset_config()
routes = [
{'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test19'}
]
extra_routes = [
{'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_15}
{
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'timeframe': timeframes.MINUTE_5,
'strategy': 'Test19',
}
]
extra_routes = [
{
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'timeframe': timeframes.MINUTE_15,
}
]
candles = {}
key = jh.key(exchanges.SANDBOX, 'BTC-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': test_candles_0
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': test_candles_0,
}
}
backtest_mode.run(False, {}, routes, extra_routes, '2019-04-01', '2019-04-02', candles)
@@ -166,15 +177,21 @@ def test_is_smart_enough_to_open_positions_via_market_orders():
set_up()
routes = [
{'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_1, 'strategy': 'Test05'}
{
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'timeframe': timeframes.MINUTE_1,
'strategy': 'Test05',
}
]
candles = {}
key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1,
}
}
# run backtest (dates are fake just to pass)
@@ -211,15 +228,21 @@ def test_is_smart_enough_to_open_positions_via_stop_orders():
set_up()
routes = [
{'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test06'}
{
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'timeframe': timeframes.MINUTE_5,
'strategy': 'Test06',
}
]
candles = {}
key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1,
}
}
# run backtest (dates are fake just to pass)
@@ -278,12 +301,13 @@ def test_modifying_stop_loss_after_part_of_position_is_already_reduced_with_stop
list(range(1, 10)) + list(range(10, 1, -1))
)
candles = {}
key = jh.key(exchanges.SANDBOX, 'BTC-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': generated_candles
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'BTC-USDT',
'candles': generated_candles,
}
}
backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)
@@ -688,15 +712,21 @@ def test_updating_stop_loss_and_take_profit_after_opening_the_position():
set_up()
routes = [
{'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_1, 'strategy': 'Test07'}
{
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'timeframe': timeframes.MINUTE_1,
'strategy': 'Test07',
}
]
candles = {}
key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
candles[key] = {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1
candles = {
key: {
'exchange': exchanges.SANDBOX,
'symbol': 'ETH-USDT',
'candles': test_candles_1,
}
}
# run backtest (dates are fake just to pass)

View File

@@ -20,10 +20,10 @@ def test_routes():
store.reset(True)
# now assert it's working as expected
assert set(config['app']['trading_exchanges']) == set([exchanges.SANDBOX, exchanges.BITFINEX])
assert set(config['app']['trading_symbols']) == set(['BTC-USD', 'ETH-USD'])
assert set(config['app']['trading_timeframes']) == set([timeframes.HOUR_3, timeframes.MINUTE_15])
assert set(config['app']['considering_exchanges']) == set([exchanges.SANDBOX, exchanges.BITFINEX])
assert set(config['app']['considering_symbols']) == set(['BTC-USD', 'ETH-USD', 'EOS-USD'])
assert set(config['app']['considering_timeframes']) == set(
[timeframes.MINUTE_1, timeframes.HOUR_3, timeframes.MINUTE_15, timeframes.HOUR_1])
assert set(config['app']['trading_exchanges']) == {exchanges.SANDBOX, exchanges.BITFINEX}
assert set(config['app']['trading_symbols']) == {'BTC-USD', 'ETH-USD'}
assert set(config['app']['trading_timeframes']) == {timeframes.HOUR_3, timeframes.MINUTE_15}
assert set(config['app']['considering_exchanges']) == {exchanges.SANDBOX, exchanges.BITFINEX}
assert set(config['app']['considering_symbols']) == {'BTC-USD', 'ETH-USD', 'EOS-USD'}
assert set(config['app']['considering_timeframes']) == {
timeframes.MINUTE_1, timeframes.HOUR_3, timeframes.MINUTE_15, timeframes.HOUR_1}

View File

@@ -75,11 +75,12 @@ def test_get_candles_including_forming():
candles_to_add = range_candles(14)
store.candles.batch_add_candle(candles_to_add, 'Sandbox', 'BTC-USD', '1m')
store.candles.add_candle(
generate_candle_from_one_minutes(
'5m', candles_to_add[0:5], False
),
'Sandbox', 'BTC-USD', '5m'
generate_candle_from_one_minutes('5m', candles_to_add[:5], False),
'Sandbox',
'BTC-USD',
'5m',
)
store.candles.add_candle(
generate_candle_from_one_minutes(
'5m', candles_to_add[5:10], False
@@ -114,7 +115,7 @@ def test_get_forming_candle():
set_up()
candles_to_add = range_candles(13)
store.candles.batch_add_candle(candles_to_add[0:4], 'Sandbox', 'BTC-USD', '1m')
store.candles.batch_add_candle(candles_to_add[:4], 'Sandbox', 'BTC-USD', '1m')
forming_candle = store.candles.get_current_candle('Sandbox', 'BTC-USD', '5m')
assert forming_candle[0] == candles_to_add[0][0]
assert forming_candle[1] == candles_to_add[0][1]