Skip to content

Commit e3ac661

Browse files
authored
Merge pull request #3152 from Drakkar-Software/dev
Master update
2 parents ced9846 + e55f838 commit e3ac661

File tree

21 files changed

+302
-116
lines changed

21 files changed

+302
-116
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
architecture: ${{ matrix.arch }}
2727

2828
- name: Install dependencies
29-
run: pip install wheel && pip install --prefer-binary -r dev_requirements.txt -r requirements.txt
29+
run: pip install wheel && pip install --prefer-binary -r dev_requirements.txt -r requirements.txt -r full_requirements.txt
3030

3131
# - name: Black lint
3232
# run: black ${{ secrets.PACKAGE_FOLDER }} --diff --check
@@ -70,7 +70,7 @@ jobs:
7070
run: exit 1
7171

7272
- name: Install dependencies
73-
run: pip install wheel && pip install --prefer-binary -r dev_requirements.txt -r requirements.txt
73+
run: pip install wheel && pip install --prefer-binary -r dev_requirements.txt -r requirements.txt -r full_requirements.txt
7474

7575
- name: Install tentacles on Unix
7676
env:
@@ -137,7 +137,7 @@ jobs:
137137
architecture: ${{ matrix.arch }}
138138

139139
- name: Install dependencies
140-
run: pip install --prefer-binary -r dev_requirements.txt -r requirements.txt
140+
run: pip install --prefer-binary -r dev_requirements.txt -r requirements.txt -r full_requirements.txt
141141

142142
- name: Build sdist
143143
run: python setup.py sdist

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ 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+
## [2.0.15] - 2025-12-08
10+
### Breaking pip installation change
11+
To install the full OctoBot (equivalent to previous versions), OctoBot needs to be installed with the [full] parameter: `pip install octobot[full]`
12+
### Added
13+
- [TradingModes] add cancel policies
14+
- [DSL] add DSL and base keywords tentacles
15+
### Updated
16+
- Light installation: OctoBot can now be used with minimal dependencies when started with the USE_MINIMAL_LIBS=true environment variable
17+
- Full installation: to use the full OctoBot (with user interface, etc), install octobot[full]
18+
- [Exchanges] update to ccxt 4.5.22
19+
- [Hyperliquid] fix markets fetch and use uniform tickers
20+
- Typing: add typing to most OctoBot-Trading objects
21+
- [TradingView] deprecate email alerts
22+
### Fixed
23+
- [RaspberryPi]: Fix "Illegal instruction" crash
24+
- [Exchanges] fix proxy error during markets loading
25+
- [StaggeredOrders] fix rare orders error
26+
927
## [2.0.14] - 2025-10-29
1028
### Fixed
1129
- Made pyarrow dependency optionnal to prevent a rare .dll import error

Dockerfile

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
FROM python:3.10-slim-buster AS base
1+
FROM python:3.10-slim-bookworm AS base
22

33
WORKDIR /
44

55
# requires git to install requirements with git+https
6-
# Update to debian archive from https://gist.github.com/ishad0w/6ce1eb569c734880200c47923577426a
7-
RUN echo "deb http://archive.debian.org/debian buster main contrib non-free" > /etc/apt/sources.list \
8-
&& echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" >> /etc/apt/sources.list \
9-
&& echo "deb http://archive.debian.org/debian buster-backports main contrib non-free" >> /etc/apt/sources.list \
10-
&& apt-get update \
6+
RUN apt-get update \
117
&& apt-get install -y --no-install-recommends build-essential git gcc binutils libffi-dev libssl-dev libxml2-dev libxslt1-dev libxslt-dev libjpeg62-turbo-dev zlib1g-dev \
128
&& python -m venv /opt/venv
139

@@ -19,10 +15,10 @@ ENV PATH="/opt/venv/bin:$PATH"
1915

2016
COPY . .
2117
RUN pip install -U setuptools wheel pip>=20.0.0 \
22-
&& pip install --no-cache-dir --prefer-binary -r requirements.txt \
18+
&& pip install --no-cache-dir --prefer-binary -r requirements.txt -r full_requirements.txt \
2319
&& python setup.py install
2420

25-
FROM python:3.10-slim-buster
21+
FROM python:3.10-slim-bookworm
2622

