From 7e26de497d44b695aa6c7fd5d6d28cea6574f1ba Mon Sep 17 00:00:00 2001 From: Herklos Date: Tue, 24 Mar 2026 11:35:29 +0100 Subject: [PATCH] [Polymarket] Add ITM profile Signed-off-by: Herklos --- .../dsl_realtime_evaluator/__init__.py | 16 + .../config/DSLRealtimeEvaluator.json | 5 + .../dsl_realtime_evaluator.py | 282 ++++++++++++++++++ .../dsl_realtime_evaluator/metadata.json | 6 + .../resources/DSLRealtimeEvaluator.md | 1 + .../dsl_realtime_evaluator/tests/__init__.py | 0 .../exchange_operators/__init__.py | 4 + .../__init__.py | 10 + .../symbol_operators.py | 89 ++++++ .../ticker_operators.py | 130 ++++++++ .../python_std_operators/__init__.py | 5 + .../base_time_operators.py | 34 +++ .../Mode/dsl_trading_mode/dsl_trading.py | 12 +- .../itm_predictions_market/profile.json | 45 +++ .../specific_config/DSLRealtimeEvaluator.json | 6 + .../specific_config/DSLTradingMode.json | 3 + .../SimpleStrategyEvaluator.json | 9 + .../tentacles_config.json | 11 + 18 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/__init__.py create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/config/DSLRealtimeEvaluator.json create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/dsl_realtime_evaluator.py create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/metadata.json create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/resources/DSLRealtimeEvaluator.md create mode 100644 packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/tests/__init__.py create mode 100644 packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/symbol_operators.py create mode 100644 packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/ticker_operators.py create mode 100644 packages/tentacles/Meta/DSL_operators/python_std_operators/base_time_operators.py create mode 100644 packages/tentacles/profiles/itm_predictions_market/profile.json create mode 100644 packages/tentacles/profiles/itm_predictions_market/specific_config/DSLRealtimeEvaluator.json create mode 100644 packages/tentacles/profiles/itm_predictions_market/specific_config/DSLTradingMode.json create mode 100644 packages/tentacles/profiles/itm_predictions_market/specific_config/SimpleStrategyEvaluator.json create mode 100644 packages/tentacles/profiles/itm_predictions_market/tentacles_config.json diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/__init__.py b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/__init__.py new file mode 100644 index 000000000..85885abd6 --- /dev/null +++ b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/__init__.py @@ -0,0 +1,16 @@ +# Drakkar-Software OctoBot-Tentacles +# Copyright (c) Drakkar-Software, All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. +from .dsl_realtime_evaluator import DSLRealtimeEvaluator diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/config/DSLRealtimeEvaluator.json b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/config/DSLRealtimeEvaluator.json new file mode 100644 index 000000000..600a734e4 --- /dev/null +++ b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/config/DSLRealtimeEvaluator.json @@ -0,0 +1,5 @@ +{ + "trigger_channel": "ohlcv", + "dsl_script": "", + "time_frame": "1m" +} diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/dsl_realtime_evaluator.py b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/dsl_realtime_evaluator.py new file mode 100644 index 000000000..2a70e574e --- /dev/null +++ b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/dsl_realtime_evaluator.py @@ -0,0 +1,282 @@ +# Drakkar-Software OctoBot-Tentacles +# Copyright (c) Drakkar-Software, All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. +import asyncio +import typing + +import octobot_commons.constants as commons_constants +import octobot_commons.enums as commons_enums +import octobot_commons.errors as commons_errors +import octobot_commons.channels_name as channels_name +import octobot_commons.dsl_interpreter as dsl_interpreter +import octobot_evaluators.evaluators as evaluators +import octobot_evaluators.util as evaluators_util +import octobot_trading.exchange_channel as exchange_channels +import octobot_trading.api as trading_api + +import tentacles.Meta.DSL_operators as dsl_operators + + +TRIGGER_CHANNEL_OHLCV = "ohlcv" +TRIGGER_CHANNEL_KLINE = "kline" +TRIGGER_CHANNEL_TICKER = "ticker" +TRIGGER_CHANNEL_ALL_TICKERS = "all_tickers" + +ALL_TICKERS_DEFAULT_REFRESH_TIME = 64 +ALL_TICKERS_REFRESH_TIME_KEY = "all_tickers_refresh_time" + +TRIGGER_CHANNEL_TO_EXCHANGE_CHANNEL = { + TRIGGER_CHANNEL_OHLCV: channels_name.OctoBotTradingChannelsName.OHLCV_CHANNEL.value, + TRIGGER_CHANNEL_KLINE: channels_name.OctoBotTradingChannelsName.KLINE_CHANNEL.value, + TRIGGER_CHANNEL_TICKER: channels_name.OctoBotTradingChannelsName.TICKER_CHANNEL.value, +} + + +class DSLRealtimeEvaluator(evaluators.RealTimeEvaluator): + TRIGGER_CHANNEL_KEY = "trigger_channel" + DSL_SCRIPT_KEY = "dsl_script" + + def __init__(self, tentacles_setup_config): + super().__init__(tentacles_setup_config) + self.trigger_channel: str = TRIGGER_CHANNEL_OHLCV + self.dsl_script: str = "" + self.interpreter: typing.Optional[dsl_interpreter.Interpreter] = None + self.exchange_manager = None + self.triggered_symbol: str = "" + self.all_tickers_refresh_time: float = ALL_TICKERS_DEFAULT_REFRESH_TIME + self._all_tickers_task: typing.Optional[asyncio.Task] = None + self._current_tickers: dict[str, dict] = {} + + def init_user_inputs(self, inputs: dict) -> None: + self.time_frame = self.time_frame or self.UI.user_input( + commons_constants.CONFIG_TIME_FRAME, + commons_enums.UserInputTypes.OPTIONS, + commons_enums.TimeFrames.ONE_MINUTE.value, + inputs, + options=[tf.value for tf in commons_enums.TimeFrames], + title="Time frame: The time frame to observe (used for OHLCV and Kline channels).", + ) + self.trigger_channel = self.UI.user_input( + self.TRIGGER_CHANNEL_KEY, + commons_enums.UserInputTypes.OPTIONS, + TRIGGER_CHANNEL_OHLCV, + inputs, + options=[ + TRIGGER_CHANNEL_OHLCV, TRIGGER_CHANNEL_KLINE, + TRIGGER_CHANNEL_TICKER, TRIGGER_CHANNEL_ALL_TICKERS, + ], + title="Trigger channel: The data channel that triggers DSL evaluation. " + "'ohlcv' fires on candle close, 'kline' fires on every price tick, " + "'ticker' fires on ticker updates (~14-64s), " + "'all_tickers' periodically fetches ALL exchange tickers and evaluates each symbol.", + ) + self.all_tickers_refresh_time = float(self.UI.user_input( + ALL_TICKERS_REFRESH_TIME_KEY, + commons_enums.UserInputTypes.INT, + ALL_TICKERS_DEFAULT_REFRESH_TIME, + inputs, + title="All tickers refresh time (seconds): How often to fetch all tickers " + "(only used when trigger_channel is 'all_tickers').", + )) + self.dsl_script = str(self.UI.user_input( + self.DSL_SCRIPT_KEY, + commons_enums.UserInputTypes.TEXT, + "", + inputs, + other_schema_values={"minLength": 0}, + title="DSL condition: The DSL expression to evaluate. " + "The script result is used as eval_note when truthy, stays pending otherwise. " + "Available operators: close(), market_expiry(), now_ms(), triggered_symbol(), etc.", + )) + + async def start(self, bot_id: str) -> bool: + if trading_api is None or exchange_channels is None: + self.logger.error("Can't connect to trading channels: octobot_trading is not installed") + return False + exchange_id = trading_api.get_exchange_id_from_matrix_id( + self.exchange_name, self.matrix_id + ) + self.exchange_manager = trading_api.get_exchange_manager_from_exchange_id( + exchange_id + ) + self._create_interpreter() + self._prepare_dsl_script() + if self.trigger_channel == TRIGGER_CHANNEL_ALL_TICKERS: + self._all_tickers_task = asyncio.create_task( + self._all_tickers_update_loop() + ) + self.logger.info( + f"Started all_tickers update loop " + f"(refresh every {self.all_tickers_refresh_time}s)" + ) + return True + channel_name = TRIGGER_CHANNEL_TO_EXCHANGE_CHANNEL.get(self.trigger_channel) + if channel_name is None: + self.logger.error(f"Unknown trigger channel: {self.trigger_channel}") + return False + if self.trigger_channel == TRIGGER_CHANNEL_TICKER: + await exchange_channels.get_chan( + channel_name, exchange_id + ).new_consumer( + callback=self.ticker_callback, + symbol=self.symbol, + priority_level=self.priority_level, + ) + elif self.trigger_channel == TRIGGER_CHANNEL_KLINE: + await exchange_channels.get_chan( + channel_name, exchange_id + ).new_consumer( + callback=self.kline_callback, + symbol=self.symbol, + time_frame=self.available_time_frame, + priority_level=self.priority_level, + ) + elif self.trigger_channel == TRIGGER_CHANNEL_OHLCV: + await exchange_channels.get_chan( + channel_name, exchange_id + ).new_consumer( + callback=self.ohlcv_callback, + symbol=self.symbol, + time_frame=self.available_time_frame, + priority_level=self.priority_level, + ) + return True + + async def _all_tickers_update_loop(self): + while True: + try: + tickers = await self.exchange_manager.exchange.get_all_currencies_price_ticker() + if tickers: + self._current_tickers.update(tickers) + self.logger.debug( + f"Fetched {len(tickers)} tickers, evaluating DSL for each" + ) + for symbol in tickers: + await self._evaluate("", symbol, eval_time=0) + else: + self.logger.warning("No tickers returned from exchange") + except asyncio.CancelledError: + self.logger.debug("All tickers update loop cancelled") + return + except Exception as err: + self.logger.exception( + err, True, + f"Error fetching all tickers: {err}", + ) + await asyncio.sleep(self.all_tickers_refresh_time) + + async def ohlcv_callback( + self, exchange: str, exchange_id: str, + cryptocurrency: str, symbol: str, time_frame, candle, + ): + await self._evaluate( + cryptocurrency, symbol, + evaluators_util.get_eval_time(full_candle=candle, time_frame=time_frame), + ) + + async def kline_callback( + self, exchange: str, exchange_id: str, + cryptocurrency: str, symbol: str, time_frame, kline, + ): + await self._evaluate( + cryptocurrency, symbol, + evaluators_util.get_eval_time(kline=kline), + ) + + async def ticker_callback( + self, exchange: str, exchange_id: str, + cryptocurrency: str, symbol: str, ticker, + ): + await self._evaluate(cryptocurrency, symbol, eval_time=0) + + async def _evaluate( + self, cryptocurrency: str, symbol: str, eval_time: int, + ): + if self.interpreter is None: + self.logger.warning("DSL interpreter not initialized, skipping evaluation") + return + self.triggered_symbol = symbol + try: + result = await self.interpreter.compute_expression() + if result is None or result is False: + self.eval_note = commons_constants.START_PENDING_EVAL_NOTE + return + self.eval_note = result + except commons_errors.DSLInterpreterError as err: + self.logger.debug( + f"DSL evaluation skipped for {symbol}: {err}" + ) + self.eval_note = commons_constants.START_PENDING_EVAL_NOTE + return + except Exception as err: + self.logger.exception( + err, True, + f"Unexpected DSL evaluation error for {symbol}: {err}", + ) + self.eval_note = commons_constants.START_PENDING_EVAL_NOTE + return + await self.evaluation_completed( + cryptocurrency, symbol, self.available_time_frame, + eval_time=eval_time, + ) + + async def stop(self) -> None: + await super().stop() + if self._all_tickers_task is not None and not self._all_tickers_task.done(): + self._all_tickers_task.cancel() + try: + await self._all_tickers_task + except asyncio.CancelledError: + pass + self._all_tickers_task = None + + def _create_interpreter(self): + operators = ( + dsl_interpreter.get_all_operators() + + dsl_operators.create_ohlcv_operators(self.exchange_manager, None, None) + + dsl_operators.create_symbol_operators(self) + + dsl_operators.create_ticker_operators(self._current_tickers) + ) + self.interpreter = dsl_interpreter.Interpreter(operators) + + def _prepare_dsl_script(self): + if not self.dsl_script: + self.logger.warning("No DSL script configured") + return + try: + self.interpreter.prepare(self.dsl_script) + self.logger.info(f"DSL script successfully loaded: '{self.dsl_script}'") + except commons_errors.DSLInterpreterError as err: + self.logger.exception( + err, True, + f"Error when parsing DSL script '{self.dsl_script}': {err}", + ) + except Exception as err: + self.logger.exception( + err, True, + f"Unexpected error when parsing DSL script '{self.dsl_script}': {err}", + ) + + def set_default_config(self): + super().set_default_config() + self.specific_config[commons_constants.CONFIG_TIME_FRAME] = "1m" + + @classmethod + def get_is_symbol_wildcard(cls) -> bool: + return True + + @classmethod + def get_is_cryptocurrencies_wildcard(cls) -> bool: + return True diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/metadata.json b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/metadata.json new file mode 100644 index 000000000..7e82e9385 --- /dev/null +++ b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/metadata.json @@ -0,0 +1,6 @@ +{ + "version": "1.2.0", + "origin_package": "OctoBot-Default-Tentacles", + "tentacles": ["DSLRealtimeEvaluator"], + "tentacles-requirements": [] +} diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/resources/DSLRealtimeEvaluator.md b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/resources/DSLRealtimeEvaluator.md new file mode 100644 index 000000000..79d5d081b --- /dev/null +++ b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/resources/DSLRealtimeEvaluator.md @@ -0,0 +1 @@ +DSLRealtimeEvaluator is a generic real-time evaluator that evaluates a DSL condition expression on a configurable trigger channel (OHLCV, Kline, or Ticker). When the condition is truthy, it emits a positive evaluation (+1); when falsy, it emits a negative evaluation (-1). diff --git a/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/tests/__init__.py b/packages/tentacles/Evaluator/RealTime/dsl_realtime_evaluator/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/tentacles/Meta/DSL_operators/exchange_operators/__init__.py b/packages/tentacles/Meta/DSL_operators/exchange_operators/__init__.py index 6ab032d26..8718d1e47 100644 --- a/packages/tentacles/Meta/DSL_operators/exchange_operators/__init__.py +++ b/packages/tentacles/Meta/DSL_operators/exchange_operators/__init__.py @@ -20,6 +20,8 @@ OHLCVOperator, ExchangeDataDependency, create_ohlcv_operators, + create_ticker_operators, + create_symbol_operators, ) import tentacles.Meta.DSL_operators.exchange_operators.exchange_personal_data_operators from tentacles.Meta.DSL_operators.exchange_operators.exchange_personal_data_operators import ( @@ -37,6 +39,8 @@ "OHLCVOperator", "ExchangeDataDependency", "create_ohlcv_operators", + "create_ticker_operators", + "create_symbol_operators", "create_portfolio_operators", "create_cancel_order_operators", "create_create_order_operators", diff --git a/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/__init__.py b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/__init__.py index 0148f895e..5e9395189 100644 --- a/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/__init__.py +++ b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/__init__.py @@ -21,9 +21,19 @@ ExchangeDataDependency, create_ohlcv_operators, ) +import tentacles.Meta.DSL_operators.exchange_operators.exchange_public_data_operators.ticker_operators +from tentacles.Meta.DSL_operators.exchange_operators.exchange_public_data_operators.ticker_operators import ( + create_ticker_operators, +) +import tentacles.Meta.DSL_operators.exchange_operators.exchange_public_data_operators.symbol_operators +from tentacles.Meta.DSL_operators.exchange_operators.exchange_public_data_operators.symbol_operators import ( + create_symbol_operators, +) __all__ = [ "OHLCVOperator", "ExchangeDataDependency", "create_ohlcv_operators", + "create_ticker_operators", + "create_symbol_operators", ] \ No newline at end of file diff --git a/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/symbol_operators.py b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/symbol_operators.py new file mode 100644 index 000000000..e1a6ba6bb --- /dev/null +++ b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/symbol_operators.py @@ -0,0 +1,89 @@ +# pylint: disable=missing-class-docstring,missing-function-docstring +# Drakkar-Software OctoBot-Commons +# Copyright (c) Drakkar-Software, All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. +import typing + +import octobot_commons.constants as commons_constants +import octobot_commons.dsl_interpreter as dsl_interpreter +import octobot_trading.enums as trading_enums + +import tentacles.Meta.DSL_operators.exchange_operators.exchange_operator as exchange_operator + + +class _SymbolOperatorHost(typing.Protocol): + triggered_symbol: str + exchange_manager: typing.Any + + +def create_symbol_operators( + host: _SymbolOperatorHost, +) -> list[type[exchange_operator.ExchangeOperator]]: + return [ + _triggered_symbol_operator(host), + _market_expiry_operator(host), + ] + + +def _triggered_symbol_operator( + host: _SymbolOperatorHost, +) -> type[exchange_operator.ExchangeOperator]: + class _TriggeredSymbolOperator(exchange_operator.ExchangeOperator): + DESCRIPTION = "Returns the symbol that triggered the current DSL execution" + EXAMPLE = "triggered_symbol()" + + @staticmethod + def get_library() -> str: + return commons_constants.CONTEXTUAL_OPERATORS_LIBRARY + + @classmethod + def get_parameters(cls) -> list: + return [] + + @staticmethod + def get_name() -> str: + return "triggered_symbol" + + async def pre_compute(self) -> None: + await super().pre_compute() + self.value = host.triggered_symbol + + return _TriggeredSymbolOperator + + +def _market_expiry_operator( + host: _SymbolOperatorHost, +) -> type[exchange_operator.ExchangeOperator]: + class _MarketExpiryOperator(exchange_operator.ExchangeOperator): + DESCRIPTION = "Returns the expiry timestamp in milliseconds for the given symbol's market, or None" + EXAMPLE = "market_expiry(triggered_symbol())" + + @classmethod + def get_parameters(cls) -> list: + return [dsl_interpreter.OperatorParameter("symbol", "The market symbol", True, str)] + + @staticmethod + def get_name() -> str: + return "market_expiry" + + async def pre_compute(self) -> None: + await super().pre_compute() + symbol = self.get_computed_parameters()[0] + markets = host.exchange_manager.exchange.connector.client.markets or {} + self.value = (markets.get(symbol) or {}).get( + trading_enums.ExchangeConstantsMarketStatusColumns.EXPIRY.value + ) + + return _MarketExpiryOperator diff --git a/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/ticker_operators.py b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/ticker_operators.py new file mode 100644 index 000000000..88e84db67 --- /dev/null +++ b/packages/tentacles/Meta/DSL_operators/exchange_operators/exchange_public_data_operators/ticker_operators.py @@ -0,0 +1,130 @@ +# pylint: disable=missing-class-docstring,missing-function-docstring +# Drakkar-Software OctoBot-Commons +# Copyright (c) Drakkar-Software, All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. +import typing + +import octobot_commons.constants +import octobot_commons.errors +import octobot_commons.dsl_interpreter as dsl_interpreter + +import tentacles.Meta.DSL_operators.exchange_operators.exchange_operator as exchange_operator + + +TICKER_CLOSE_KEY = "close" +TICKER_OPEN_KEY = "open" +TICKER_HIGH_KEY = "high" +TICKER_LOW_KEY = "low" +TICKER_BASE_VOLUME_KEY = "baseVolume" +TICKER_LAST_KEY = "last" + + +def create_ticker_operators( + tickers_by_symbol: dict[str, dict], +) -> list[type[exchange_operator.ExchangeOperator]]: + + class _TickerOperator(exchange_operator.ExchangeOperator): + TICKER_FIELD: str = "" + + @staticmethod + def get_library() -> str: + return octobot_commons.constants.CONTEXTUAL_OPERATORS_LIBRARY + + @classmethod + def get_parameters(cls) -> list[dsl_interpreter.OperatorParameter]: + return [ + dsl_interpreter.OperatorParameter( + name="symbol", description="The symbol to get the ticker value for", + required=True, type=str, + ), + ] + + async def pre_compute(self) -> None: + await super().pre_compute() + symbol = self.get_computed_parameters()[0] + ticker = tickers_by_symbol.get(str(symbol)) + if ticker is None: + raise octobot_commons.errors.DSLInterpreterError( + f"No ticker data available for symbol '{symbol}'" + ) + value = ticker.get(self.TICKER_FIELD) + if value is None: + raise octobot_commons.errors.DSLInterpreterError( + f"Ticker field '{self.TICKER_FIELD}' is None for symbol '{symbol}'" + ) + self.value = value + + class _TickerCloseOperator(_TickerOperator): + DESCRIPTION = "Returns the close price from the latest fetched ticker" + EXAMPLE = "ticker_close(triggered_symbol())" + TICKER_FIELD = TICKER_CLOSE_KEY + + @staticmethod + def get_name() -> str: + return "ticker_close" + + class _TickerOpenOperator(_TickerOperator): + DESCRIPTION = "Returns the open price from the latest fetched ticker" + EXAMPLE = "ticker_open(triggered_symbol())" + TICKER_FIELD = TICKER_OPEN_KEY + + @staticmethod + def get_name() -> str: + return "ticker_open" + + class _TickerHighOperator(_TickerOperator): + DESCRIPTION = "Returns the high price from the latest fetched ticker" + EXAMPLE = "ticker_high(triggered_symbol())" + TICKER_FIELD = TICKER_HIGH_KEY + + @staticmethod + def get_name() -> str: + return "ticker_high" + + class _TickerLowOperator(_TickerOperator): + DESCRIPTION = "Returns the low price from the latest fetched ticker" + EXAMPLE = "ticker_low(triggered_symbol())" + TICKER_FIELD = TICKER_LOW_KEY + + @staticmethod + def get_name() -> str: + return "ticker_low" + + class _TickerVolumeOperator(_TickerOperator): + DESCRIPTION = "Returns the base volume from the latest fetched ticker" + EXAMPLE = "ticker_volume(triggered_symbol())" + TICKER_FIELD = TICKER_BASE_VOLUME_KEY + + @staticmethod + def get_name() -> str: + return "ticker_volume" + + class _TickerLastOperator(_TickerOperator): + DESCRIPTION = "Returns the last price from the latest fetched ticker" + EXAMPLE = "ticker_last(triggered_symbol())" + TICKER_FIELD = TICKER_LAST_KEY + + @staticmethod + def get_name() -> str: + return "ticker_last" + + return [ + _TickerCloseOperator, + _TickerOpenOperator, + _TickerHighOperator, + _TickerLowOperator, + _TickerVolumeOperator, + _TickerLastOperator, + ] diff --git a/packages/tentacles/Meta/DSL_operators/python_std_operators/__init__.py b/packages/tentacles/Meta/DSL_operators/python_std_operators/__init__.py index 3afd51e36..b99372879 100644 --- a/packages/tentacles/Meta/DSL_operators/python_std_operators/__init__.py +++ b/packages/tentacles/Meta/DSL_operators/python_std_operators/__init__.py @@ -86,6 +86,10 @@ from tentacles.Meta.DSL_operators.python_std_operators.base_iterable_operators import ( ListOperator, ) +import tentacles.Meta.DSL_operators.python_std_operators.base_time_operators as dsl_interpreter_base_time_operators +from tentacles.Meta.DSL_operators.python_std_operators.base_time_operators import ( + NowMsOperator, +) __all__ = [ "AddOperator", @@ -129,4 +133,5 @@ "SliceOperator", "ListOperator", "ErrorOperator", + "NowMsOperator", ] diff --git a/packages/tentacles/Meta/DSL_operators/python_std_operators/base_time_operators.py b/packages/tentacles/Meta/DSL_operators/python_std_operators/base_time_operators.py new file mode 100644 index 000000000..f9d517150 --- /dev/null +++ b/packages/tentacles/Meta/DSL_operators/python_std_operators/base_time_operators.py @@ -0,0 +1,34 @@ +# pylint: disable=missing-class-docstring,missing-function-docstring +# Drakkar-Software OctoBot-Commons +# Copyright (c) Drakkar-Software, All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. +import time + +import octobot_commons.dsl_interpreter as dsl_interpreter + + +class NowMsOperator(dsl_interpreter.CallOperator): + MIN_PARAMS = 0 + MAX_PARAMS = 0 + NAME = "now_ms" + DESCRIPTION = "Returns the current time in milliseconds since epoch." + EXAMPLE = "now_ms()" + + @staticmethod + def get_name() -> str: + return "now_ms" + + def compute(self) -> dsl_interpreter.ComputedOperatorParameterType: + return int(time.time() * 1000) diff --git a/packages/tentacles/Trading/Mode/dsl_trading_mode/dsl_trading.py b/packages/tentacles/Trading/Mode/dsl_trading_mode/dsl_trading.py index 941ac6f3b..ee895a617 100644 --- a/packages/tentacles/Trading/Mode/dsl_trading_mode/dsl_trading.py +++ b/packages/tentacles/Trading/Mode/dsl_trading_mode/dsl_trading.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. import typing +import octobot_commons.constants as commons_constants import octobot_commons.enums as commons_enums import octobot_commons.errors as commons_errors import octobot_commons.dsl_interpreter as dsl_interpreter @@ -37,7 +38,11 @@ async def set_final_eval( self.logger.info( f"Executing DSL script trigger by {matrix_id=}, {cryptocurrency=}, {symbol=}, {time_frame=}, {trigger_source=}" ) - result = await self.trading_mode.interpret_dsl_script() # type: ignore + if symbol not in self.exchange_manager.exchange_config.traded_symbol_pairs: + self.logger.info(f"Registering new trading pair: {symbol}") + await self.exchange_manager.exchange_config.add_traded_symbols([symbol], []) + self.trading_mode.triggered_symbol = symbol + result = await self.trading_mode.interpret_dsl_script() self.logger.info(f"DSL script successfully executed. Result: {result.result}") @classmethod @@ -57,6 +62,7 @@ class DSLTradingMode(trading_modes.AbstractTradingMode): def __init__(self, config, exchange_manager): super().__init__(config, exchange_manager) self.dsl_script: str = "" + self.triggered_symbol: str = "" self.interpreter: dsl_interpreter.Interpreter = None # type: ignore def init_user_inputs(self, inputs: dict) -> None: @@ -116,6 +122,7 @@ def _create_interpreter( self.exchange_manager, trading_mode=self, dependencies=dependencies ) + dsl_operators.create_blockchain_wallet_operators(self.exchange_manager) + + dsl_operators.create_symbol_operators(self) ) async def interpret_dsl_script(self) -> dsl_interpreter.DSLCallResult: @@ -145,4 +152,7 @@ def get_supported_exchange_types(cls) -> list: return [ trading_enums.ExchangeTypes.SPOT, trading_enums.ExchangeTypes.FUTURE, + trading_enums.ExchangeTypes.OPTION, ] + + diff --git a/packages/tentacles/profiles/itm_predictions_market/profile.json b/packages/tentacles/profiles/itm_predictions_market/profile.json new file mode 100644 index 000000000..b607666b0 --- /dev/null +++ b/packages/tentacles/profiles/itm_predictions_market/profile.json @@ -0,0 +1,45 @@ +{ + "config": { + "crypto-currencies": {}, + "distribution": "default", + "exchanges": { + "polymarket": { + "enabled": true, + "exchange-type": "option" + } + }, + "trader": { + "enabled": false, + "load-trade-history": false + }, + "trader-simulator": { + "enabled": true, + "fees": { + "maker": 0.0, + "taker": 0.0 + }, + "starting-portfolio": { + "USDC": 1000 + } + }, + "trading": { + "reference-market": "USDC", + "risk": 0.5 + } + }, + "profile": { + "auto_update": false, + "avatar": "default_profile.png", + "complexity": 2, + "description": "ITM Predictions Market profile: buys in-the-money binary options on predictions markets (e.g. Polymarket) using limit orders with stop loss and take profit.", + "extra_backtesting_time_frames": [], + "id": "itm_predictions_market", + "imported": false, + "name": "ITM Predictions Market", + "origin_url": null, + "read_only": true, + "risk": 2, + "slug": "", + "type": "live" + } +} diff --git a/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLRealtimeEvaluator.json b/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLRealtimeEvaluator.json new file mode 100644 index 000000000..d2640029b --- /dev/null +++ b/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLRealtimeEvaluator.json @@ -0,0 +1,6 @@ +{ + "trigger_channel": "all_tickers", + "all_tickers_refresh_time": 64, + "dsl_script": "market_expiry(triggered_symbol()) is not None and 0 < market_expiry(triggered_symbol()) - now_ms() <= 60000 and ticker_close(triggered_symbol()) >= 0.90 and ticker_close(triggered_symbol()) < 0.95", + "time_frame": "1m" +} diff --git a/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLTradingMode.json b/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLTradingMode.json new file mode 100644 index 000000000..b72fbc79b --- /dev/null +++ b/packages/tentacles/profiles/itm_predictions_market/specific_config/DSLTradingMode.json @@ -0,0 +1,3 @@ +{ + "dsl_script": "limit('buy', triggered_symbol(), available('USDC') * 0.05, price='-1%', stop_loss_price='-10%', take_profit_prices=['0.99'], allow_holdings_adaptation=True)" +} diff --git a/packages/tentacles/profiles/itm_predictions_market/specific_config/SimpleStrategyEvaluator.json b/packages/tentacles/profiles/itm_predictions_market/specific_config/SimpleStrategyEvaluator.json new file mode 100644 index 000000000..ca194393e --- /dev/null +++ b/packages/tentacles/profiles/itm_predictions_market/specific_config/SimpleStrategyEvaluator.json @@ -0,0 +1,9 @@ +{ + "default_config": ["DSLRealtimeEvaluator"], + "required_evaluators": ["DSLRealtimeEvaluator"], + "required_time_frames": ["1m"], + "required_candles_count": 2, + "re_evaluate_TA_when_social_or_realtime_notification": false, + "social_evaluators_notification_timeout": 0, + "background_social_evaluators": [] +} diff --git a/packages/tentacles/profiles/itm_predictions_market/tentacles_config.json b/packages/tentacles/profiles/itm_predictions_market/tentacles_config.json new file mode 100644 index 000000000..097f1b5db --- /dev/null +++ b/packages/tentacles/profiles/itm_predictions_market/tentacles_config.json @@ -0,0 +1,11 @@ +{ + "tentacle_activation": { + "Evaluator": { + "DSLRealtimeEvaluator": true, + "SimpleStrategyEvaluator": true + }, + "Trading": { + "DSLTradingMode": true + } + } +}