Source code for omega_client.messaging.message_factory

import logging
import time
from typing import List

# pylint: disable=W0611
import capnp
# pylint: enable=W0611

# pylint: disable=E0611
# pylint: disable=E0401
import trading_communication_protocol.Exchanges_capnp as exch_capnp
import trading_communication_protocol.TradeMessage_capnp as msgs_capnp
# pylint: enable=E0611
# pylint: enable=E0401
from omega_client.messaging.common_types import AccountBalancesReport, \
    AccountCredentials, AccountDataReport, AccountInfo, AuthorizationGrant, \
    AuthorizationRefresh, Balance, CompletedOrdersReport, Exchange,\
    ExchangePropertiesReport, ExecutionReport, \
    LogoffAck,  LogonAck, Message, OpenPosition, OpenPositionsReport, Order, \
    OrderInfo,  OrderType, RequestHeader, SymbolProperties, \
    SystemMessage, TimeInForce, WorkingOrdersReport

logger = logging.getLogger(__name__)

# pylint: disable=E1101
EXCHANGE_ENUM_MAPPING = {
    Exchange.poloniex.name: exch_capnp.Exchange.poloniex,
    Exchange.kraken.name: exch_capnp.Exchange.kraken,
    Exchange.gemini.name: exch_capnp.Exchange.gemini,
    Exchange.bitfinex.name: exch_capnp.Exchange.bitfinex,
    Exchange.bittrex.name: exch_capnp.Exchange.bittrex,
    Exchange.binance.name: exch_capnp.Exchange.binance,
    Exchange.coinbasePro.name: exch_capnp.Exchange.coinbasePro,
    Exchange.coinbasePrime.name: exch_capnp.Exchange.coinbasePrime,
    Exchange.bitstamp.name: exch_capnp.Exchange.bitstamp,
    Exchange.itBit.name: exch_capnp.Exchange.itBit
}
# pylint: enable=E1101


def _build_py_message(msg):
    """

    :param msg: (capnp._DynamicStructBuilder) Message object
    :return: Message python object
    """
    return Message(msg.code, msg.body)


