Skip to content

Commit 316f552

Browse files
authored
Merge pull request #1991 from Drakkar-Software/dev
Update master
2 parents 831ad43 + d753dbe commit 316f552

File tree

10 files changed

+80
-44
lines changed

10 files changed

+80
-44
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ In short :
111111
[![Binance](../assets/okex-logo.png)](https://octobot.click/gh-okex)
112112
[![Binance](../assets/gateio-logo.png)](https://octobot.click/gh-gateio)
113113
[![Binance](../assets/huobi-logo.png)](https://octobot.click/gh-huobi)
114-
[![Bitmax](../assets/ascendex-logo.png)](https://octobot.click/gh-ascendex)
114+
[![Hollaex](../assets/hollaex-logo.png)](https://octobot.click/gh-hollaex)
115115
[![Coinbase](../assets/coinbasepro-logo.png)](https://pro.coinbase.com)
116116
[![Kucoin](../assets/kucoin-logo.png)](https://www.kucoin.com)
117117
[![Bitmex](../assets/bitmex-logo.png)](https://bitmex.com)
118+
[![Bitmax](../assets/ascendex-logo.png)](https://octobot.click/gh-ascendex)
118119

119120
Octobot supports many [exchanges](https://octobot.click/gh-exchanges) thanks to the [ccxt library](https://github.com/ccxt/ccxt).
120121
To activate trading on an exchange, just configure OctoBot with your api keys as described [on the exchange documentation](https://www.octobot.online/guides/#exchanges).

octobot/backtesting/independent_backtesting.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ cdef class IndependentBacktesting:
6161
cdef str _find_reference_market(self)
6262
cdef void _add_config_default_backtesting_values(self)
6363
cdef void _add_crypto_currencies_config(self)
64+
cdef void _init_exchange_type(self)

octobot/backtesting/independent_backtesting.py

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import octobot_commons.errors as errors
2424
import octobot_commons.logging as commons_logging
2525
import octobot_commons.pretty_printer as pretty_printer
26-
import octobot_commons.symbol_util as symbol_util
26+
import octobot_commons.symbols.symbol_util as symbol_util
2727
import octobot_commons.databases as databases
2828
import octobot_commons.optimization_campaign as optimization_campaign
2929
import octobot_commons.time_frame_manager as time_frame_manager
@@ -37,6 +37,7 @@
3737
import octobot_evaluators.constants as evaluator_constants
3838

3939
import octobot_trading.api as trading_api
40+
import octobot_trading.enums as trading_enums
4041

4142

4243
class IndependentBacktesting:
@@ -165,7 +166,7 @@ async def _post_backtesting_end_callback(self):
165166
@staticmethod
166167
def _get_market_delta(symbol, exchange_manager, min_timeframe):
167168
market_data = trading_api.get_symbol_historical_candles(
168-
trading_api.get_symbol_data(exchange_manager, symbol), min_timeframe)
169+
trading_api.get_symbol_data(exchange_manager, symbol.legacy_symbol()), min_timeframe)
169170
market_begin = market_data[enums.PriceIndexes.IND_PRICE_CLOSE.value][0]
170171
market_end = market_data[enums.PriceIndexes.IND_PRICE_CLOSE.value][-1]
171172

@@ -186,7 +187,7 @@ async def _register_available_data(self):
186187
if exchange_name not in self.symbols_to_create_exchange_classes:
187188
self.symbols_to_create_exchange_classes[exchange_name] = []
188189
for symbol in description[backtesting_enums.DataFormatKeys.SYMBOLS.value]:
189-
self.symbols_to_create_exchange_classes[exchange_name].append(symbol)
190+
self.symbols_to_create_exchange_classes[exchange_name].append(symbol_util.parse_symbol(symbol))
190191

191192
def _init_default_config_values(self):
192193
self.risk = copy.deepcopy(self.octobot_origin_config[common_constants.CONFIG_TRADING][
@@ -241,9 +242,9 @@ def _get_exchanges_report(self, reference_market, trading_mode):
241242
exchange_name = trading_api.get_exchange_name(exchange_manager)
242243
for symbol in self.symbols_to_create_exchange_classes[exchange_name]:
243244
market_delta = self._get_market_delta(symbol, exchange_manager, min_timeframe)
244-
report[SYMBOL_REPORT].append({symbol: market_delta * 100})
245+
report[SYMBOL_REPORT].append({symbol.symbol_str: market_delta * 100})
245246
report[CHART_IDENTIFIERS].append({
246-
"symbol": symbol,
247+
"symbol": symbol.symbol_str,
247248
"exchange_id": exchange_id,
248249
"exchange_name": exchange_name,
249250
"time_frame": min_timeframe.value
@@ -286,7 +287,7 @@ def _log_trades_history(self, exchange_manager, exchange_name):
286287

287288
def _log_symbol_report(self, symbol, exchange_manager, min_time_frame):
288289
market_delta = self._get_market_delta(symbol, exchange_manager, min_time_frame)
289-
self.logger.info(f"{symbol} Profitability : {market_delta * 100}%")
290+
self.logger.info(f"{symbol.symbol_str} Profitability : {market_delta * 100}%")
290291

291292
def _log_global_report(self, exchange_manager):
292293
_, profitability, _, market_average_profitability, _ = trading_api.get_profitability_stats(exchange_manager)
@@ -310,6 +311,14 @@ def _log_global_report(self, exchange_manager):
310311
f"{round(backtesting_api.get_backtesting_duration(self.octobot_backtesting.backtesting), 3)} sec")
311312

312313
def _adapt_config(self):
314+
self._init_exchange_type()
315+
self.backtesting_config[common_constants.CONFIG_EXCHANGES] = copy.deepcopy(
316+
self.octobot_origin_config[common_constants.CONFIG_EXCHANGES])
317+
for exchange_details in self.backtesting_config[common_constants.CONFIG_EXCHANGES].values():
318+
exchange_details.pop(common_constants.CONFIG_EXCHANGE_KEY, None)
319+
exchange_details.pop(common_constants.CONFIG_EXCHANGE_SECRET, None)
320+
exchange_details.pop(common_constants.CONFIG_EXCHANGE_PASSWORD, None)
321+
exchange_details.pop(common_constants.CONFIG_EXCHANGE_SANDBOXED, None)
313322
self.backtesting_config[common_constants.CONFIG_TRADING][common_constants.CONFIG_TRADER_RISK] = self.risk
314323
self.backtesting_config[common_constants.CONFIG_TRADING][
315324
common_constants.CONFIG_TRADER_REFERENCE_MARKET] = self._find_reference_market()
@@ -323,6 +332,17 @@ def _adapt_config(self):
323332
self.backtesting_config[evaluator_constants.CONFIG_FORCED_TIME_FRAME] = self.forced_time_frames
324333
self._add_config_default_backtesting_values()
325334

335+
def _init_exchange_type(self):
336+
try:
337+
for exchange_name in self.symbols_to_create_exchange_classes:
338+
# use current profile config to create a spot/future/margin backtesting exchange
339+
self.octobot_backtesting.exchange_type_by_exchange[exchange_name] = \
340+
self.octobot_origin_config[common_constants.CONFIG_EXCHANGES].get(exchange_name, {}).\
341+
get(common_constants.CONFIG_EXCHANGE_TYPE, common_constants.DEFAULT_EXCHANGE_TYPE)
342+
except StopIteration:
343+
# use default exchange type
344+
pass
345+
326346
async def _generate_backtesting_id_if_missing(self):
327347
if self.backtesting_config[common_constants.CONFIG_BACKTESTING_ID] is None:
328348
run_dbs_identifier = databases.RunDatabasesIdentifier(
@@ -337,25 +357,31 @@ async def _generate_backtesting_id_if_missing(self):
337357
def _find_reference_market(self):
338358
ref_market_candidate = None
339359
ref_market_candidates = {}
340-
for pairs in self.symbols_to_create_exchange_classes.values():
341-
if self.octobot_backtesting.is_future and \
342-
trading_api.is_inverse_future_contract(self.octobot_backtesting.futures_contract_type):
343-
if len(pairs) > 1:
344-
self.logger.error(f"Only one trading pair is supported in inverse contracts backtesting. "
345-
f"Found: the following pairs: {pairs}")
360+
for symbols in self.symbols_to_create_exchange_classes.values():
361+
symbol = symbols[0]
362+
if next(iter(self.octobot_backtesting.exchange_type_by_exchange.values())) \
363+
== common_constants.CONFIG_EXCHANGE_FUTURE:
364+
if symbol.is_inverse():
365+
if not all([symbol.is_inverse() for symbol in symbols]):
366+
self.logger.error(f"Mixed inverse and linear contracts backtesting are not supported yet")
367+
self.octobot_backtesting.futures_contract_type = trading_enums.FutureContractType.INVERSE_PERPETUAL
368+
else:
369+
if not all([symbol.is_linear() for symbol in symbols]):
370+
self.logger.error(f"Mixed inverse and linear contracts backtesting are not supported yet")
371+
self.octobot_backtesting.futures_contract_type = trading_enums.FutureContractType.LINEAR_PERPETUAL
346372
# in inverse contracts, use BTC for BTC/USD trading as reference market
347-
return symbol_util.split_symbol(pairs[0])[0]
348-
for pair in pairs:
349-
base = symbol_util.split_symbol(pair)[1]
373+
return symbol.settlement_asset
374+
for symbol in symbols:
375+
quote = symbol.quote
350376
if ref_market_candidate is None:
351-
ref_market_candidate = base
352-
if base in ref_market_candidates:
353-
ref_market_candidates[base] += 1
377+
ref_market_candidate = quote
378+
if quote in ref_market_candidates:
379+
ref_market_candidates[quote] += 1
354380
else:
355-
ref_market_candidates[base] = 1
356-
if ref_market_candidate != base and \
357-
ref_market_candidates[ref_market_candidate] < ref_market_candidates[base]:
358-
ref_market_candidate = base
381+
ref_market_candidates[quote] = 1
382+
if ref_market_candidate != quote and \
383+
ref_market_candidates[ref_market_candidate] < ref_market_candidates[quote]:
384+
ref_market_candidate = quote
359385
return ref_market_candidate
360386

361387
def _add_config_default_backtesting_values(self):
@@ -366,11 +392,12 @@ def _add_config_default_backtesting_values(self):
366392
self.backtesting_config[common_constants.CONFIG_SIMULATOR][common_constants.CONFIG_ENABLED_OPTION] = True
367393

368394
def _add_crypto_currencies_config(self):
369-
for pairs in self.symbols_to_create_exchange_classes.values():
370-
for pair in pairs:
371-
if pair not in self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES]:
372-
self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES][pair] = {
395+
for symbols in self.symbols_to_create_exchange_classes.values():
396+
for symbol in symbols:
397+
symbol_id = symbol.legacy_symbol()
398+
if symbol_id not in self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES]:
399+
self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES][symbol_id] = {
373400
common_constants.CONFIG_CRYPTO_PAIRS: []
374401
}
375-
self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES][pair][
376-
common_constants.CONFIG_CRYPTO_PAIRS] = [pair]
402+
self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES][symbol_id][
403+
common_constants.CONFIG_CRYPTO_PAIRS] = [symbol_id]

octobot/backtesting/octobot_backtesting.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ cdef class OctoBotBacktesting:
3535
cdef public object start_timestamp
3636
cdef public object end_timestamp
3737
cdef public bint enable_logs
38-
cdef public bint is_future
38+
cdef public dict exchange_type_by_exchange
3939
cdef public object futures_contract_type
4040

4141
cpdef object memory_leak_checkup(self, list to_check_elements)

octobot/backtesting/octobot_backtesting.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def __init__(self, backtesting_config,
6363
self.start_timestamp = start_timestamp
6464
self.end_timestamp = end_timestamp
6565
self.enable_logs = enable_logs
66-
self.is_future = False
66+
self.exchange_type_by_exchange = {}
6767
self.futures_contract_type = trading_enums.FutureContractType.LINEAR_PERPETUAL
6868

6969
async def initialize_and_run(self):
@@ -232,14 +232,16 @@ async def _init_exchanges(self):
232232
end_timestamp=self.end_timestamp)
233233

234234
for exchange_class_string in self.symbols_to_create_exchange_classes.keys():
235+
is_future = self.exchange_type_by_exchange[exchange_class_string] == \
236+
commons_constants.CONFIG_EXCHANGE_FUTURE
235237
exchange_builder = trading_api.create_exchange_builder(self.backtesting_config, exchange_class_string) \
236238
.has_matrix(self.matrix_id) \
237239
.use_tentacles_setup_config(self.tentacles_setup_config) \
238240
.set_bot_id(self.bot_id) \
239241
.is_simulated() \
240242
.is_rest_only() \
241-
.is_future(self.is_future, self.futures_contract_type) \
242-
.is_backtesting(self.backtesting)
243+
.is_backtesting(self.backtesting) \
244+
.is_future(is_future, self.futures_contract_type)
243245
try:
244246
await exchange_builder.build()
245247
finally:

octobot/cli.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,13 @@ def _create_startup_config(logger):
128128

129129
def _read_config(config, logger):
130130
try:
131-
config.read(should_raise=False, fill_missing_fields=True)
131+
config.read(should_raise=True, fill_missing_fields=True)
132132
except errors.NoProfileError:
133133
_repair_with_default_profile(config, logger)
134134
config = _create_configuration()
135135
config.read(should_raise=False, fill_missing_fields=True)
136+
except Exception as e:
137+
raise errors.ConfigError(e)
136138

137139

138140
def _validate_config(config, logger):
@@ -245,9 +247,9 @@ def start_octobot(args):
245247

246248
commands.run_bot(bot, logger)
247249

248-
except errors.ConfigError:
250+
except errors.ConfigError as e:
249251
logger.error("OctoBot can't start without a valid " + common_constants.CONFIG_FILE
250-
+ " configuration file." + "\nYou can use " +
252+
+ " configuration file.\nError: " + str(e) + "\nYou can use " +
251253
constants.DEFAULT_CONFIG_FILE + " as an example to fix it.")
252254
os._exit(-1)
253255

octobot/community/community_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import octobot_commons.logging as logging
2323
import octobot_commons.configuration as configuration
2424
import octobot_commons.os_util as os_util
25-
import octobot_commons.symbol_util as symbol_util
25+
import octobot_commons.symbols.symbol_util as symbol_util
2626
import octobot_commons.authentication as authentication
2727

2828
import octobot_commons.constants as common_constants
@@ -179,7 +179,7 @@ def _get_traded_volumes(self):
179179
trades += trading_api.get_trade_history(exchange_manager, since=self.octobot_api.get_start_time())
180180
for trade in trades:
181181
# cost is in quote currency for a traded pair
182-
currency = symbol_util.split_symbol(trade.symbol)[-1]
182+
currency = symbol_util.parse_symbol(trade.symbol).quote
183183
if currency in volume_by_currency:
184184
volume_by_currency[currency] += float(trade.total_cost)
185185
else:

octobot/config/profile_schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
"properties": {
6363
"enabled": {
6464
"type": "boolean"
65+
},
66+
"exchange-type": {
67+
"type": "string"
6568
}
6669
},
6770
"required": [

octobot/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
# OctoBot urls
3030
OCTOBOT_WEBSITE_URL = os.getenv("OCTOBOT_ONLINE_URL", "https://www.octobot.online")
31-
OCTOBOT_DOCS_URL = os.getenv("DOCS_OCTOBOT_ONLINE_URL", "https://octobot.info")
31+
OCTOBOT_DOCS_URL = os.getenv("DOCS_OCTOBOT_ONLINE_URL", "https://www.octobot.info")
3232
EXCHANGES_DOCS_URL = os.getenv("DOCS_OCTOBOT_ONLINE_URL", "https://exchanges.octobot.info/")
3333
DEVELOPER_DOCS_URL = os.getenv("DOCS_OCTOBOT_ONLINE_URL", "https://developer.octobot.info/")
3434
OCTOBOT_ONLINE = os.getenv("TENTACLES_OCTOBOT_ONLINE_URL", "https://static.octobot.online")

requirements.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
cython==0.29.26
33

44
# Drakkar-Software requirements
5-
OctoBot-Commons==1.7.8
6-
OctoBot-Trading==2.2.6
7-
OctoBot-Evaluators==1.7.3
5+
OctoBot-Commons==1.7.10
6+
OctoBot-Trading==2.2.7
7+
OctoBot-Evaluators==1.7.4
88
OctoBot-Tentacles-Manager==2.7.0
9-
OctoBot-Services==1.3.1
10-
OctoBot-Backtesting==1.7.1
9+
OctoBot-Services==1.3.2
10+
OctoBot-Backtesting==1.7.2
1111
Async-Channel==2.0.13
1212
trading-backend==1.0.13
1313

0 commit comments

Comments
 (0)