2723
ARG TENTACLES_URL_TAG=""
2824
ENV TENTACLES_URL_TAG=$TENTACLES_URL_TAG
@@ -41,11 +37,7 @@ COPY docker/* /octobot/
4137
# 2. Install required packages
4238
# 3. Finish env setup
4339
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
44-
# Update to debian archive from https://gist.github.com/ishad0w/6ce1eb569c734880200c47923577426a
45-
RUN echo "deb http://archive.debian.org/debian buster main contrib non-free" > /etc/apt/sources.list \
46-
&& echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" >> /etc/apt/sources.list \
47-
&& echo "deb http://archive.debian.org/debian buster-backports main contrib non-free" >> /etc/apt/sources.list \
48-
&& apt-get update \
40+
RUN apt-get update \
4941
&& apt-get install -y --no-install-recommends curl libxslt-dev libxcb-xinput0 libjpeg62-turbo-dev zlib1g-dev libblas-dev liblapack-dev libatlas-base-dev libopenjp2-7 libtiff-dev \
5042
&& rm -rf /var/lib/apt/lists/* \
5143
&& ln -s /opt/venv/bin/OctoBot OctoBot # Make sure we use the virtualenv \

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ include README.md
44
include LICENSE
55
include CHANGELOG.md
66
include requirements.txt
7+
include full_requirements.txt
78

89
global-exclude *.c

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OctoBot [2.0.14](https://github.com/Drakkar-Software/OctoBot/blob/master/CHANGELOG.md)
1+
# OctoBot [2.0.15](https://github.com/Drakkar-Software/OctoBot/blob/master/CHANGELOG.md)
22
[![PyPI](https://img.shields.io/pypi/v/OctoBot.svg?logo=pypi)](https://pypi.org/project/OctoBot)
33
[![Downloads](https://pepy.tech/badge/octobot/month)](https://pepy.tech/project/octobot)
44
[![Dockerhub](https://img.shields.io/docker/pulls/drakkarsoftware/octobot.svg?logo=docker)](https://hub.docker.com/r/drakkarsoftware/octobot)

additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -554,16 +554,17 @@ async def test_create_and_cancel_limit_orders(self):
554554

555555
async def inner_test_create_and_cancel_limit_orders(self, symbol=None, settlement_currency=None, **kwargs):
556556
symbol = symbol or self.SYMBOL
557+
side = trading_enums.TradeOrderSide.BUY
557558
# # DEBUG tools p1, uncomment to create specific orders
558559
# symbol = "ADA/USDT"
559560
# # end debug tools
560561
market_status = self.exchange_manager.exchange.get_market_status(symbol)
561562
exchange_data = self.get_exchange_data(symbol=symbol)
562563
settlement_currency = settlement_currency or self.SETTLEMENT_CURRENCY
563-
price = self.get_order_price(await self.get_price(symbol=symbol), False, symbol=symbol)
564+
price = self.get_order_price(await self.get_price(symbol=symbol), side == trading_enums.TradeOrderSide.SELL, symbol=symbol)
564565
# 1. try with "normal" order size
565566
default_size = self.get_order_size(
566-
await self.get_portfolio(), price, symbol=symbol, settlement_currency=settlement_currency
567+
await self.get_portfolio(), price, symbol=symbol, settlement_currency=(settlement_currency if side == trading_enums.TradeOrderSide.BUY else self.ORDER_CURRENCY)
567568
)
568569
# self.check_order_size_and_price(default_size, price, symbol=symbol, allow_empty_size=self.CHECK_EMPTY_ACCOUNT)
569570
enable_min_size_check = False
@@ -604,26 +605,27 @@ async def inner_test_create_and_cancel_limit_orders(self, symbol=None, settlemen
604605
assert cancelled_orders == []
605606
return
606607
try:
607-
buy_limit = await self.create_limit_order(price, size, trading_enums.TradeOrderSide.BUY, symbol=symbol)
608+
limit_order = await self.create_limit_order(price, size, side, symbol=symbol)
608609
except trading_errors.AuthenticationError as err:
609610
raise trading_errors.AuthenticationError(
610611
f"inner_test_create_and_cancel_limit_orders#create_limit_order {err}"
611612
) from err
612613
try:
613-
self.check_created_limit_order(buy_limit, price, size, trading_enums.TradeOrderSide.BUY)
614-
assert await self.order_in_open_orders(open_orders, buy_limit, symbol=symbol)
615-
await self.check_can_get_order(buy_limit)
614+
self.check_created_limit_order(limit_order, price, size, side)
615+
assert await self.order_in_open_orders(open_orders, limit_order, symbol=symbol)
616+
await self.check_can_get_order(limit_order)
616617
# assert free portfolio amount is smaller than total amount
617618
balance = await self.get_portfolio()
618-
assert balance[settlement_currency][trading_constants.CONFIG_PORTFOLIO_FREE] < \
619-
balance[settlement_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL], (
620-
f"FALSE: {balance[settlement_currency][trading_constants.CONFIG_PORTFOLIO_FREE]} < {balance[settlement_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL]}"
619+
locked_currency = settlement_currency if side == trading_enums.TradeOrderSide.BUY else self.ORDER_CURRENCY
620+
assert balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE] < \
621+
balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL], (
622+
f"FALSE: {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE]} < {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL]}"
621623
)
622624
finally:
623625
# don't leave buy_limit as open order
624-
await self.cancel_order(buy_limit)
625-
assert await self.order_not_in_open_orders(open_orders, buy_limit, symbol=symbol)
626-
assert await self.order_in_cancelled_orders(cancelled_orders, buy_limit, symbol=symbol)
626+
await self.cancel_order(limit_order)
627+
assert await self.order_not_in_open_orders(open_orders, limit_order, symbol=symbol)
628+
assert await self.order_in_cancelled_orders(cancelled_orders, limit_order, symbol=symbol)
627629

628630
async def inner_test_cancel_uncancellable_order(self):
629631
if self.UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE:
@@ -636,39 +638,44 @@ async def test_create_and_fill_market_orders(self):
636638
await self.inner_test_create_and_fill_market_orders()
637639

638640
async def inner_test_create_and_fill_market_orders(self):
641+
side = trading_enums.TradeOrderSide.BUY
639642
portfolio = await self.get_portfolio()
640643
current_price = await self.get_price()
641-
price = self.get_order_price(current_price, False, price_diff=0)
642-
size = self.get_order_size(portfolio, price)
644+
order_currency = self.ORDER_CURRENCY if side == trading_enums.TradeOrderSide.SELL else self.SETTLEMENT_CURRENCY
645+
price = self.get_order_price(current_price, side == trading_enums.TradeOrderSide.SELL, price_diff=0)
646+
size = self.get_order_size(portfolio, price, settlement_currency=order_currency)
643647
if self.CHECK_EMPTY_ACCOUNT:
644648
assert size == trading_constants.ZERO
645649
return
646-
buy_market = await self.create_market_order(current_price, size, trading_enums.TradeOrderSide.BUY)
650+
first_market_order = await self.create_market_order(current_price, size, side)
647651
post_buy_portfolio = {}
648652
try:
649-
self.check_created_market_order(buy_market, size, trading_enums.TradeOrderSide.BUY)
650-
filled_order = await self.wait_for_fill(buy_market)
653+
self.check_created_market_order(first_market_order, size, side)
654+
filled_order = await self.wait_for_fill(first_market_order)
651655
parsed_filled_order = personal_data.create_order_instance_from_raw(
652656
self.exchange_manager.trader,
653657
filled_order
654658
)
655-
self._check_order(parsed_filled_order, size, trading_enums.TradeOrderSide.BUY)
659+
self._check_order(parsed_filled_order, size, side)
656660
await self.wait_for_order_exchange_id_in_trades(parsed_filled_order.exchange_order_id)
657661
await self.check_require_order_fees_from_trades(
658662
filled_order[trading_enums.ExchangeConstantsOrderColumns.EXCHANGE_ID.value]
659663
)
660664
self.check_raw_closed_orders([filled_order])
661665
post_buy_portfolio = await self.get_portfolio()
662-
self.check_portfolio_changed(portfolio, post_buy_portfolio, False)
666+
portfolio_increased = side == trading_enums.TradeOrderSide.SELL
667+
self.check_portfolio_changed(portfolio, post_buy_portfolio, portfolio_increased)
663668
finally:
664-
sell_size = self.get_sell_size_from_buy_order(buy_market)
669+
mirror_size = self.get_sell_size_from_buy_order(first_market_order)
665670
# sell: reset portfolio
666-
sell_market = await self.create_market_order(current_price, sell_size, trading_enums.TradeOrderSide.SELL)
667-
self.check_created_market_order(sell_market, sell_size, trading_enums.TradeOrderSide.SELL)
668-
await self.wait_for_fill(sell_market)
671+
other_side = trading_enums.TradeOrderSide.SELL if side == trading_enums.TradeOrderSide.BUY else trading_enums.TradeOrderSide.BUY
672+
second_market_order = await self.create_market_order(current_price, mirror_size, other_side)
673+
self.check_created_market_order(second_market_order, mirror_size, other_side)
674+
await self.wait_for_fill(second_market_order)
669675
post_sell_portfolio = await self.get_portfolio()
670676
if post_buy_portfolio:
671-
self.check_portfolio_changed(post_buy_portfolio, post_sell_portfolio, True)
677+
portfolio_increased = other_side == trading_enums.TradeOrderSide.SELL
678+
self.check_portfolio_changed(post_buy_portfolio, post_sell_portfolio, portfolio_increased)
672679

673680
async def check_require_order_fees_from_trades(self, filled_exchange_order_id, symbol=None):
674681
symbol = symbol or self.SYMBOL
@@ -771,8 +778,8 @@ async def inner_test_edit_limit_order(self):
771778
sell_limit = await self.create_limit_order(price, size, trading_enums.TradeOrderSide.SELL)
772779
self.check_created_limit_order(sell_limit, price, size, trading_enums.TradeOrderSide.SELL)
773780
assert await self.order_in_open_orders(open_orders, sell_limit)
774-
edited_price = self.get_order_price(current_price, True, price_diff=2*self.ORDER_PRICE_DIFF)
775-
edited_size = self.get_order_size(portfolio, price, order_size=2*self.ORDER_SIZE, settlement_currency=self._get_edit_order_settlement_currency())
781+
edited_price = self.get_order_price(current_price, True, price_diff=1.3*self.ORDER_PRICE_DIFF)
782+
edited_size = self.get_order_size(portfolio, price, order_size=1.3*self.ORDER_SIZE, settlement_currency=self._get_edit_order_settlement_currency())
776783
sell_limit = await self.edit_order(sell_limit, edited_price=edited_price, edited_quantity=edited_size)
777784
await self.wait_for_edit(sell_limit, edited_size)
778785
sell_limit = await self.get_order(sell_limit.exchange_order_id, sell_limit.symbol)
@@ -800,8 +807,8 @@ async def inner_test_edit_stop_order(self):
800807
)
801808
self.check_created_stop_order(stop_loss, price, size, trading_enums.TradeOrderSide.SELL)
802809
assert await self.order_in_open_orders(open_orders, stop_loss)
803-
edited_price = self.get_order_price(current_price, False, price_diff=2*self.ORDER_PRICE_DIFF)
804-
edited_size = self.get_order_size(portfolio, price, order_size=2*self.ORDER_SIZE, settlement_currency=self._get_edit_order_settlement_currency())
810+
edited_price = self.get_order_price(current_price, False, price_diff=1.3*self.ORDER_PRICE_DIFF)
811+
edited_size = self.get_order_size(portfolio, price, order_size=1.3*self.ORDER_SIZE, settlement_currency=self._get_edit_order_settlement_currency())
805812
stop_loss = await self.edit_order(stop_loss, edited_price=edited_price, edited_quantity=edited_size)
806813
await self.wait_for_edit(stop_loss, edited_size)
807814
stop_loss = await self.get_order(stop_loss.exchange_order_id, stop_loss.symbol)

additional_tests/exchanges_tests/test_hyperliquid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class TestHyperliquidAuthenticatedExchange(
2626
):
2727
# enter exchange name as a class variable here
2828
EXCHANGE_NAME = "hyperliquid"
29-
ORDER_CURRENCY = "HYPE"
29+
ORDER_CURRENCY = "ETH"
3030
SETTLEMENT_CURRENCY = "USDC"
3131
SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}"
3232
ORDER_SIZE = 25 # % of portfolio to include in test orders

full_requirements.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Drakkar-Software full requirements
2+
OctoBot-Commons[full]==1.9.91
3+
OctoBot-Trading[full]==2.4.236
4+
OctoBot-Evaluators[full]==1.9.9
5+
OctoBot-Tentacles-Manager[full]==2.9.19
6+
OctoBot-Services[full]==1.6.30
7+
OctoBot-Backtesting[full]==1.9.8
8+
9+
## Others
10+
colorlog==6.8.0
11+
12+
# Community
13+
gmqtt==0.7.0
14+
pgpy==0.6.0
15+
clickhouse-connect==0.8.18
16+
pyiceberg==0.10.0
17+
pydantic<2.12 # required for pyiceberg 0.10.0 https://github.com/apache/iceberg-python/issues/2590
18+
pyarrow==21.0.0
19+
20+
# used by ccxt for protobuf "websockets" such as mexc
21+
# lock protobuf to avoid using .rc versions
22+
protobuf==5.29.5

octobot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616

1717
PROJECT_NAME = "OctoBot"
1818
AUTHOR = "Drakkar-Software"
19-
VERSION = "2.0.14" # major.minor.revision
19+
VERSION = "2.0.15" # major.minor.revision
2020
LONG_VERSION = f"{VERSION}"

octobot/cli.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,42 @@
2121

2222
import packaging.version as packaging_version
2323

24-
import octobot_commons.os_util as os_util
25-
import octobot_commons.logging as logging
26-
import octobot_commons.configuration as configuration
27-
import octobot_commons.profiles as profiles
28-
import octobot_commons.authentication as authentication
29-
import octobot_commons.constants as common_constants
30-
import octobot_commons.errors as errors
31-
32-
import octobot_services.api as service_api
33-
34-
import octobot_tentacles_manager.api as tentacles_manager_api
35-
import octobot_tentacles_manager.cli as tentacles_manager_cli
36-
import octobot_tentacles_manager.constants as tentacles_manager_constants
37-
38-
# make tentacles importable
39-
sys.path.append(os.path.dirname(sys.executable))
40-
41-
import octobot.octobot as octobot_class
42-
import octobot.commands as commands
43-
import octobot.configuration_manager as configuration_manager
44-
import octobot.octobot_backtesting_factory as octobot_backtesting
45-
import octobot.constants as constants
46-
import octobot.enums as enums
47-
import octobot.disclaimer as disclaimer
48-
import octobot.logger as octobot_logger
49-
import octobot.community as octobot_community
50-
import octobot.community.errors
51-
import octobot.limits as limits
52-
24+
try:
25+
import octobot_commons.os_util as os_util
26+
import octobot_commons.logging as logging
27+
import octobot_commons.configuration as configuration
28+
import octobot_commons.profiles as profiles
29+
import octobot_commons.authentication as authentication
30+
import octobot_commons.constants as common_constants
31+
import octobot_commons.errors as errors
32+
33+
import octobot_services.api as service_api
34+
35+
import octobot_tentacles_manager.api as tentacles_manager_api
36+
import octobot_tentacles_manager.cli as tentacles_manager_cli
37+
import octobot_tentacles_manager.constants as tentacles_manager_constants
38+
39+
# make tentacles importable
40+
sys.path.append(os.path.dirname(sys.executable))
41+
42+
import octobot.octobot as octobot_class
43+
import octobot.commands as commands
44+
import octobot.configuration_manager as configuration_manager
45+
import octobot.octobot_backtesting_factory as octobot_backtesting
46+
import octobot.constants as constants
47+
import octobot.enums as enums
48+
import octobot.disclaimer as disclaimer
49+
import octobot.logger as octobot_logger
50+
import octobot.community as octobot_community
51+
import octobot.community.errors
52+
import octobot.limits as limits
53+
except ImportError as err:
54+
print(
55+
"Error importing OctoBot dependencies, please install OctoBot with the [full] option. "
56+
"Example: \"pip install -U octobot[full]\" "
57+
"(Error: {0}: {1})".format(err.__class__.__name__, str(err)), file=sys.stderr
58+
)
59+
sys.exit(-1)
5360

5461
def update_config_with_args(starting_args, config: configuration.Configuration, logger):
5562
try:

0 commit comments

Comments
 (0)