Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions additional_tests/exchanges_tests/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ PHEMEX_KEY=
PHEMEX_SECRET=
PHEMEX_PASSWORD=
PHEMEX_SANDBOXED=true

POLYMARKET_KEY=
POLYMARKET_SECRET=
POLYMARKET_PASSWORD=
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class AbstractAuthenticatedExchangeTester:
ORDER_SIZE = 10 # % of portfolio to include in test orders
PORTFOLIO_TYPE_FOR_SIZE = trading_constants.CONFIG_PORTFOLIO_FREE
CONVERTS_ORDER_SIZE_BEFORE_PUSHING_TO_EXCHANGES = False
CONVERTS_ORDER_PRICE_BEFORE_PUSHING_TO_EXCHANGE = False
ORDER_PRICE_DIFF = 20 # % of price difference compared to current price for limit and stop orders
EXPECT_MISSING_ORDER_FEES_DUE_TO_ORDERS_TOO_OLD_FOR_RECENT_TRADES = False # when recent trades are limited and
# closed orders fees are taken from recent trades
Expand All @@ -86,6 +87,7 @@ class AbstractAuthenticatedExchangeTester:
OPEN_TIMEOUT = 15
# if >0: retry fetching open/cancelled orders when created/cancelled orders are not synchronised instantly
ORDER_IN_OPEN_AND_CANCELLED_ORDERS_TIMEOUT = 10
ORDER_IMPACTS_PORTFOLIO_FREE_BALANCE = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

CANCEL_TIMEOUT = 15
EDIT_TIMEOUT = 15
MIN_PORTFOLIO_SIZE = 1
Expand Down Expand Up @@ -616,13 +618,22 @@ async def inner_test_create_and_cancel_limit_orders(self, symbol=None, settlemen
assert await self.order_in_open_orders(open_orders, limit_order, symbol=symbol)
await self.check_can_get_order(limit_order)
await self.sleep_before_checking_portfolio()
# assert free portfolio amount is smaller than total amount
balance = await self.get_portfolio()
locked_currency = settlement_currency if side == trading_enums.TradeOrderSide.BUY else self.ORDER_CURRENCY
assert balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE] < \
balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL], (
f"FALSE: {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE]} < {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL]}"
)
if self.ORDER_IMPACTS_PORTFOLIO_FREE_BALANCE:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a else to make sure the free balance is == to total balance when this is false ?
In these tests, it's better to always test those behaviors to avoid creating blind spots when conditions are skipped

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

# assert free portfolio amount is smaller than total amount
balance = await self.get_portfolio()
locked_currency = settlement_currency if side == trading_enums.TradeOrderSide.BUY else self.ORDER_CURRENCY
assert balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE] < \
balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL], (
f"FALSE: {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE]} < {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL]}"
)
else:
# assert free portfolio amount equals total amount when orders don't impact free balance
balance = await self.get_portfolio()
locked_currency = settlement_currency if side == trading_enums.TradeOrderSide.BUY else self.ORDER_CURRENCY
assert balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE] == \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL], (
f"FALSE: {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_FREE]} == {balance[locked_currency][trading_constants.CONFIG_PORTFOLIO_TOTAL]}"
)
finally:
# don't leave buy_limit as open order
await self.cancel_order(limit_order)
Expand Down Expand Up @@ -1344,7 +1355,13 @@ def _check_fetched_order_dicts(self, orders: list[dict]):

def check_created_limit_order(self, order, price, size, side):
self._check_order(order, size, side)
assert order.origin_price == price, f"{order.origin_price} != {price}"
if self.CONVERTS_ORDER_PRICE_BEFORE_PUSHING_TO_EXCHANGE:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