[docs]def omega_test_message_py(test_message): """ TODO: the naming gets rid of 'test' as a function prefix since pytest tests all functions with 'test' as a prefix. Rename this back to test_message_py once we have disabled that behavior. Builds test message Python object from capnp object. :param test_message: (capnp._DynamicStructBuilder) TestMessage object. :return: (str) test message. """ return test_message.string
[docs]def system_message_py(system_message): """ Builds system message Python object from capnp object. :param system_message: (capnp._DynamicStructBuilder) system message. :return: SystemMessage. """ py_message = _build_py_message(system_message.message) return SystemMessage(account_info=account_info_py( system_message.accountInfo), message=py_message)
[docs]def authorization_grant_py(authorization_grant): """ Builds AuthorizationGrant Python object from capnp object. :param authorization_grant: (capnp._DynamicStructBuilder) capnp AuthorizationGrant message :return: AuthorizationGrant """ py_message = _build_py_message(authorization_grant.message) return AuthorizationGrant(success=authorization_grant.success, message=py_message, access_token=authorization_grant.accessToken, refresh_token=authorization_grant.refreshToken, expire_at=authorization_grant.expireAt)
[docs]def logon_ack_py(logon_ack): """ Builds LogonAck Python object from capnp object. :param logon_ack: (capnp._DynamicStructBuilder) LogonAck object. :return: LogonAck """ client_accounts = list([account_info_py(account) for account in logon_ack.clientAccounts]) py_message = _build_py_message(logon_ack.message) return LogonAck(success=logon_ack.success, message=py_message, client_accounts=client_accounts, authorization_grant=authorization_grant_py( logon_ack.authorizationGrant))
[docs]def logoff_ack_py(logoff_ack): """ Builds LogoffAck Python object from capnp object. :param logoff_ack: (capnp._DynamicStructBuilder) LogoffAck object. :return: LogoffAck """ py_message = _build_py_message(logoff_ack.message) return LogoffAck(bool(logoff_ack.success), py_message)
[docs]def execution_report_py(execution_report): """ Builds ExecutionReport Python object from capnp object. :param execution_report: (capnp._DynamicStructBuilder) ExecutionReport object. :return: (ExecutionReport) python object. """ return _build_py_execution_report_from_capnp(execution_report)
[docs]def account_data_report_py(account_data_report): """ Builds AccountDataReport Python object from capnp object, including AccountBalances, OpenPositions, and ExecutionReports. :param account_data_report: (capnp._DynamicStructBuilder) AccountDataReport object. :return: (AccountDataReport) Python class object. """ acct_balances = [_build_py_balance_from_capnp(ab) for ab in account_data_report.balances] open_positions = [_build_py_open_position_from_capnp(op) for op in account_data_report.openPositions] orders = [_build_py_execution_report_from_capnp(er) for er in account_data_report.orders] return AccountDataReport( account_info=account_info_py(account_data_report.accountInfo), balances=acct_balances, open_positions=open_positions, orders=orders )
[docs]def account_balances_report_py(account_balances_report): """ Builds AccountBalancesReport Python object from capnp object. :param account_balances_report: (capnp._DynamicStructBuilder) AccountBalancesReport object. :return: (AccountBalancesReport) Python class object. """ acct_balances = [_build_py_balance_from_capnp(ab) for ab in account_balances_report.balances] return AccountBalancesReport( account_info=account_info_py(account_balances_report.accountInfo), balances=acct_balances )
[docs]def open_positions_report_py(open_position_report): """ Builds OpenPositionReport Python object from capnp object. :param open_position_report: (capnp._DynamicStructBuilder) OpenPositionReport object. :return: (OpenPositionReport) Python object. """ open_pos = [_build_py_open_position_from_capnp(op) for op in open_position_report.openPositions] return OpenPositionsReport( account_info=account_info_py(open_position_report.accountInfo), open_positions=open_pos )
[docs]def working_orders_report_py(working_orders_report): """ Builds WorkingOrdersReport Python object from capnp object. :param working_orders_report: (capnp._DynamicStructBuilder) WorkingOrdersReport object. :return: (WorkingOrdersReport) Python object. """ execution_reports = [_build_py_execution_report_from_capnp(er) for er in working_orders_report.orders] return WorkingOrdersReport( account_info=account_info_py(working_orders_report.accountInfo), orders=execution_reports )
[docs]def completed_orders_report_py(completed_orders_report): """ Builds CompletedOrdersReport Python object from capnp object. :param completed_orders_report: (capnp._DynamicStructBuilder) CompletedOrdersReport object. :return: (CompletedOrdersReport) Python object. """ execution_reports = [_build_py_execution_report_from_capnp(er) for er in completed_orders_report.orders] return CompletedOrdersReport( account_info=account_info_py(completed_orders_report.accountInfo), orders=execution_reports )
[docs]def exchange_properties_report_py(exchange_properties_report): """ Builds ExchangePropertiesReport Python object from capnp object. :param exchange_properties_report: (capnp._DynamicStructBuilder) ExchangePropertiesReport object. :return: (ExchangePropertiesReport) Python object. """ currencies = set(ccy for ccy in exchange_properties_report.currencies) symbol_properties = {} for sp in exchange_properties_report.symbolProperties: symbol_properties[sp.symbol] = SymbolProperties( symbol=sp.symbol, price_precision=sp.pricePrecision, quantity_precision=sp.quantityPrecision, min_quantity=sp.minQuantity, max_quantity=sp.maxQuantity, margin_supported=sp.marginSupported, leverage=set(lev for lev in sp.leverage) ) time_in_forces = set(str(tif) for tif in exchange_properties_report.timeInForces) order_types = set(str(ot) for ot in exchange_properties_report.orderTypes) return ExchangePropertiesReport( exchange=str(exchange_properties_report.exchange), currencies=currencies, symbol_properties=symbol_properties, time_in_forces=time_in_forces, order_types=order_types )
[docs]def logon_capnp(request_header: RequestHeader, client_secret: str, credentials: List[AccountCredentials]): """ Generates a capnp Logon message with a specific clientID and set of credentials. :param client_secret: (str) client_secret key assigned by Fund3. :param credentials: (List[AccountCredentials]) List of exchange credentials in the form of AccountCredentials. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) Logon capnp object. """ request_header.access_token = '' # Empty in logon omega_message, body = _generate_omega_request(request_header=request_header) logon = body.init('logon') logon.clientSecret = client_secret logon.init('credentials', len(credentials)) logon = _set_logon_credentials(logon=logon, credentials=credentials) return omega_message, logon
[docs]def logoff_capnp(request_header: RequestHeader): """ Generates a capnp Logoff message with a specific clientID. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) Logoff capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) body.logoff = None return omega_message, body
[docs]def heartbeat_capnp(request_header: RequestHeader): """ Generates a capnp heartbeat message. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) heartbeat capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) body.heartbeat = None return omega_message, body
[docs]def request_server_time_capnp(request_header: RequestHeader): """ Generates a capnp getServerTime message. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) heartbeat capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) body.getServerTime = None return omega_message, body
[docs]def place_order_capnp(request_header: RequestHeader, order: Order): """ Generates a capnp placeOrder message from an Order. :param order: (Order) Python object from omega_client.common_types. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) placeOrder capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) place_order = body.init('placeSingleOrder') acct = place_order.init('accountInfo') acct.accountID = order.account_info.account_id place_order.clientOrderID = order.client_order_id place_order.clientOrderLinkID = order.client_order_link_id place_order.symbol = order.symbol place_order.side = order.side place_order.orderType = order.order_type place_order.quantity = order.quantity place_order.price = order.price place_order.stopPrice = order.stop_price place_order.timeInForce = order.time_in_force place_order.expireAt = order.expire_at place_order.leverageType = order.leverage_type place_order.leverage = order.leverage return omega_message, place_order
[docs]def replace_order_capnp( request_header: RequestHeader, account_info: AccountInfo, order_id: str, # pylint: disable=E1101 order_type: str = OrderType.market.name, quantity: float = 0.0, price: float = 0.0, stop_price: float = 0.0, time_in_force: str = TimeInForce.gtc.name, # pylint: enable=E1101 expire_at: float = 0.0 ): """ Generates a request to Omega to replace an order. :param account_info: (AccountInfo) Account on which to cancel order. :param order_id: (str) orderID as returned from the ExecutionReport. :param request_header: Header parameter object for requests. :param order_type: (OrderType) (OPTIONAL) :param quantity: (float) (OPTIONAL) :param price: (float) (OPTIONAL) :param stop_price: (float) (OPTIONAL) :param time_in_force: (TimeInForce) (OPTIONAL) :param expire_at: (float) (OPTIONAL) utc timestamp gtt orders expire at :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) replaceOrder capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) replace_order = body.init('replaceOrder') acct = replace_order.init('accountInfo') acct.accountID = account_info.account_id replace_order.orderID = order_id replace_order.orderType = order_type replace_order.quantity = quantity # https://github.com/fund3/omega_python_client/issues/39 # merge with ExchangePropertiesReport to get more sophisticated # price values replace_order.price = _determine_order_price( order_price=price, order_type=order_type) replace_order.stopPrice = stop_price replace_order.timeInForce = time_in_force replace_order.expireAt = expire_at return omega_message, replace_order
[docs]def cancel_order_capnp( request_header: RequestHeader, account_info: AccountInfo, order_id: str): """ Generates a capnp cancelOrder message. :param account_info: (AccountInfo) Account on which to cancel order. :param order_id: (str) order_id as returned from the ExecutionReport. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) cancelOrder capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) cancel_order = body.init('cancelOrder') acct = cancel_order.init('accountInfo') acct.accountID = account_info.account_id cancel_order.orderID = order_id return omega_message, cancel_order
[docs]def cancel_all_orders_capnp( request_header: RequestHeader, account_info: AccountInfo, symbol: str = None, side: str = None): """ Generates a capnp CancelAllOrders message. :param request_header: Header parameter object for requests. :param account_info: (AccountInfo) Account on which to cancel order. :param symbol: str (optional) :param side: str (optional) :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) cancelOrder capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) cancel_all_orders = body.init('cancelAllOrders') acct = cancel_all_orders.init('accountInfo') acct.accountID = account_info.account_id if symbol: cancel_all_orders.symbol = symbol if side: cancel_all_orders.side = side return omega_message, cancel_all_orders
[docs]def request_auth_refresh_capnp( request_header: RequestHeader, auth_refresh: AuthorizationRefresh): """ Generates a request to Omega for an session AuthorizationRefresh :param request_header: Header parameter object for requests. :param auth_refresh: (AuthorizationRefresh) python object :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) authorizationRefresh capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) authorization_refresh = body.init('authorizationRefresh') authorization_refresh.refreshToken = auth_refresh.refresh_token return omega_message, authorization_refresh
[docs]def request_account_data_capnp( request_header: RequestHeader, account_info: AccountInfo): """ Generates a request to Omega for full account snapshot including balances, open positions, and working orders. :param account_info: (AccountInfo) Account from which to retrieve data. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getAccountData capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_account_data = body.init('getAccountData') acct = get_account_data.init('accountInfo') acct.accountID = account_info.account_id return omega_message, get_account_data
[docs]def request_open_positions_capnp( request_header: RequestHeader, account_info: AccountInfo): """ Send a request to Omega for open positions on an Account. :param account_info: (AccountInfo) Account from which to retrieve data. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getOpenPositions capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_open_positions = body.init('getOpenPositions') acct = get_open_positions.init('accountInfo') acct.accountID = account_info.account_id return omega_message, get_open_positions
[docs]def request_account_balances_capnp( request_header: RequestHeader, account_info: AccountInfo): """ Generates a request to Omega for full account balances snapshot on an Account. :param account_info: (AccountInfo) Account from which to retrieve data. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getAccountBalances capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_account_balances = body.init('getAccountBalances') acct = get_account_balances.init('accountInfo') acct.accountID = account_info.account_id return omega_message, get_account_balances
[docs]def request_working_orders_capnp( request_header: RequestHeader, account_info: AccountInfo): """ Generates a request to Omega for all working orders snapshot on an Account. :param account_info: (AccountInfo) Account from which to retrieve data. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getWorkingOrders capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_working_orders = body.init('getWorkingOrders') acct = get_working_orders.init('accountInfo') acct.accountID = account_info.account_id return omega_message, get_working_orders
[docs]def request_order_status_capnp( request_header: RequestHeader, account_info: AccountInfo, order_id: str): """ Generates a request to Omega to request status of a specific order. :param account_info: (AccountInfo) Account from which to retrieve data. :param order_id: (str) The id of the order of interest. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getOrderStatus capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_order_status = body.init('getOrderStatus') acct = get_order_status.init('accountInfo') acct.accountID = account_info.account_id get_order_status.orderID = order_id return omega_message, get_order_status
[docs]def request_completed_orders_capnp( request_header: RequestHeader, account_info: AccountInfo, count: int = None, since: float = None): """ Generates a request to Omega for all completed orders on specified account. If both 'count' and 'from_unix' are None, returns orders for last 24h. :param account_info: (AccountInfo) Account from which to retrieve data. :param request_header: Header parameter object for requests. :param count: (int) optional, number of returned orders (most recent ones). :param since: (float) optional, returns all orders from provided unix timestamp to present. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getCompletedOrders capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_completed_orders = body.init('getCompletedOrders') acct = get_completed_orders.init('accountInfo') acct.accountID = account_info.account_id if count is not None: get_completed_orders.count = count if since is not None: get_completed_orders.since = since return omega_message, get_completed_orders
[docs]def request_exchange_properties_capnp(request_header: RequestHeader, exchange: str): """ Generates a request to Omega for supported currencies, symbols and their associated properties, timeInForces, and orderTypes on an exchange. :param exchange: (str) The exchange of interest. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) TradeMessage capnp object, (capnp._DynamicStructBuilder) getExchangeProperties capnp object. """ omega_message, body = _generate_omega_request(request_header=request_header) get_exchange_properties = body.init('getExchangeProperties') get_exchange_properties.exchange = EXCHANGE_ENUM_MAPPING.get( exchange, exch_capnp.Exchange.undefined) return omega_message, get_exchange_properties
def _set_logon_credentials(logon, credentials): """ Sets the credentials of a capnp Logon object. :param logon: (capnp._DynamicStructBuilder) Logon message. :param credentials: (List[AccountCredentials]) List of exchange credentials in the form of AccountCredentials. :return: (capnp._DynamicStructBuilder) Logon message. """ for credi in zip(logon.credentials, credentials): try: acct_id = credi[1].account_info.account_id api_key = credi[1].api_key secret_key = credi[1].secret_key except Exception as e: logger.error('Missing logon credentials. Account ID, apiKey, ' 'secretKey are all required', extra={'error': e}) raise e credi[0].accountInfo.accountID = acct_id credi[0].apiKey = api_key credi[0].secretKey = secret_key if credi[1].passphrase is not None: credi[0].passphrase = credi[1].passphrase return logon
[docs]def account_info_py(account_info): """ Converts a capnp AccountInfo to Python object. :param account_info: (capnp._DynamicStructBuilder) AccountInfo object. :return: (AccountInfo) Populated Python object. """ return AccountInfo(account_id=account_info.accountID, exchange_account_id=account_info.exchangeAccountID, account_type=str(account_info.accountType), exchange_client_id=account_info.exchangeClientID)
def _build_py_open_position_from_capnp(open_position): """ Converts a capnp OpenPosition to Python object. :param open_position: (capnp._DynamicStructBuilder) OpenPosition object. :return: (OpenPosition) Populated Python object. """ return OpenPosition( symbol=open_position.symbol, side=open_position.side, quantity=open_position.quantity, initial_price=open_position.initialPrice, unrealized_pl=open_position.unrealizedPL ) def _build_py_balance_from_capnp(balance): """ Converts a capnp Balance to Python object. :param balance: (capnp._DynamicStructBuilder) Balance object. :return: (Balance) Populated Python object. """ return Balance( currency=balance.currency, full_balance=balance.fullBalance, available_balance=balance.availableBalance ) def _build_py_execution_report_from_capnp(execution_report): """ Converts a capnp ExecutionReport to Python object. :param execution_report: (capnp._DynamicStructBuilder) ExecutionReport object. :return: (ExecutionReport) Populated Python object. """ return ExecutionReport( order_id=execution_report.orderID, client_order_id=execution_report.clientOrderID, exchange_order_id=execution_report.exchangeOrderID, client_order_link_id=execution_report.clientOrderLinkID, account_info=account_info_py( execution_report.accountInfo), symbol=execution_report.symbol, side=execution_report.side, order_type=execution_report.orderType, quantity=execution_report.quantity, price=execution_report.price, stop_price=execution_report.stopPrice, time_in_force=execution_report.timeInForce, expire_at=execution_report.expireAt, leverage_type=execution_report.leverageType, leverage=execution_report.leverage, order_status=execution_report.orderStatus, filled_quantity=execution_report.filledQuantity, avg_fill_price=execution_report.avgFillPrice, fee=execution_report.fee, creation_time=execution_report.creationTime, submission_time=execution_report.submissionTime, completion_time=execution_report.completionTime, execution_report_type=execution_report.executionType, rejection_reason=_build_py_message(execution_report.rejectionReason) ) def _determine_order_price(order_price: float, order_type: str): """ Omega rejects market orders with a non-zero price, hence this method assigns 0.0 as order_price if it receives a market order. :param order_price: (float) Desired order price. :param order_type: (str) Type of order ie limit, market, etc. :return: (float) Properly formatted order price. """ if order_type == 'market': return 0.0 return order_price def _generate_omega_request(request_header: RequestHeader): """ Generates an empty Omega request from TradeMessage.capnp. :param request_header: Header parameter object for requests. :return: (capnp._DynamicStructBuilder) omega_message to be serialized, (capnp._DynamicStructBuilder) body (empty, to be filled). """ omega_message = msgs_capnp.TradeMessage.new_message() request = omega_message.init('type').init('request') request.requestID = request_header.request_id request.clientID = request_header.client_id request.senderCompID = request_header.sender_comp_id request.accessToken = request_header.access_token body = request.init('body') return omega_message, body
[docs]def generate_client_order_id(): """ Simple way to generate client_order_id. The client can generate their own unique order id as they wish. :return: (str) Client order_id based on the microsecond timestamp. """ client_order_id = str(time.time()*1000000) return client_order_id