610 lines
26 KiB
Python
610 lines
26 KiB
Python
from typing import Any
|
|
|
|
from Users import Users
|
|
from DataCache_v3 import DataCache
|
|
from Strategies import Strategies
|
|
from backtesting import Backtester
|
|
from candles import Candles
|
|
from Configuration import Configuration
|
|
from ExchangeInterface import ExchangeInterface
|
|
from indicators import Indicators
|
|
from Signals import Signals
|
|
from trade import Trades
|
|
|
|
|
|
class BrighterTrades:
|
|
def __init__(self):
|
|
# Object that interacts and maintains exchange_interface and account data
|
|
self.exchanges = ExchangeInterface()
|
|
|
|
# Object that interacts with the persistent data.
|
|
self.data = DataCache(self.exchanges)
|
|
|
|
# Configuration for the app
|
|
self.config = Configuration()
|
|
|
|
# The object that manages users in the system.
|
|
self.users = Users(data_cache=self.data)
|
|
|
|
# Object that maintains signals.
|
|
self.signals = Signals(self.config)
|
|
|
|
# Object that maintains candlestick and price data.
|
|
self.candles = Candles(users=self.users, exchanges=self.exchanges, data_source=self.data,
|
|
config=self.config)
|
|
|
|
# Object that interacts with and maintains data from available indicators
|
|
self.indicators = Indicators(self.candles, self.users)
|
|
|
|
# Object that maintains the trades data
|
|
self.trades = Trades(self.users)
|
|
# The Trades object needs to connect to an exchange_interface.
|
|
self.trades.connect_exchanges(exchanges=self.exchanges)
|
|
|
|
# Object that maintains the strategies data
|
|
self.strategies = Strategies(self.data, self.trades)
|
|
|
|
# Object responsible for testing trade and strategies data.
|
|
self.backtester = Backtester()
|
|
|
|
def create_new_user(self, email: str, username: str, password: str) -> bool:
|
|
"""
|
|
Creates a new user and logs the user in.
|
|
|
|
:param email: User's email address.
|
|
:param username: User's user_name.
|
|
:param password: User's password.
|
|
:return: bool - True on successful creation and log in.
|
|
"""
|
|
if not email or not username or not password:
|
|
raise ValueError("Missing required arguments for 'create_new_user'")
|
|
|
|
try:
|
|
self.users.create_new_user(email=email, username=username, password=password)
|
|
login_successful = self.users.log_in_user(username=username, password=password)
|
|
return login_successful
|
|
except Exception as e:
|
|
# Handle specific exceptions or log the error
|
|
raise ValueError("Error creating a new user: " + str(e))
|
|
|
|
def log_user_in_out(self, user_name: str, cmd: str, password: str = None):
|
|
"""
|
|
Logs the user in or out based on the provided command.
|
|
|
|
:param user_name: The user_name.
|
|
:param cmd: The command indicating the action to perform ('logout' or 'login').
|
|
:param password: The password for logging in. Required if cmd is 'login'.
|
|
:return: True if the action was successful, False otherwise.
|
|
"""
|
|
if cmd not in ['login', 'logout']:
|
|
raise ValueError("Invalid command. Expected 'login' or 'logout'.")
|
|
|
|
try:
|
|
if cmd == 'logout':
|
|
return self.users.log_out_user(username=user_name)
|
|
elif cmd == 'login':
|
|
if password is None:
|
|
raise ValueError("Password is required for login.")
|
|
return self.users.log_in_user(username=user_name, password=password)
|
|
except Exception as e:
|
|
# Handle specific exceptions or log the error
|
|
raise ValueError("Error during user login/logout: " + str(e))
|
|
|
|
def get_user_info(self, user_name: str, info: str) -> Any | None:
|
|
"""
|
|
Returns specified user info.
|
|
|
|
:param user_name: The user_name.
|
|
:param info: The information being requested.
|
|
:return: The requested info or None.
|
|
:raises ValueError: If the provided info is invalid.
|
|
"""
|
|
|
|
if info == 'Chart View':
|
|
try:
|
|
return self.users.get_chart_view(user_name=user_name)
|
|
except Exception as e:
|
|
# Handle specific exceptions or log the error
|
|
raise ValueError("Error retrieving chart view information: " + str(e))
|
|
elif info == 'Is logged in?':
|
|
try:
|
|
return self.users.is_logged_in(user_name=user_name)
|
|
except Exception as e:
|
|
# Handle specific exceptions or log the error
|
|
raise ValueError("Error checking logged in status: " + str(e))
|
|
elif info == 'User_id':
|
|
try:
|
|
return self.users.get_id(user_name=user_name)
|
|
except Exception as e:
|
|
# Handle specific exceptions or log the error
|
|
raise ValueError("Error fetching id: " + str(e))
|
|
else:
|
|
raise ValueError("Invalid information requested: " + info)
|
|
|
|
def get_market_info(self, info: str, **kwargs) -> Any:
|
|
"""
|
|
Request market information from the application.
|
|
|
|
:param info: str - The information requested.
|
|
:param kwargs: arguments required depending on the info requested.
|
|
:return: The info requested.
|
|
"""
|
|
if info == 'Candle History':
|
|
chart_view = kwargs.get('chart_view', {})
|
|
num_records = kwargs.get('num_records', 10)
|
|
symbol = chart_view.get('market')
|
|
timeframe = chart_view.get('timeframe')
|
|
exchange_name = chart_view.get('exchange')
|
|
user_name = kwargs.get('user_name')
|
|
|
|
if symbol and timeframe and exchange_name and user_name:
|
|
return self.candles.get_candle_history(num_records=num_records,
|
|
symbol=symbol,
|
|
interval=timeframe,
|
|
exchange_name=exchange_name,
|
|
user_name=user_name)
|
|
else:
|
|
missing_args = [arg for arg in ['symbol', 'timeframe', 'exchange', 'user_name'] if arg not in kwargs]
|
|
raise ValueError(f"Missing required arguments for 'Candle History': {', '.join(missing_args)}")
|
|
elif info == 'Something Else':
|
|
# Add code or action for 'Something Else'
|
|
pass
|
|
else:
|
|
raise ValueError(f"Unknown or missing argument for get_market_info(): {info}")
|
|
|
|
return None
|
|
|
|
def get_indicator_data(self, user_name: str, source: dict, start_ts: float = None, num_results: int = None) -> dict:
|
|
"""
|
|
Fetches indicator data for a specific user.
|
|
|
|
:param user_name: The name of the user making the request.
|
|
:param source: A dictionary containing values specific to the type of indicator.
|
|
:param start_ts: The optional timestamp to start fetching the data from.
|
|
:param num_results: The optional number of results requested.
|
|
:return: dict - A dictionary of timestamp indexed indicator data.
|
|
:raises: ValueError if user_name or source is invalid.
|
|
"""
|
|
if not user_name:
|
|
raise ValueError("Invalid user_name provided.")
|
|
|
|
if not source:
|
|
raise ValueError("Invalid source provided.")
|
|
|
|
# Additional validation checks for start_ts and num_results if needed
|
|
|
|
return self.indicators.get_indicator_data(user_name=user_name, source=source, start_ts=start_ts,
|
|
num_results=num_results)
|
|
|
|
def connect_user_to_exchange(self, user_name: str, default_exchange: str, default_keys: dict = None) -> bool:
|
|
"""
|
|
Connects an exchange if it is not already connected.
|
|
|
|
:param user_name: str - The user executing the action.
|
|
:param default_exchange: - The name of the default exchange to connect.
|
|
:param default_keys: default API keys.
|
|
:return: bool - True on success.
|
|
"""
|
|
active_exchanges = self.users.get_exchanges(user_name, category='active_exchanges')
|
|
success = False
|
|
|
|
for exchange in active_exchanges:
|
|
keys = self.users.get_api_keys(user_name, exchange)
|
|
result = self.connect_or_config_exchange(user_name=user_name,
|
|
exchange_name=exchange,
|
|
api_keys=keys)
|
|
if (result['status'] == 'success') or (result['status'] == 'already_connected'):
|
|
success = True
|
|
|
|
if not success:
|
|
# If no active exchange was successfully connected, connect to the default exchange
|
|
result = self.connect_or_config_exchange(user_name=user_name,
|
|
exchange_name=default_exchange,
|
|
api_keys=default_keys)
|
|
if result['status'] == 'success':
|
|
success = True
|
|
|
|
return success
|
|
|
|
def get_js_init_data(self, user_name: str) -> dict:
|
|
"""
|
|
Returns a JSON object of initialization data.
|
|
This is passed into the frontend HTML template for the javascript to access in the rendered HTML.
|
|
|
|
:param user_name: str - The name of the user making the query.
|
|
"""
|
|
chart_view = self.users.get_chart_view(user_name=user_name)
|
|
indicator_types = self.indicators.get_available_indicator_types()
|
|
available_indicators = self.indicators.get_indicator_list(user_name)
|
|
|
|
if not chart_view:
|
|
chart_view = {'timeframe': '', 'exchange_name': '', 'market': ''}
|
|
if not indicator_types:
|
|
indicator_types = []
|
|
if not available_indicators:
|
|
available_indicators = []
|
|
|
|
js_data = {
|
|
'i_types': indicator_types,
|
|
'indicators': available_indicators,
|
|
'timeframe': chart_view.get('timeframe'),
|
|
'exchange_name': chart_view.get('exchange_name'),
|
|
'trading_pair': chart_view.get('market'),
|
|
'user_name': user_name,
|
|
'public_exchanges': self.exchanges.get_public_exchanges()
|
|
|
|
}
|
|
return js_data
|
|
|
|
def get_rendered_data(self, user_name: str) -> dict:
|
|
"""
|
|
Returns data required to render the HTML template of the application's frontend.
|
|
|
|
:param user_name: The name of the user executing the request.
|
|
:return: A dictionary containing the requested data.
|
|
"""
|
|
|
|
chart_view = self.users.get_chart_view(user_name=user_name)
|
|
exchange = self.exchanges.get_exchange(ename=chart_view.get('exchange'), uname=user_name)
|
|
|
|
# noinspection PyDictCreation
|
|
r_data = {}
|
|
r_data['title'] = self.config.get_setting('application_title')
|
|
r_data['chart_interval'] = chart_view.get('timeframe', '')
|
|
r_data['selected_exchange'] = chart_view.get('exchange', '')
|
|
r_data['intervals'] = exchange.intervals if exchange else []
|
|
r_data['symbols'] = exchange.get_symbols() if exchange else {}
|
|
r_data['available_exchanges'] = self.exchanges.get_available_exchanges() or []
|
|
r_data['connected_exchanges'] = self.exchanges.get_connected_exchanges(user_name) or []
|
|
r_data['configured_exchanges'] = self.users.get_exchanges(
|
|
user_name, category='configured_exchanges') or []
|
|
r_data['my_balances'] = self.exchanges.get_all_balances(user_name) or {}
|
|
r_data['indicator_types'] = self.indicators.get_available_indicator_types() or []
|
|
r_data['indicator_list'] = self.indicators.get_indicator_list(user_name) or []
|
|
r_data['enabled_indicators'] = self.indicators.get_indicator_list(user_name, only_enabled=True) or []
|
|
r_data['ma_vals'] = self.indicators.MV_AVERAGE_ENUM
|
|
r_data['active_trades'] = self.exchanges.get_all_activated(user_name, fetch_type='trades') or {}
|
|
r_data['open_orders'] = self.exchanges.get_all_activated(user_name, fetch_type='orders') or {}
|
|
|
|
return r_data
|
|
|
|
def received_cdata(self, cdata: dict) -> dict | None:
|
|
"""
|
|
Processes the received candle data and updates the indicators, signals, trades, and strategies.
|
|
|
|
:param cdata: Dictionary containing the most recent price data.
|
|
:return: Dictionary of updates to be passed onto the UI or None if received duplicate data.
|
|
"""
|
|
# Return if this candle is the same as the last candle received, except when it's the first candle received.
|
|
if not self.candles.cached_last_candle:
|
|
self.candles.cached_last_candle = cdata
|
|
elif cdata['time'] == self.candles.cached_last_candle['time']:
|
|
return None
|
|
|
|
self.candles.set_new_candle(cdata)
|
|
# i_updates = self.indicators.update_indicators()
|
|
state_changes = self.signals.process_all_signals(self.indicators)
|
|
trade_updates = self.trades.update(float(cdata['close']))
|
|
stg_updates = self.strategies.update(self.signals)
|
|
|
|
updates = {}
|
|
# if i_updates:
|
|
# updates['i_updates'] = i_updates
|
|
if state_changes:
|
|
updates['s_updates'] = state_changes
|
|
if trade_updates:
|
|
updates['trade_updts'] = trade_updates
|
|
if stg_updates:
|
|
updates['stg_updts'] = stg_updates
|
|
return updates
|
|
|
|
def received_new_signal(self, data: dict) -> str | dict:
|
|
"""
|
|
Handles the creation of a new signal based on the provided data.
|
|
|
|
:param data: A dictionary containing the attributes of the new signal.
|
|
:return: An error message if the required attribute is missing, or the incoming data for chaining on success.
|
|
"""
|
|
if 'name' not in data:
|
|
return "The new signal must have a 'name' attribute."
|
|
|
|
self.signals.new_signal(data)
|
|
self.config.set_setting('signals_list', self.signals.get_signals('dict'))
|
|
return data
|
|
|
|
def received_new_strategy(self, data: dict) -> str | dict:
|
|
"""
|
|
Handles the creation of a new strategy based on the provided data.
|
|
|
|
:param data: A dictionary containing the attributes of the new strategy.
|
|
:return: An error message if the required attribute is missing, or the incoming data for chaining on success.
|
|
"""
|
|
if 'name' not in data:
|
|
return "The new strategy must have a 'name' attribute."
|
|
|
|
self.strategies.new_strategy(data)
|
|
self.config.set_setting('strategies', self.strategies.get_strategies('dict'))
|
|
return data
|
|
|
|
def delete_strategy(self, strategy_name: str) -> None:
|
|
"""
|
|
Deletes the specified strategy from the strategies instance and the configuration file.
|
|
|
|
:param strategy_name: The name of the strategy to delete.
|
|
:return: None
|
|
:raises ValueError: If the strategy does not exist or there are issues
|
|
with removing it from the configuration file.
|
|
"""
|
|
# if not self.strategies.has_strategy(strategy_name):
|
|
# raise ValueError(f"The strategy '{strategy_name}' does not exist.")
|
|
|
|
self.strategies.delete_strategy(strategy_name)
|
|
try:
|
|
self.config.remove('strategies', strategy_name)
|
|
except Exception as e:
|
|
raise ValueError(f"Failed to remove the strategy '{strategy_name}' from the configuration file: {str(e)}")
|
|
|
|
def delete_signal(self, signal_name: str) -> None:
|
|
"""
|
|
Deletes a signal from the signals instance and removes it from the configuration file.
|
|
|
|
:param signal_name: The name of the signal to delete.
|
|
:return: None
|
|
"""
|
|
|
|
# Delete the signal from the signals instance.
|
|
self.signals.delete_signal(signal_name)
|
|
|
|
# Delete the signal from the configuration file.
|
|
self.config.remove('signals', signal_name)
|
|
|
|
def get_signals_json(self) -> str:
|
|
"""
|
|
Retrieve all the signals from the signals instance and return them as a JSON object.
|
|
|
|
:return: str - A JSON object containing all the signals.
|
|
"""
|
|
return self.signals.get_signals('json')
|
|
|
|
def get_strategies_json(self) -> str:
|
|
"""
|
|
Retrieve all the strategies from the strategies instance and return them as a JSON object.
|
|
|
|
:return: str - A JSON object containing all the strategies.
|
|
"""
|
|
return self.strategies.get_strategies('json')
|
|
|
|
def connect_or_config_exchange(self, user_name: str, exchange_name: str, api_keys: dict = None) -> dict:
|
|
"""
|
|
Connects to an exchange if not already connected, or configures the exchange connection for a single user.
|
|
|
|
:param user_name: str - The name of the user.
|
|
:param exchange_name: str - The name of the exchange.
|
|
:param api_keys: dict - The API keys for the exchange.
|
|
:return: dict - A dictionary containing the result of the operation.
|
|
"""
|
|
|
|
result = {
|
|
'exchange': exchange_name,
|
|
'status': '',
|
|
'message': ''
|
|
}
|
|
|
|
try:
|
|
if self.exchanges.exchange_data.query("user == @user_name and name == @exchange_name").empty:
|
|
# Exchange is not connected, try to connect
|
|
success = self.exchanges.connect_exchange(exchange_name=exchange_name, user_name=user_name,
|
|
api_keys=api_keys)
|
|
if success:
|
|
self.users.active_exchange(exchange=exchange_name, user_name=user_name, cmd='set')
|
|
if api_keys:
|
|
self.users.update_api_keys(api_keys=api_keys, exchange=exchange_name, user_name=user_name)
|
|
result['status'] = 'success'
|
|
result['message'] = f'Successfully connected to {exchange_name}.'
|
|
else:
|
|
result['status'] = 'failure'
|
|
result['message'] = f'Failed to connect to {exchange_name}.'
|
|
else:
|
|
# Exchange is already connected, update API keys if provided
|
|
if api_keys:
|
|
self.users.update_api_keys(api_keys=api_keys, exchange=exchange_name, user_name=user_name)
|
|
result['status'] = 'already_connected'
|
|
result['message'] = f'{exchange_name}: API keys updated.'
|
|
except Exception as e:
|
|
result['status'] = 'error'
|
|
result['message'] = f"Failed to connect to {exchange_name} for user '{user_name}': {str(e)}"
|
|
|
|
return result
|
|
|
|
def close_trade(self, trade_id):
|
|
"""
|
|
Closes a trade identified by the given trade ID.
|
|
|
|
:param trade_id: The ID of the trade to be closed.
|
|
"""
|
|
if self.trades.is_valid_trade_id(trade_id):
|
|
self.trades.close_trade(trade_id)
|
|
self.config.remove('trades', trade_id)
|
|
print(f"Trade {trade_id} has been closed.")
|
|
else:
|
|
print(f"Invalid trade ID: {trade_id}. Unable to close the trade.")
|
|
|
|
def received_new_trade(self, data: dict) -> dict | None:
|
|
"""
|
|
Called when a new trade has been defined and created in the UI.
|
|
|
|
:param data: A dictionary containing the attributes of the trade.
|
|
:return: The details of the trade as a dictionary, or None on failure.
|
|
"""
|
|
|
|
def vld(attr):
|
|
"""
|
|
Casts numeric strings to float before returning the attribute.
|
|
Returns None if the attribute is absent in the data.
|
|
"""
|
|
if attr in data and data[attr] != '':
|
|
try:
|
|
return float(data[attr])
|
|
except ValueError:
|
|
return data[attr]
|
|
else:
|
|
return None
|
|
|
|
# Forward the request to trades.
|
|
status, result = self.trades.new_trade(target=vld('exchange_name'), symbol=vld('symbol'), price=vld('price'),
|
|
side=vld('side'), order_type=vld('orderType'),
|
|
qty=vld('quantity'))
|
|
if status == 'Error':
|
|
print(f'Error placing the trade: {result}')
|
|
return None
|
|
|
|
print(f'Trade order received: exchange_name={vld("exchange_name")}, '
|
|
f'symbol={vld("symbol")}, '
|
|
f'side={vld("side")}, '
|
|
f'type={vld("orderType")}, '
|
|
f'quantity={vld("quantity")}, '
|
|
f'price={vld("price")}')
|
|
|
|
# Update config's list of trades and save to file.
|
|
self.config.update_data('trades', self.trades.get_trades('dict'))
|
|
|
|
trade_obj = self.trades.get_trade_by_id(result)
|
|
if trade_obj:
|
|
# Return the trade object that was created in a form that can be converted to json.
|
|
return trade_obj.__dict__
|
|
else:
|
|
return None
|
|
|
|
def get_trades(self):
|
|
""" Return a JSON object of all the trades in the trades instance."""
|
|
return self.trades.get_trades('dict')
|
|
|
|
def adjust_setting(self, user_name: str, setting: str, params: Any):
|
|
"""
|
|
Adjusts the specified setting for a user.
|
|
|
|
:param user_name: The name of the user.
|
|
:param setting: The setting to adjust.
|
|
:param params: The parameters for the setting adjustment.
|
|
:raises ValueError: If the provided setting is not supported.
|
|
"""
|
|
print(f"[SETTINGS()] MODIFYING({user_name, setting})")
|
|
|
|
if setting == 'interval':
|
|
interval_state = params['timeframe']
|
|
self.users.set_chart_view(values=interval_state, specific_property='timeframe', user_name=user_name)
|
|
|
|
elif setting == 'trading_pair':
|
|
trading_pair = params['symbol']
|
|
self.users.set_chart_view(values=trading_pair, specific_property='market', user_name=user_name)
|
|
|
|
elif setting == 'exchange':
|
|
exchange_name = params['exchange_name']
|
|
|
|
# Get the list of available symbols (markets) for the specified exchange and user.
|
|
markets = self.exchanges.get_exchange(ename=exchange_name, uname=user_name).get_symbols()
|
|
|
|
# Check if the markets list is empty
|
|
if not markets:
|
|
# If no markets are available, exit without changing the chart view.
|
|
print(f"No available markets found for exchange '{exchange_name}'. Chart view remains unchanged.")
|
|
return
|
|
|
|
# Get the currently viewed market for the user.
|
|
current_symbol = self.users.get_chart_view(user_name=user_name, prop='market')
|
|
|
|
# Determine the market to display based on availability.
|
|
if current_symbol not in markets:
|
|
# If the current market is not available, default to the first available market.
|
|
market = markets[0]
|
|
else:
|
|
# Otherwise, continue displaying the current market.
|
|
market = current_symbol
|
|
|
|
# Update the user's chart view to reflect the new exchange and default market.
|
|
self.users.set_chart_view(values=exchange_name, specific_property='exchange_name',
|
|
user_name=user_name, default_market=market)
|
|
|
|
elif setting == 'toggle_indicator':
|
|
indicators_to_toggle = params.getlist('indicator')
|
|
user_id = self.get_user_info(user_name=user_name, info='User_id')
|
|
self.indicators.toggle_indicators(user_id=user_id, indicator_names=indicators_to_toggle)
|
|
|
|
elif setting == 'edit_indicator':
|
|
self.indicators.edit_indicator(user_name=user_name, params=params)
|
|
|
|
elif setting == 'new_indicator':
|
|
self.indicators.new_indicator(user_name=user_name, params=params)
|
|
|
|
else:
|
|
print(f'ERROR SETTING VALUE')
|
|
print(f'The string received by the server was: /n{params}')
|
|
|
|
# Now that the state is changed reload price history.
|
|
self.candles.set_cache(user_name=user_name)
|
|
return
|
|
|
|
def process_incoming_message(self, msg_type: str, msg_data: dict | str) -> dict | None:
|
|
"""
|
|
Processes an incoming message and performs the corresponding actions based on the message type and data.
|
|
|
|
:param msg_type: The type of the incoming message.
|
|
:param msg_data: The data associated with the incoming message.
|
|
:return: dict|None - A dictionary containing the response message and data, or None if no response is needed.
|
|
"""
|
|
|
|
def standard_reply(reply_msg: str, reply_data: Any) -> dict:
|
|
""" Formats a standard reply message. """
|
|
return {"reply": reply_msg, "data": reply_data}
|
|
|
|
if msg_type == 'candle_data':
|
|
if r_data := self.received_cdata(msg_data):
|
|
return standard_reply("updates", r_data)
|
|
|
|
if msg_type == 'request':
|
|
if msg_data == 'signals':
|
|
if signals := self.get_signals_json():
|
|
return standard_reply("signals", signals)
|
|
|
|
elif msg_data == 'strategies':
|
|
if strategies := self.get_strategies_json():
|
|
return standard_reply("strategies", strategies)
|
|
|
|
elif msg_data == 'trades':
|
|
if trades := self.get_trades():
|
|
return standard_reply("trades", trades)
|
|
else:
|
|
print('Warning: Unhandled request!')
|
|
print(msg_data)
|
|
|
|
# Processing commands
|
|
if msg_type == 'delete_signal':
|
|
self.delete_signal(msg_data)
|
|
|
|
if msg_type == 'delete_strategy':
|
|
self.delete_strategy(msg_data)
|
|
|
|
if msg_type == 'close_trade':
|
|
self.close_trade(msg_data)
|
|
|
|
if msg_type == 'new_signal':
|
|
if r_data := self.received_new_signal(msg_data):
|
|
return standard_reply("signal_created", r_data)
|
|
|
|
if msg_type == 'new_strategy':
|
|
if r_data := self.received_new_strategy(msg_data):
|
|
return standard_reply("strategy_created", r_data)
|
|
|
|
if msg_type == 'new_trade':
|
|
if r_data := self.received_new_trade(msg_data):
|
|
return standard_reply("trade_created", r_data)
|
|
|
|
if msg_type == 'config_exchange':
|
|
user, exchange, keys = msg_data['user'], msg_data['exch'], msg_data['keys']
|
|
r_data = self.connect_or_config_exchange(user_name=user, exchange_name=exchange, api_keys=keys)
|
|
return standard_reply("Exchange_connection_result", r_data)
|
|
|
|
if msg_type == 'reply':
|
|
# If the message is a reply log the response to the terminal.
|
|
print(f"\napp.py:Received reply: {msg_data}")
|