Skip to content

Commit 5eaaf84

Browse files
authored
Merge pull request #2038 from Drakkar-Software/dev
master merge
2 parents c705ed6 + 0679319 commit 5eaaf84

21 files changed

+160
-71
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
*It is strongly advised to perform an update of your tentacles after updating OctoBot. (start.py tentacles --install --all)*
88

9+
## [0.4.10] - 2022-09-13
10+
### Updated
11+
- Beta tentacles
12+
### Fixed
13+
- Kucoin rate limit issues
14+
- Futures trading issues
15+
- Tentacles versioning in profile import
16+
917
## [0.4.9] - 2022-09-07
1018
### Updated
1119
- Beta tentacles

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OctoBot [0.4.9](https://octobot.click/gh-changelog)
1+
# OctoBot [0.4.10](https://octobot.click/gh-changelog)
22
[![PyPI](https://img.shields.io/pypi/v/OctoBot.svg)](https://octobot.click/gh-pypi)
33
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/e07fb190156d4efb8e7d07aaa5eff2e1)](https://app.codacy.com/gh/Drakkar-Software/OctoBot?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot&utm_campaign=Badge_Grade_Dashboard)[![Downloads](https://pepy.tech/badge/octobot/month)](https://pepy.tech/project/octobot)
44
[![Dockerhub](https://img.shields.io/docker/pulls/drakkarsoftware/octobot.svg)](https://octobot.click/gh-dockerhub)
@@ -46,11 +46,11 @@ Octobot's main feature is **evolution** : you can [install](https://www.octobot.
4646

4747
## Hardware requirements
4848
- CPU : 1 Core / 1GHz
49-
- RAM : 250 Mo
50-
- Disk : 1 Go
49+
- RAM : 250 MB
50+
- Disk : 1 GB
5151

5252
## Installation
53-
OctoBot's installation is **very simple**... because **very documented** ! See the [installation guides](https://www.octobot.online/guides/#installation) for more info.
53+
OctoBot's installation is **very simple**... because it is **very documented** ! See the [installation guides](https://www.octobot.online/guides/#installation) for more info.
5454

5555
#### [With executable](https://www.octobot.info/installation/with-binary)
5656
Follow the [2 steps installation guide](https://www.octobot.online/executable_installation/)
@@ -114,17 +114,16 @@ In short :
114114
[![Bitmax](../assets/ascendex-logo.png)](https://octobot.click/gh-ascendex)
115115

116116
Octobot supports many [exchanges](https://octobot.click/gh-exchanges) thanks to the [ccxt library](https://github.com/ccxt/ccxt).
117-
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).
117+
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).
118118

119119
## Disclaimer
120120
Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
121121
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
122122

123123
Always start by running a trading bot in simulation mode and do not engage money
124-
before you understand how it works and what profit/loss you should
125-
expect.
124+
before you understand how it works and what profit/loss you should expect.
126125

127-
Do not hesitate to read the source code and understand the mechanism of this bot.
126+
Please feel free to read the source code and understand the mechanism of this bot.
128127

129128
## License
130129
GNU General Public License v3.0 or later.

octobot/api/strategy_optimizer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ async def update_strategy_optimizer_total_runs(optimizer, runs):
3737

3838

3939
async def generate_and_save_strategy_optimizer_runs(trading_mode, tentacles_setup_config,
40-
optimizer_config, optimizer_id) -> list:
40+
optimizer_config, optimizer_id, queue_size) -> list:
4141
optimizer = StrategyDesignOptimizer(trading_mode, None, tentacles_setup_config,
42-
optimizer_config, optimizer_id)
42+
optimizer_config, optimizer_id, queue_size)
4343
return await optimizer.generate_and_save_run()
4444

4545

octobot/backtesting/independent_backtesting.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ cdef class IndependentBacktesting:
5858
cdef void _log_symbol_report(self, str symbol, object exchange_manager, object min_time_frame)
5959
cdef void _log_global_report(self, object exchange_manager)
6060
cdef void _adapt_config(self)
61-
cdef str _find_reference_market(self)
61+
cdef str _find_reference_market_and_update_contract_type(self)
6262
cdef void _add_config_default_backtesting_values(self)
6363
cdef void _add_crypto_currencies_config(self)
6464
cdef void _init_exchange_type(self)

octobot/backtesting/independent_backtesting.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def _adapt_config(self):
321321
exchange_details.pop(common_constants.CONFIG_EXCHANGE_SANDBOXED, None)
322322
self.backtesting_config[common_constants.CONFIG_TRADING][common_constants.CONFIG_TRADER_RISK] = self.risk
323323
self.backtesting_config[common_constants.CONFIG_TRADING][
324-
common_constants.CONFIG_TRADER_REFERENCE_MARKET] = self._find_reference_market()
324+
common_constants.CONFIG_TRADER_REFERENCE_MARKET] = self._find_reference_market_and_update_contract_type()
325325
self.backtesting_config[common_constants.CONFIG_SIMULATOR][
326326
common_constants.CONFIG_STARTING_PORTFOLIO] = self.starting_portfolio
327327
self.backtesting_config[common_constants.CONFIG_SIMULATOR][
@@ -333,12 +333,17 @@ def _adapt_config(self):
333333
self._add_config_default_backtesting_values()
334334

335335
def _init_exchange_type(self):
336+
forced_exchange_type = self.octobot_origin_config.get(common_constants.CONFIG_EXCHANGE_TYPE,
337+
common_constants.USE_CURRENT_PROFILE)
336338
try:
337339
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)
340+
if forced_exchange_type == common_constants.USE_CURRENT_PROFILE:
341+
# use current profile config to create a spot/future/margin backtesting exchange
342+
self.octobot_backtesting.exchange_type_by_exchange[exchange_name] = \
343+
self.octobot_origin_config[common_constants.CONFIG_EXCHANGES].get(exchange_name, {}).\
344+
get(common_constants.CONFIG_EXCHANGE_TYPE, common_constants.DEFAULT_EXCHANGE_TYPE)
345+
else:
346+
self.octobot_backtesting.exchange_type_by_exchange[exchange_name] = forced_exchange_type
342347
except StopIteration:
343348
# use default exchange type
344349
pass
@@ -354,25 +359,36 @@ async def _generate_backtesting_id_if_missing(self):
354359
await run_dbs_identifier.initialize()
355360
self.backtesting_config[common_constants.CONFIG_BACKTESTING_ID] = run_dbs_identifier.backtesting_id
356361

357-
def _find_reference_market(self):
362+
def _find_reference_market_and_update_contract_type(self):
358363
ref_market_candidate = None
359364
ref_market_candidates = {}
365+
forced_contract_type = self.octobot_origin_config.get(common_constants.CONFIG_CONTRACT_TYPE,
366+
common_constants.USE_CURRENT_PROFILE)
360367
for symbols in self.symbols_to_create_exchange_classes.values():
361368
symbol = symbols[0]
362369
if next(iter(self.octobot_backtesting.exchange_type_by_exchange.values())) \
363370
== 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
371+
if forced_contract_type == common_constants.USE_CURRENT_PROFILE:
372+
if symbol.is_inverse():
373+
if not all([symbol.is_inverse() for symbol in symbols]):
374+
self.logger.error(f"Mixed inverse and linear contracts backtesting are not supported yet")
375+
self.octobot_backtesting.futures_contract_type = \
376+
trading_enums.FutureContractType.INVERSE_PERPETUAL
377+
else:
378+
if not all([symbol.is_linear() for symbol in symbols]):
379+
self.logger.error(f"Mixed inverse and linear contracts backtesting are not supported yet")
380+
self.octobot_backtesting.futures_contract_type = \
381+
trading_enums.FutureContractType.LINEAR_PERPETUAL
382+
# in inverse contracts, use BTC for BTC/USD trading as reference market
383+
if symbol.settlement_asset:
384+
# only use settlement asset if available
385+
return symbol.settlement_asset
368386
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
372-
# in inverse contracts, use BTC for BTC/USD trading as reference market
373-
if symbol.settlement_asset:
374-
# only use settlement asset if available
375-
return symbol.settlement_asset
387+
self.octobot_backtesting.futures_contract_type = forced_contract_type
388+
return symbol.base \
389+
if forced_contract_type is trading_enums.FutureContractType.INVERSE_PERPETUAL \
390+
else symbol.quote
391+
376392
for symbol in symbols:
377393
quote = symbol.quote
378394
if ref_market_candidate is None:

octobot/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ def _load_or_create_tentacles(config, logger):
162162
if os.path.isfile(tentacles_manager_constants.USER_REFERENCE_TENTACLE_CONFIG_FILE_PATH):
163163
config.load_profiles_if_possible_and_necessary()
164164
tentacles_setup_config = tentacles_manager_api.get_tentacles_setup_config(
165-
config.get_tentacles_config_path())
165+
config.get_tentacles_config_path()
166+
)
166167
commands.run_update_or_repair_tentacles_if_necessary(config, tentacles_setup_config)
167168
else:
168169
# when no tentacles folder has been found

octobot/commands.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,36 @@ def _check_tentacles_install_exit():
9292
sys.exit(0)
9393

9494

95-
async def update_or_repair_tentacles_if_necessary(tentacles_setup_config, config):
96-
if not tentacles_manager_api.are_tentacles_up_to_date(tentacles_setup_config, constants.VERSION):
97-
logging.get_logger(COMMANDS_LOGGER_NAME).info("OctoBot tentacles are not up to date. Updating tentacles...")
95+
def _get_first_non_imported_profile_tentacles_setup_config(config):
96+
return tentacles_manager_api.get_tentacles_setup_config(
97+
config.get_non_imported_profiles()[0].get_tentacles_config_path()
98+
)
99+
100+
101+
async def update_or_repair_tentacles_if_necessary(selected_profile_tentacles_setup_config, config):
102+
local_profile_tentacles_setup_config = selected_profile_tentacles_setup_config
103+
logger = logging.get_logger(COMMANDS_LOGGER_NAME)
104+
if config.profile.imported:
105+
if not tentacles_manager_api.are_tentacles_up_to_date(selected_profile_tentacles_setup_config,
106+
constants.VERSION):
107+
selected_profile_tentacles_version = tentacles_manager_api.get_tentacles_installation_version(
108+
selected_profile_tentacles_setup_config
109+
)
110+
logger.warning(f"Current imported profile \"{config.profile.name}\" references tentacles in a different "
111+
f"version from the current OctoBot. Referenced version: "
112+
f"{selected_profile_tentacles_version}, current OctoBot version: {constants.VERSION}. "
113+
f"This profile might not work properly.")
114+
# only update tentacles based on local (non imported) profiles tentacles installation version
115+
local_profile_tentacles_setup_config = _get_first_non_imported_profile_tentacles_setup_config(config)
116+
if not tentacles_manager_api.are_tentacles_up_to_date(local_profile_tentacles_setup_config, constants.VERSION):
117+
logger.info("OctoBot tentacles are not up to date. Updating tentacles...")
98118
_check_tentacles_install_exit()
99119
if await install_or_update_tentacles(config):
100-
logging.get_logger(COMMANDS_LOGGER_NAME).info("OctoBot tentacles are now up to date.")
120+
logger.info("OctoBot tentacles are now up to date.")
101121
elif tentacles_manager_api.load_tentacles(verbose=True):
102-
logging.get_logger(COMMANDS_LOGGER_NAME).debug("OctoBot tentacles are up to date.")
122+
logger.debug("OctoBot tentacles are up to date.")
103123
else:
104-
logging.get_logger(COMMANDS_LOGGER_NAME).info("OctoBot tentacles are damaged. Installing default tentacles ...")
124+
logger.info("OctoBot tentacles are damaged. Installing default tentacles ...")
105125
_check_tentacles_install_exit()
106126
await install_or_update_tentacles(config)
107127

octobot/community/authentication.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class CommunityAuthentication(authentication.Authenticator):
3939
"""
4040
ALLOWED_TIME_DELAY = 1 * commons_constants.MINUTE_TO_SECONDS
4141
LOGIN_TIMEOUT = 20
42+
DEVICE_NOT_FOUND_RETRY_DELAY = 1
4243
AUTHORIZATION_HEADER = "authorization"
4344
SESSION_HEADER = "X-Session"
4445
GQL_AUTHORIZATION_HEADER = "Authorization"
@@ -111,8 +112,11 @@ async def _ensure_init_community_feed(self):
111112
await self._community_feed.start()
112113

113114
async def register_feed_callback(self, channel_type, callback, identifier=None):
114-
await self._ensure_init_community_feed()
115-
await self._community_feed.register_feed_callback(channel_type, callback, identifier=identifier)
115+
try:
116+
await self._ensure_init_community_feed()
117+
await self._community_feed.register_feed_callback(channel_type, callback, identifier=identifier)
118+
except errors.DeviceError as e:
119+
self.logger.error(f"Impossible to connect to community signals: {e}")
116120

117121
async def send(self, message, channel_type, identifier=None):
118122
"""
@@ -229,25 +233,36 @@ async def update_selected_device(self):
229233
self.user_account.flush_device_details()
230234
await self._load_device_if_selected()
231235
if not self.user_account.has_selected_device_data():
232-
self.logger.info("No selected device. Please select a device to enable your community features.")
236+
self.logger.info(self.user_account.NO_SELECTED_DEVICE_DESC)
233237

234238
async def _load_device_if_selected(self):
235239
# 1. use user selected device id if any
236240
if saved_uuid := self._get_saved_gql_device_id():
237-
await self.select_device(saved_uuid)
238-
else:
239-
# 2. fetch all user devices and create one if none, otherwise ask use for which one to use
240-
await self.load_user_devices()
241-
if len(self.user_account.get_all_user_devices_raw_data()) == 0:
242-
await self.select_device(
243-
self.user_account.get_device_id(
244-
await self.create_new_device()
245-
)
241+
try:
242+
await self.select_device(saved_uuid)
243+
return
244+
except errors.DeviceNotFoundError:
245+
# proceed to 2.
246+
pass
247+
# 2. fetch all user devices and create one if none, otherwise ask use for which one to use
248+
await self.load_user_devices()
249+
if len(self.user_account.get_all_user_devices_raw_data()) == 0:
250+
await self.select_device(
251+
self.user_account.get_device_id(
252+
await self.create_new_device()
246253
)
247-
# more than one possible device, can't auto-select one
254+
)
255+
# more than one possible device, can't auto-select one
248256

249257
async def select_device(self, device_id):
250-
self.user_account.set_selected_device_raw_data(await self.fetch_device(device_id))
258+
fetched_device = await self.fetch_device(device_id)
259+
if fetched_device is None:
260+
# retry after some time, if still None, there is an issue
261+
await asyncio.sleep(self.DEVICE_NOT_FOUND_RETRY_DELAY)
262+
fetched_device = await self.fetch_device(device_id)
263+
if fetched_device is None:
264+
raise errors.DeviceNotFoundError(f"Can't find device with id: {device_id}")
265+
self.user_account.set_selected_device_raw_data(fetched_device)
251266
self.user_account.gql_device_id = device_id
252267
self._save_gql_device_id(self.user_account.gql_device_id)
253268
await self.on_new_device_select()

octobot/community/community_user_account.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
# You should have received a copy of the GNU General Public
1515
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
1616
import octobot.community.community_supports as community_supports
17+
import octobot.community.errors as errors
1718

1819

1920
class CommunityUserAccount:
2021
USER_DATA_CONTENT = "content"
22+
NO_SELECTED_DEVICE_DESC = "No selected device. Please select a device to enable your community features."
23+
2124

2225
def __init__(self):
2326
self.gql_user_id = None
@@ -51,7 +54,10 @@ def get_selected_device_raw_data(self):
5154
return self._selected_device_raw_data
5255

5356
def get_selected_device_uuid(self):
54-
return self.get_selected_device_raw_data().get("uuid", None)
57+
try:
58+
return self.get_selected_device_raw_data().get("uuid", None)
59+
except AttributeError:
60+
raise errors.DeviceError(self.NO_SELECTED_DEVICE_DESC)
5561

5662
@staticmethod
5763
def get_device_id(device):

octobot/community/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ class StatusCodeRequestError(RequestError):
2626

2727
class DeviceError(commons_authentication.UnavailableError):
2828
pass
29+
30+
31+
class DeviceNotFoundError(DeviceError):
32+
pass

0 commit comments

Comments
 (0)