# actual origin_price may vary due to price conversion
assert price * decimal.Decimal("0.8") <= order.origin_price <= price * decimal.Decimal("1.2"), (
f"FALSE: {price * decimal.Decimal('0.8')} <= {order.origin_price} <= {price * decimal.Decimal('1.2')}"
)
else:
assert order.origin_price == price, f"{order.origin_price} != {price}"
assert isinstance(order.filled_quantity, decimal.Decimal)
expected_type = personal_data.BuyLimitOrder \
if side is trading_enums.TradeOrderSide.BUY else personal_data.SellLimitOrder
Expand Down
130 changes: 130 additions & 0 deletions additional_tests/exchanges_tests/test_polymarket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# This file is part of OctoBot (https://github.com/Drakkar-Software/OctoBot)
# Copyright (c) 2025 Drakkar-Software, All rights reserved.
#
# OctoBot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# OctoBot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
import pytest

from additional_tests.exchanges_tests import abstract_authenticated_exchange_tester

try:
import tentacles.Trading.Exchange.polymarket.ccxt.polymarket_async
except ImportError:
pytest.skip(
reason=(
"Polymarket tentacle is not installed, skipping TestPolymarketAuthenticatedExchange"
)
)

# All test coroutines will be treated as marked.
pytestmark = pytest.mark.asyncio


class TestPolymarketAuthenticatedExchange(
abstract_authenticated_exchange_tester.AbstractAuthenticatedExchangeTester
):
# enter exchange name as a class variable here
EXCHANGE_NAME = "polymarket"
ORDER_CURRENCY = "will-bitcoin-replace-sha-256-before-2027"
SETTLEMENT_CURRENCY = "USDC"
EXPIRATION_DATE = "261231"
SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}:{SETTLEMENT_CURRENCY}-{EXPIRATION_DATE}"
ORDER_SIZE = 10 # % of portfolio to include in test orders
EXPECT_MISSING_FEE_IN_CANCELLED_ORDERS = False
CONVERTS_ORDER_SIZE_BEFORE_PUSHING_TO_EXCHANGES = True
CONVERTS_ORDER_PRICE_BEFORE_PUSHING_TO_EXCHANGE = True
ORDER_IMPACTS_PORTFOLIO_FREE_BALANCE = False

async def test_get_portfolio(self):
await super().test_get_portfolio()

async def test_get_portfolio_with_market_filter(self):
# pass if not implemented
pass

async def test_untradable_symbols(self):
# pass if not implemented
pass

async def test_get_max_orders_count(self):
# pass if not implemented
pass

async def test_get_account_id(self):
# pass if not implemented
pass

async def test_is_authenticated_request(self):
await super().test_is_authenticated_request()

async def test_invalid_api_key_error(self):
await super().test_invalid_api_key_error()

async def test_get_api_key_permissions(self):
# pass if not implemented
pass

async def test_missing_trading_api_key_permissions(self):
pass

async def test_api_key_ip_whitelist_error(self):
# pass if not implemented
pass

async def test_get_not_found_order(self):
await super().test_get_not_found_order()

async def test_is_valid_account(self):
# pass if not implemented
pass

async def test_get_special_orders(self):
# pass if not implemented
pass

async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()

async def test_create_and_fill_market_orders(self):
await super().test_create_and_fill_market_orders()

async def test_get_my_recent_trades(self):
await super().test_get_my_recent_trades()

async def test_get_closed_orders(self):
# pass if not implemented
pass

async def test_get_cancelled_orders(self):
# pass if not implemented
pass
Comment on lines +109 to +110
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As fetching cancelled orders is not supported, we can run this test as well, it will make sure fetching cancelled orders raises "unsupported error" and will start failing if fetching cancelled orders becomes available in the ccxt lib

Copy link
Member Author

@Herklos Herklos Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not able to run it as closed orders fetching is not available. It seems that the test requires at least canceled orders fetching or closed orders fetching to be supported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍


async def test_create_and_cancel_stop_orders(self):
# pass if not implemented
pass

async def test_edit_limit_order(self):
# pass if not implemented
pass

async def test_edit_stop_order(self):
# pass if not implemented
pass

async def test_create_single_bundled_orders(self):
# pass if not implemented
pass

async def test_create_double_bundled_orders(self):
# pass if not implemented
pass