Skip to content

Commit

Permalink
Merge pull request #1376 from Drakkar-Software/dev
Browse files Browse the repository at this point in the history
Dev merge
  • Loading branch information
GuillaumeDSM authored Oct 26, 2024
2 parents d9e9ce8 + 8f22149 commit 004a330
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 51 deletions.
14 changes: 9 additions & 5 deletions Meta/Keywords/scripting_library/UI/plots/displayed_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async def fill_from_database(self, trading_mode, database_manager, exchange_name
meta_db.get_trades_db(account_type, exchange_name),
meta_db.get_symbol_db(exchange_name, symbol)
]
for db in dbs:
for index, db in enumerate(dbs):
for table_name in await db.tables():
display_data = await db.all(table_name)
if table_name == commons_enums.DBTables.INPUTS.value:
Expand All @@ -78,7 +78,10 @@ async def fill_from_database(self, trading_mode, database_manager, exchange_name
cached_values += display_data
else:
try:
filtered_data = self._filter_and_adapt_displayed_elements(display_data, symbol, time_frame, table_name)
filter_symbol = index != len(dbs) - 1 # don't filter symbol for symbol db
filtered_data = self._filter_and_adapt_displayed_elements(
display_data, symbol, time_frame, table_name, filter_symbol
)
chart = display_data[0][commons_enums.DisplayedElementTypes.CHART.value]
if chart is None:
continue
Expand Down Expand Up @@ -258,16 +261,17 @@ def _adapt_for_display(self, table_name, filtered_elements):
commons_enums.PlotCharts.MAIN_CHART.value
return filtered_elements

def _filter_and_adapt_displayed_elements(self, elements, symbol, time_frame, table_name):
def _filter_and_adapt_displayed_elements(self, elements, symbol, time_frame, table_name, filter_symbol):
default_symbol = None if filter_symbol else symbol
filtered_elements = [
display_element
for display_element in elements
if (
display_element.get(commons_enums.DBRows.SYMBOL.value) == symbol
display_element.get(commons_enums.DBRows.SYMBOL.value, default_symbol) == symbol
and display_element.get(commons_enums.DBRows.TIME_FRAME.value) == time_frame
) or (
display_element.get(trading_constants.STORAGE_ORIGIN_VALUE, {})
.get(trading_enums.ExchangeConstantsOrderColumns.SYMBOL.value, None) == symbol
.get(trading_enums.ExchangeConstantsOrderColumns.SYMBOL.value, default_symbol) == symbol
)
]
return self._adapt_for_display(table_name, filtered_elements)
Expand Down
7 changes: 7 additions & 0 deletions Services/Interfaces/web_interface/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -719,4 +719,11 @@ table.dataTable tfoot th {

.introjs-tooltip-title {
color: #fff; /* avoid using h1 color */
}

/* markdown fixes */
pre code {
font-size: inherit;
color: var(--mdb-code-color); /* 'inherit' overridden for themes compatibility */
word-break: normal;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const mardownConverter = new showdown.Converter();
const currentURL = `${window.location.protocol}//${window.location.host}`;

function markdown_to_html(text) {
return mardownConverter.makeHtml(text?.trim().replaceAll("<br><br>", "\n\n"))
return mardownConverter.makeHtml(
text?.trim().replaceAll("<br><br>", "\n\n")
)
}

function fetch_images() {
Expand Down
29 changes: 25 additions & 4 deletions Services/Services_bases/gpt_service/gpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import octobot_commons.time_frame_manager as time_frame_manager
import octobot_commons.authentication as authentication
import octobot_commons.tree as tree
import octobot_commons.configuration.fields_utils as fields_utils

import octobot.constants as constants
import octobot.community as community
Expand All @@ -46,13 +47,15 @@ def get_fields_description(self):
if self._env_secret_key is None:
return {
services_constants.CONIG_OPENAI_SECRET_KEY: "Your openai API secret key",
services_constants.CONIG_LLM_CUSTOM_BASE_URL: "Custom LLM base url to use. Leave empty to use openai.com",
}
return {}

def get_default_value(self):
if self._env_secret_key is None:
return {
services_constants.CONIG_OPENAI_SECRET_KEY: "",
services_constants.CONIG_LLM_CUSTOM_BASE_URL: "",
}
return {}

Expand Down Expand Up @@ -104,7 +107,10 @@ async def get_chat_completion(
return await self._get_signal_from_gpt(messages, model, max_tokens, n, stop, temperature)

def _get_client(self) -> openai.AsyncOpenAI:
return openai.AsyncOpenAI(api_key=self._get_api_key())
return openai.AsyncOpenAI(
api_key=self._get_api_key(),
base_url=self._get_base_url(),
)

async def _get_signal_from_gpt(
self,
Expand All @@ -128,7 +134,10 @@ async def _get_signal_from_gpt(
)
self._update_token_usage(completions.usage.total_tokens)
return completions.choices[0].message.content
except openai.BadRequestError as err:
except (
openai.BadRequestError, # error in request
openai.UnprocessableEntityError # error in model (ex: model not found)
)as err:
raise errors.InvalidRequestError(
f"Error when running request with model {model} (invalid request): {err}"
) from err
Expand Down Expand Up @@ -315,6 +324,14 @@ def _get_api_key(self):
services_constants.CONIG_OPENAI_SECRET_KEY
]

def _get_base_url(self):
value = self.config[services_constants.CONFIG_CATEGORY_SERVICES][services_constants.CONFIG_GPT].get(
services_constants.CONIG_LLM_CUSTOM_BASE_URL
)
if fields_utils.has_invalid_default_config_value(value):
return None
return value or None

async def prepare(self) -> None:
try:
if self.use_stored_signals_only():
Expand All @@ -323,8 +340,12 @@ async def prepare(self) -> None:
fetched_models = await self._get_client().models.list()
self.models = [d.id for d in fetched_models.data]
if self.model not in self.models:
self.logger.warning(f"Warning: selected '{self.model}' model is not in GPT available models. "
f"Available models are: {self.models}")
self.logger.warning(
f"Warning: the default '{self.model}' model is not in available LLM models from the "
f"selected LLM provider. "
f"Available models are: {self.models}. Please select an available model when configuring your "
f"evaluators."
)
except openai.AuthenticationError as err:
self.logger.error(f"Invalid OpenAI api key: {err}")
self.creation_error_message = err
Expand Down
4 changes: 2 additions & 2 deletions Trading/Exchange/bitmart/bitmart_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class BitMartConnector(exchanges.CCXTConnector):
def _client_factory(self, force_unauth, keys_adapter=None) -> tuple:
return super()._client_factory(force_unauth, keys_adapter=self._keys_adapter)

def _keys_adapter(self, key, secret, password, uid):
def _keys_adapter(self, key, secret, password, uid, auth_token):
# use password as uid
return key, secret, "", password
return key, secret, "", password, None, None


class BitMart(exchanges.RestExchange):
Expand Down
7 changes: 5 additions & 2 deletions Trading/Exchange/coinbase/coinbase_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ class CoinbaseConnector(ccxt_connector.CCXTConnector):
def _client_factory(self, force_unauth, keys_adapter=None) -> tuple:
return super()._client_factory(force_unauth, keys_adapter=self._keys_adapter)

def _keys_adapter(self, key, secret, password, uid):
def _keys_adapter(self, key, secret, password, uid, auth_token):
if auth_token:
# when auth token is provided, force invalid keys
return "ANY_KEY", "ANY_SECRET", password, uid, auth_token, "Bearer "
# CCXT pem key reader is not expecting users to under keys pasted as text from the coinbase UI
# convert \\n to \n to make this format compatible as well
if secret and "\\n" in secret:
secret = secret.replace("\\n", "\n")
return key, secret, password, uid
return key, secret, password, uid, None, None

@_coinbase_retrier
async def _load_markets(self, client, reload: bool):
Expand Down
114 changes: 85 additions & 29 deletions Trading/Mode/daily_trading_mode/daily_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import asyncio
import decimal
import math
import dataclasses
import typing

import octobot_commons.constants as commons_constants
import octobot_commons.enums as commons_enums
Expand All @@ -35,6 +37,12 @@
import octobot_trading.api as trading_api


@dataclasses.dataclass
class OrderDetails:
price: decimal.Decimal
quantity: typing.Optional[decimal.Decimal]


class DailyTradingMode(trading_modes.AbstractTradingMode):

def init_user_inputs(self, inputs: dict) -> None:
Expand Down Expand Up @@ -184,6 +192,7 @@ class DailyTradingModeConsumer(trading_modes.AbstractTradingModeConsumer):
VOLUME_KEY = "VOLUME"
STOP_PRICE_KEY = "STOP_PRICE"
TAKE_PROFIT_PRICE_KEY = "TAKE_PROFIT_PRICE"
ADDITIONAL_TAKE_PROFIT_PRICES_KEY = "ADDITIONAL_TAKE_PROFIT_PRICES"
STOP_ONLY = "STOP_ONLY"
REDUCE_ONLY_KEY = "REDUCE_ONLY"
TAG_KEY = "TAG"
Expand Down Expand Up @@ -468,48 +477,75 @@ def _get_max_amount_from_max_ratio(self, max_ratio, quantity, quote, default_rat
f"Set it to 100 to buy anyway.")
return trading_constants.ZERO

def _get_split_take_profit_details(
self, order_details: list[OrderDetails], total_quantity: decimal.Decimal, symbol_market
):
prices = [order_detail.price for order_detail in order_details]
quantities, prices = trading_personal_data.get_valid_split_orders(
total_quantity, prices, symbol_market
)
return [
OrderDetails(price, quantity)
for quantity, price in zip(quantities, prices)
]

async def _create_order(
self, current_order,
use_take_profit_orders, take_profit_price,
use_stop_loss_orders, stop_price,
use_take_profit_orders, take_profits_details: list[OrderDetails],
use_stop_loss_orders, stop_loss_details: list[OrderDetails],
symbol_market, tag
):
params = {}
chained_orders = []
is_long = current_order.side is trading_enums.TradeOrderSide.BUY
exit_side = trading_enums.TradeOrderSide.SELL if is_long else trading_enums.TradeOrderSide.BUY
if use_stop_loss_orders:
if len(stop_loss_details) > 1:
self.logger.error(f"Multiple stop loss orders is not supported.")
stop_price = trading_personal_data.decimal_adapt_price(
symbol_market,
current_order.origin_price * (
trading_constants.ONE + (self.TARGET_PROFIT_STOP_LOSS * (-1 if is_long else 1))
)
) if stop_price.is_nan() else stop_price
) if (not stop_loss_details or stop_loss_details[0].price.is_nan()) else stop_loss_details[0].price
param_update, chained_order = await self.register_chained_order(
current_order, stop_price, trading_enums.TraderOrderType.STOP_LOSS, exit_side, tag=tag
)
params.update(param_update)
chained_orders.append(chained_order)
if use_take_profit_orders:
take_profit_price = trading_personal_data.decimal_adapt_price(
symbol_market,
current_order.origin_price * (
trading_constants.ONE + (self.TARGET_PROFIT_TAKE_PROFIT * (1 if is_long else -1))
if take_profits_details:
local_take_profits_details = self._get_split_take_profit_details(
take_profits_details, current_order.origin_quantity, symbol_market
)
) if take_profit_price.is_nan() else take_profit_price
order_type = self.exchange_manager.trader.get_take_profit_order_type(
current_order,
trading_enums.TraderOrderType.SELL_LIMIT if exit_side is trading_enums.TradeOrderSide.SELL
else trading_enums.TraderOrderType.BUY_LIMIT
)
param_update, chained_order = await self.register_chained_order(
current_order, take_profit_price, order_type, exit_side, tag=tag
)
params.update(param_update)
chained_orders.append(chained_order)
else:
local_take_profits_details = [
OrderDetails(decimal.Decimal("nan"), current_order.origin_quantity)
]
for take_profits_detail in local_take_profits_details:
take_profit_price = trading_personal_data.decimal_adapt_price(
symbol_market,
current_order.origin_price * (
trading_constants.ONE + (self.TARGET_PROFIT_TAKE_PROFIT * (1 if is_long else -1))
)
) if take_profits_detail.price.is_nan() else take_profits_detail.price
order_type = self.exchange_manager.trader.get_take_profit_order_type(
current_order,
trading_enums.TraderOrderType.SELL_LIMIT if exit_side is trading_enums.TradeOrderSide.SELL
else trading_enums.TraderOrderType.BUY_LIMIT
)
param_update, chained_order = await self.register_chained_order(
current_order, take_profit_price, order_type, exit_side,
quantity=take_profits_detail.quantity, tag=tag
)
params.update(param_update)
chained_orders.append(chained_order)
if len(chained_orders) > 1:
oco_group = self.exchange_manager.exchange_personal_data.orders_manager \
.create_group(trading_personal_data.OneCancelsTheOtherOrderGroup)
stop_count = len([o for o in chained_orders if trading_personal_data.is_stop_order(o.order_type)])
tp_count = len(chained_orders) - stop_count
group_type = trading_personal_data.OneCancelsTheOtherOrderGroup if stop_count == tp_count \
else trading_personal_data.BalancedTakeProfitAndStopOrderGroup
oco_group = self.exchange_manager.exchange_personal_data.orders_manager.create_group(group_type)
for order in chained_orders:
order.add_to_order_group(oco_group)
return await self.trading_mode.create_order(current_order, params=params or None)
Expand Down Expand Up @@ -568,6 +604,13 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
symbol_market,
data.get(self.TAKE_PROFIT_PRICE_KEY, decimal.Decimal(math.nan))
)
additional_user_take_profit_prices = [
trading_personal_data.decimal_adapt_price(
symbol_market,
price
)
for price in (data.get(self.ADDITIONAL_TAKE_PROFIT_PRICES_KEY) or [])
]
user_stop_price = trading_personal_data.decimal_adapt_price(
symbol_market,
data.get(self.STOP_PRICE_KEY, decimal.Decimal(math.nan))
Expand Down Expand Up @@ -599,11 +642,24 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
use_stop_orders = is_reducing_position and (self.USE_STOP_ORDERS or not user_stop_price.is_nan())
# use stop loss when increasing the position and the user explicitly asks for one
use_chained_take_profit_orders = increasing_position and (
not user_take_profit_price.is_nan() or self.USE_TARGET_PROFIT_MODE
(not user_take_profit_price.is_nan() or additional_user_take_profit_prices)
or self.USE_TARGET_PROFIT_MODE
)
use_chained_stop_loss_orders = increasing_position and (
not user_stop_price.is_nan() or (self.USE_TARGET_PROFIT_MODE and self.USE_STOP_ORDERS)
)
stop_loss_order_details = take_profit_order_details = []
if use_chained_take_profit_orders:
take_profit_order_details = [] if user_take_profit_price.is_nan() else [
OrderDetails(user_take_profit_price, None)
]
take_profit_order_details += [
OrderDetails(price, None)
for price in additional_user_take_profit_prices
]
if use_chained_stop_loss_orders:
stop_loss_order_details = [OrderDetails(user_stop_price, None)]

if state == trading_enums.EvaluatorStates.VERY_SHORT.value and not self.DISABLE_SELL_ORDERS:
quantity = user_volume \
or await self._get_market_quantity_from_risk(
Expand Down Expand Up @@ -631,8 +687,8 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
)
if current_order := await self._create_order(
current_order,
use_chained_take_profit_orders, user_take_profit_price,
use_chained_stop_loss_orders, user_stop_price,
use_chained_take_profit_orders, take_profit_order_details,
use_chained_stop_loss_orders, stop_loss_order_details,
symbol_market, tag
):
created_orders.append(current_order)
Expand Down Expand Up @@ -667,8 +723,8 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
updated_limit = None
if create_stop_only or (updated_limit := await self._create_order(
current_order,
use_chained_take_profit_orders, user_take_profit_price,
use_chained_stop_loss_orders, user_stop_price,
use_chained_take_profit_orders, take_profit_order_details,
use_chained_stop_loss_orders, stop_loss_order_details,
symbol_market, tag
)):
if updated_limit:
Expand Down Expand Up @@ -735,8 +791,8 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
updated_limit = None
if create_stop_only or (updated_limit := await self._create_order(
current_order,
use_chained_take_profit_orders, user_take_profit_price,
use_chained_stop_loss_orders, user_stop_price,
use_chained_take_profit_orders, take_profit_order_details,
use_chained_stop_loss_orders, stop_loss_order_details,
symbol_market, tag
)):
if updated_limit:
Expand Down Expand Up @@ -797,8 +853,8 @@ async def create_new_orders(self, symbol, final_note, state, **kwargs):
)
if current_order := await self._create_order(
current_order,
use_chained_take_profit_orders, user_take_profit_price,
use_chained_stop_loss_orders, user_stop_price,
use_chained_take_profit_orders, take_profit_order_details,
use_chained_stop_loss_orders, stop_loss_order_details,
symbol_market, tag
):
created_orders.append(current_order)
Expand Down
Loading

0 comments on commit 004a330

Please sign in to comment.