Skip to content

Commit

Permalink
Merge pull request #1337 from Drakkar-Software/dev
Browse files Browse the repository at this point in the history
Dev merge
  • Loading branch information
GuillaumeDSM authored Aug 22, 2024
2 parents dc84d5c + 49a6b24 commit 7c5a61c
Show file tree
Hide file tree
Showing 31 changed files with 456 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async def backtesting_exchange_manager(request, backtesting_config, fake_backtes
exchange_manager_instance.use_cached_markets = False
exchange_manager_instance.backtesting = fake_backtesting
exchange_manager_instance.backtesting.time_manager = backtesting_time.TimeManager(config)
await exchange_manager_instance.initialize()
await exchange_manager_instance.initialize(exchange_config_by_exchange=None)
yield exchange_manager_instance
await exchange_manager_instance.stop()

Expand Down
9 changes: 5 additions & 4 deletions Services/Interfaces/web_interface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ def register_notifier(notification_key, notifier):
for logger in ('engineio.server', 'socketio.server', 'geventwebsocket.handler'):
logging.getLogger(logger).setLevel(logging.WARNING)

notifications_history = collections.deque(maxlen=1000)
notifications = []
MAX_NOTIFICATION_HISTORY_SIZE = 1000
notifications_history = collections.deque(maxlen=MAX_NOTIFICATION_HISTORY_SIZE)
notifications = collections.deque(maxlen=MAX_NOTIFICATION_HISTORY_SIZE)

TIME_AXIS_TITLE = "Time"

Expand Down Expand Up @@ -134,8 +135,8 @@ async def add_notification(level, title, message, sound=None):
send_general_notifications()


def get_notifications():
return notifications
def get_notifications() -> list:
return list(notifications)


def get_notifications_history() -> list:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ def community_register():
return flask.redirect(next_url)
except community_errors.EmailValidationRequiredError:
flask.flash(f"Please validate your email from the confirm link we sent you.", "error")
authenticator.logout()
interfaces_util.run_in_bot_main_loop(authenticator.logout())
return flask.redirect(flask.url_for(f"community_login", **flask.request.args))
except authentication.AuthenticationError as err:
flask.flash(f"Error when creating account: {err}", "error")
authenticator.logout()
interfaces_util.run_in_bot_main_loop(authenticator.logout())
except Exception as e:
logging.get_logger("CommunityAuthentication").exception(e, False)
flask.flash(f"Unexpected error when creating account: {e}", "error")
authenticator.logout()
interfaces_util.run_in_bot_main_loop(authenticator.logout())
return flask.render_template('community_register.html',
form=form,
current_logged_in_email=logged_in_email,
Expand All @@ -118,7 +118,7 @@ def community_logout():
next_url = flask.request.args.get("next", flask.url_for("community_login"))
if not models.can_logout():
return flask.redirect(flask.url_for('community'))
authentication.Authenticator.instance().logout()
interfaces_util.run_in_bot_main_loop(authentication.Authenticator.instance().logout())
return flask.redirect(next_url)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import octobot_commons.json_util as json_util
import octobot_commons.configuration as commons_configuration
import octobot_commons.authentication as commons_authentication
from octobot_services.interfaces import run_in_bot_main_loop


class BrowsingDataProvider(singleton.Singleton):
Expand Down Expand Up @@ -100,9 +101,12 @@ def set_all_currencies(self, all_currencies):

def _get_session_secret_key(self):
authenticator = commons_authentication.Authenticator.instance()
if authenticator.must_be_authenticated_through_authenticator() and not authenticator.has_login_info():
if (
authenticator.must_be_authenticated_through_authenticator()
and not run_in_bot_main_loop(authenticator.has_login_info())
):
# reset session key to force login
self.logger.debug("Regenerating session key as user is required but not connected. ")
self.logger.debug("Regenerating session key as user is required but not connected.")
self._generate_session_secret_key()
return commons_configuration.decrypt(self.browsing_data[self.SESSION_SEC_KEY]).encode()

Expand Down
17 changes: 14 additions & 3 deletions Services/Interfaces/web_interface/models/trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# License along with this library.
import time
import sortedcontainers

import octobot_services.interfaces.util as interfaces_util
import octobot_trading.api as trading_api
import octobot_trading.enums as trading_enums
Expand All @@ -24,6 +25,7 @@
import octobot_commons.logging as logging
import octobot_commons.timestamp_util as timestamp_util
import octobot_commons.time_frame_manager as time_frame_manager
import octobot_commons.pretty_printer as pretty_printer
import octobot_commons.symbols as commons_symbols
import tentacles.Services.Interfaces.web_interface.errors as errors
import tentacles.Services.Interfaces.web_interface.models.dashboard as dashboard
Expand Down Expand Up @@ -135,9 +137,18 @@ def get_symbols_values(symbols, has_real_trader, has_simulated_trader):
return value_per_symbols


def _get_exchange_historical_portfolio(exchange_manager, currency, time_frame, from_timestamp, to_timestamp):
return trading_api.get_portfolio_historical_values(exchange_manager, currency, time_frame,
from_timestamp=from_timestamp, to_timestamp=to_timestamp)
def _get_exchange_historical_portfolio(exchange_manager, currency, time_frame, from_timestamp, to_timestamp) -> list:
return [
{
trading_enums.HistoricalPortfolioValue.TIME.value: value[trading_enums.HistoricalPortfolioValue.TIME.value],
trading_enums.HistoricalPortfolioValue.VALUE.value: pretty_printer.get_min_string_from_number(
value[trading_enums.HistoricalPortfolioValue.VALUE.value]
)
}
for value in trading_api.get_portfolio_historical_values(
exchange_manager, currency, time_frame, from_timestamp=from_timestamp, to_timestamp=to_timestamp
)
]


def _merge_all_exchanges_historical_portfolio(currency, time_frame, from_timestamp, to_timestamp):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,16 @@ function manage_alert(data){
errorBadge.text("");
}
}
$.each(data["notifications"], function(i, item) {
const notifications = data["notifications"];
const maxDisplayedNotifications = 10;
// only display latest notifications when too many to display
const displayedNotifications = (
notifications.length > maxDisplayedNotifications ?
notifications.slice(notifications.length - maxDisplayedNotifications, notifications.length): notifications
);
$.each(displayedNotifications, (i, item) => {
create_alert(item["Level"], item["Title"], item["Message"], "", item["Sound"]);
$.each(notificationCallbacks, function(_, callback) {
$.each(notificationCallbacks, (_, callback) => {
callback(item["Title"], item);
});
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,14 @@ function create_line_chart(element, data, title, fontColor='white', update=true,
const minY = Math.min.apply(null, trace.y);
const maxDisplayY = Math.max.apply(null, trace.y);
const minDisplayY = Math.max(0, minY - ((maxDisplayY - minY) / 2));
const titleSpecs = {
text: title,
font: {
size: 24
},
};
const layout = {
title: title,
title: titleSpecs,
height: height,
dragmode: isMobileDisplay() ? false : 'zoom',
margin: {
Expand Down Expand Up @@ -142,7 +148,10 @@ function create_line_chart(element, data, title, fontColor='white', update=true,
displaylogo: false // no logo to avoid 'rel="noopener noreferrer"' security issue (see https://webhint.io/docs/user-guide/hints/hint-disown-opener/)
};
if(update){
Plotly.restyle(element, {x: [trace.x], y: [trace.y]}, 0);
const layoutUpdate = {
title: titleSpecs
}
Plotly.update(element, {x: [trace.x], y: [trace.y]}, layoutUpdate, 0);
} else {
Plotly.newPlot(element, [trace], layout, plotlyConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

$(document).ready(function() {
const createHistoricalPortfolioChart = (element_id, title, update) => {
const createHistoricalPortfolioChart = (element_id, reference_market, update) => {
const element = $(`#${element_id}`);
const selectedTimeFrame = "1d"; // todo add timeframe selector
const url = `${element.data("url")}${selectedTimeFrame}`;
Expand All @@ -28,6 +28,8 @@ $(document).ready(function() {
if(msg.length > 1){
graphDiv.removeClass(hidden_class);
defaultDiv.addClass(hidden_class);
const current_value = msg[msg.length - 1].value;
const title = `${current_value > 0 ? current_value : '-'} ${reference_market}`
create_line_chart(document.getElementById(element_id), msg, title, 'white', update, height);
}else{
graphDiv.addClass(hidden_class);
Expand All @@ -38,8 +40,7 @@ $(document).ready(function() {
}

const displayPortfolioHistory = (elementId, referenceMarket, update) => {
const chartTitle = `Portfolio total ${referenceMarket} value`;
createHistoricalPortfolioChart(elementId, chartTitle, update);
createHistoricalPortfolioChart(elementId, referenceMarket, update);
}

const update_display = (update) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ $(document).ready(function () {
})
}

const updateProfitabilityDisplay = (bot_real_profitability, bot_real_flat_profitability,
bot_simulated_profitability, bot_simulated_flat_profitability) => {
const updateProfitabilityDisplay = (
bot_real_profitability, bot_real_flat_profitability,
bot_simulated_profitability, bot_simulated_flat_profitability
) => {
if(isDefined(bot_real_profitability)){
displayProfitability(bot_real_profitability, bot_real_flat_profitability);
}
Expand All @@ -56,15 +58,15 @@ $(document).ready(function () {
}
}

const displayProfitability = (value, flatValue) => {
const displayedValue = parseFloat(value.toFixed(2));
const displayProfitability = (profitabilityValue, flatValue) => {
const displayedValue = parseFloat(profitabilityValue.toFixed(2));
const badge = $("#profitability-badge");
const flatValueSpan = $("#flat-profitability");
const flatValueText = $("#flat-profitability-text");
const displayValue = $("#profitability-value");
badge.removeClass(hidden_class);
flatValueSpan.removeClass(hidden_class);
if(value < 0){
if(profitabilityValue < 0){
displayValue.text(displayedValue);
flatValueText.text(flatValue);
badge.addClass("badge-warning");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ const handle_confirm = (modalElement, confirmButton, data, update_url, disable_c

const add_cancel_individual_orders_buttons = () => {
$("button[action=cancel_order]").each((_, element) => {
$(element).off("click");
$(element).on("click", (event) => {
cancel_after_confirm($('#CancelOrderModal'), $(event.currentTarget).attr("order_desc"), $(event.currentTarget).attr(update_url_attr));
});
Expand Down Expand Up @@ -302,7 +303,7 @@ const registerTableButtonsEvents = () => {
$("#positions-table").on("draw.dt", () => {
handle_close_buttons();
});
$("#orders-table").on("draw.dt", () => {
$("#orders-table").on("draw.dt row-reordered", () => {
add_cancel_individual_orders_buttons();
const cancelIcon = $("#cancel_all_icon");
$("#cancel_order_progress_bar").hide();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ <h4 class="text-capitalize">
class="editable editable-click config-element">
{{keys_value}}</a><br>

<i>API Password</i>
<i>API Pass/UID</i>
<i class="fa-solid fa-question"
data-toggle="tooltip" data-placement="top"
title="An API password is required by some exchanges, leave as is otherwise.">
title="An API password or Memo/UID is required by some exchanges, leave as is otherwise.">
</i> : <a href="#"
id="exchange_api-password"
config-key="exchanges_{{exchange}}_api-password"
Expand Down
2 changes: 1 addition & 1 deletion Services/Interfaces/web_interface/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
except ImportError:
# todo remove once supabase migration is complete
configuration_storage = mock.Mock(
SyncConfigurationStorage=mock.Mock(
ASyncConfigurationStorage=mock.Mock(
_save_value_in_config=mock.Mock()
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class NotificationsNamespace(websockets.AbstractWebSocketNamespaceNotifier):
@staticmethod
def _get_update_data():
return {
"notifications": copy.copy(web_interface.get_notifications()),
"notifications": web_interface.get_notifications(),
"errors_count": web_interface.get_errors_count()
}

Expand Down
4 changes: 2 additions & 2 deletions Services/Services_bases/webhook_service/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ def _flask_webhook_call(self, webhook_name):
data = flask.request.get_data(as_text=True)
if self._default_webhook_call(webhook_name, data):
return '', 200
flask.abort(500)
flask.abort(400)
return 'invalid or missing input parameters', 400
flask.abort(405)

def _community_webhook_call_factory(self, service_name: str):

Expand Down
9 changes: 7 additions & 2 deletions Trading/Exchange/binance/binance_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ class Binance(exchanges.RestExchange):
INVERSE_TYPE = "inverse"
LINEAR_TYPE = "linear"

def __init__(self, config, exchange_manager, connector_class=None):
def __init__(
self, config, exchange_manager, exchange_config_by_exchange: typing.Optional[dict[str, dict]],
connector_class=None
):
self._futures_account_types = self._infer_account_types(exchange_manager)
super().__init__(config, exchange_manager, connector_class=connector_class)
super().__init__(config, exchange_manager, exchange_config_by_exchange, connector_class=connector_class)

@classmethod
def get_name(cls):
Expand Down Expand Up @@ -202,6 +205,8 @@ async def _create_market_stop_loss_order(self, symbol, quantity, price, side, cu

async def get_positions(self, symbols=None, **kwargs: dict) -> list:
positions = []
if "useV2" not in kwargs:
kwargs["useV2"] = True #V2 api is required to fetch empty positions (not retured in V3)
if "subType" in kwargs:
return _filter_positions(await super().get_positions(symbols=symbols, **kwargs))
for account_type in self._futures_account_types:
Expand Down
11 changes: 1 addition & 10 deletions Trading/Exchange/bitget/bitget_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,7 @@ class BitgetCCXTAdapter(exchanges.CCXTAdapter):

def fix_order(self, raw, **kwargs):
fixed = super().fix_order(raw, **kwargs)
try:
if fixed[trading_enums.ExchangeConstantsOrderColumns.TYPE.value] \
== trading_enums.TradeOrderType.MARKET.value and \
fixed[trading_enums.ExchangeConstantsOrderColumns.SIDE.value] \
== trading_enums.TradeOrderSide.BUY.value:
# convert amount to have the same units as evert other exchange: use FILLED for accuracy
fixed[trading_enums.ExchangeConstantsOrderColumns.AMOUNT.value] = \
fixed[trading_enums.ExchangeConstantsOrderColumns.FILLED.value]
except KeyError:
pass
self.adapt_amount_from_filled_or_cost(fixed)
return fixed

def fix_trades(self, raw, **kwargs):
Expand Down
Loading

0 comments on commit 7c5a61c

Please sign in to comment.