Ejemplo n.º 1
0
def do_upload(behemoth_path: str, upload_date: datetime.date,
              cloud_upload: bool):
    logger.info(f'uploading into Behemoth for upload date = {upload_date}')
    conn = connect_serenity_db()
    conn.autocommit = True
    cur = conn.cursor()
    instr_cache = InstrumentCache(cur, TypeCodeCache(cur))

    exchanges = {
        'PHEMEX': {
            'db_prefix': 'PHEMEX',
            'supported_products': {'BTCUSD'}
        },
        'COINBASEPRO': {
            'db_prefix': 'COINBASE_PRO',
            'supported_products': {'BTC-USD'}
        }
    }
    for exchange in exchanges.keys():
        logger.info(f'Uploading for exchange: {exchange}')
        db_prefix = exchanges[exchange]['db_prefix']
        supported_products = exchanges[exchange]['supported_products']
        for instrument in instr_cache.get_all_exchange_instruments(exchange):
            symbol = instrument.get_exchange_instrument_code()
            if symbol in supported_products:
                upload_trades(behemoth_path, db_prefix, exchange, symbol,
                              upload_date, cloud_upload)
                upload_order_books(behemoth_path, db_prefix, exchange, symbol,
                                   upload_date, cloud_upload)
Ejemplo n.º 2
0
def generate_tax_report():
    conn = connect_serenity_db()
    cur = conn.cursor()
    type_code_cache = TypeCodeCache(cur)
    instrument_cache = InstrumentCache(cur, type_code_cache)

    analyzer = TradeAnalyzer(cur, type_code_cache, instrument_cache)
    analyzer.run_analysis(['BTC-USD', 'ETH-USD'], 2019, 0.35)
Ejemplo n.º 3
0
    def __init__(self, config: BacktestConfig):
        logger = logging.getLogger(__name__)
        logger.info('Serenity backtester starting up')

        sys.path.append(str(config.get_strategy_basedir()))

        bt_env = config.get_env()

        exchange_id = bt_env.getenv('EXCHANGE_ID', 'autofill')
        instance_id = bt_env.getenv('EXCHANGE_INSTANCE', 'prod')
        account = bt_env.getenv('EXCHANGE_ACCOUNT', 'Main')

        self.logger.info('Connecting to Serenity database')
        conn = connect_serenity_db()
        conn.autocommit = True
        cur = conn.cursor()

        self.scheduler = HistoricNetworkScheduler(
            config.get_start_time_millis(), config.get_end_time_millis())
        instrument_cache = InstrumentCache(cur, TypeCodeCache(cur))

        oms = OrderManagerService(self.scheduler)

        md_service = AzureHistoricMarketdataService(
            self.scheduler, bt_env.getenv('AZURE_CONNECT_STR'))
        mark_service = MarketdataMarkService(self.scheduler.get_network(),
                                             md_service)
        op_service = OrderPlacerService(self.scheduler, oms)
        op_service.register_order_placer(
            f'{exchange_id}:{instance_id}',
            AutoFillOrderPlacer(self.scheduler, oms, md_service, account))

        xps = NullExchangePositionService(self.scheduler)

        extra_outputs_txt = bt_env.getenv('EXTRA_OUTPUTS')
        if extra_outputs_txt is None:
            extra_outputs = []
        else:
            extra_outputs = extra_outputs_txt.split(',')
        self.dcs = HDF5DataCaptureService(Mode.BACKTEST, self.scheduler,
                                          extra_outputs)

        # wire up orders and fills from OMS
        Do(self.scheduler.get_network(), oms.get_orders(),
           lambda: self.dcs.capture_order(oms.get_orders().get_value()))
        Do(self.scheduler.get_network(), oms.get_order_events(),
           lambda: self.dcs.capture_fill(oms.get_order_events().get_value()))

        self.strategy_name = config.get_strategy_name()
        strategy_env = config.get_strategy_env()
        ctx = StrategyContext(self.scheduler, instrument_cache, md_service,
                              mark_service, op_service,
                              PositionService(self.scheduler, oms), xps,
                              self.dcs, strategy_env.values)
        strategy_instance = config.get_strategy_instance()
        strategy_instance.init(ctx)
        strategy_instance.start()
Ejemplo n.º 4
0
def backfill_coinbase():
    conn = connect_serenity_db()
    cur = conn.cursor()
    type_code_cache = TypeCodeCache(cur)
    instrument_cache = InstrumentCache(cur, type_code_cache)
    exch_service = ExchangeEntityService(cur, type_code_cache,
                                         instrument_cache)

    # create a default account for Coinbase
    exchange = type_code_cache.get_by_code(Exchange, 'Coinbase')
    account = exch_service.get_or_create_account(
        ExchangeAccount(0, exchange, 'default'))

    # create a BTC-USD symbol for Coinbase
    product_id = 'BTC-USD'
    instrument_type = type_code_cache.get_by_code(InstrumentType,
                                                  'CurrencyPair')
    instrument = instrument_cache.get_or_create_instrument(
        product_id, instrument_type)
    exchange_instrument = instrument_cache.get_or_create_exchange_instrument(
        product_id, instrument, 'Coinbase')

    # synthesize market orders and fills for Coinbase purchases
    side = type_code_cache.get_by_code(Side, 'Buy')
    order_type = type_code_cache.get_by_code(OrderType, 'Market')
    tif = type_code_cache.get_by_code(TimeInForce, 'Day')

    # noinspection DuplicatedCode
    def create_exchange_order_and_fill(price, quantity, fees, order_id,
                                       create_time):
        order = ExchangeOrder(0, exchange, exchange_instrument, order_type,
                              account, side, tif, order_id, price, quantity,
                              create_time)
        order = exch_service.get_or_create_exchange_order(order)
        conn.commit()
        fill = ExchangeFill(0, price, quantity, fees, order_id, create_time)
        fill.set_order(order)
        exch_service.get_or_create_exchange_fill(fill)
        conn.commit()

    # investment purchase
    create_exchange_order_and_fill(265.39, 1.13, 2.9993, 1,
                                   datetime(2015, 1, 26))

    # residual left in account after payment by Bitcoin
    create_exchange_order_and_fill(238.47, 2.1 - 1.68136, 5.013, 2,
                                   datetime(2015, 2, 13))

    # investment purchase
    create_exchange_order_and_fill(249.05, 0.5, 1.25, 3, datetime(2015, 2, 14))
Ejemplo n.º 5
0
def do_upload(behemoth_path: str, upload_date: datetime.date):
    logger.info(f'uploading into Behemoth for upload date = {upload_date}')
    conn = connect_serenity_db()
    conn.autocommit = True
    cur = conn.cursor()
    instr_cache = InstrumentCache(cur, TypeCodeCache(cur))

    exchanges = {'PHEMEX': 'PHEMEX', 'COINBASE_PRO': 'COINBASE_PRO'}
    for exchange, db_prefix in exchanges.items():
        for instrument in instr_cache.get_all_exchange_instruments(exchange):
            symbol = instrument.get_exchange_instrument_code()

            upload_trades(behemoth_path, db_prefix, exchange, symbol,
                          upload_date)
            upload_order_books(behemoth_path, db_prefix, exchange, symbol,
                               upload_date)
Ejemplo n.º 6
0
import yfinance

from serenity.db.api import connect_serenity_db


def backfill_symbol(symbol):
    ticker = yfinance.Ticker(symbol)
    marks = ticker.history(period="max")

    cur.execute("SELECT instrument_id FROM serenity.instrument WHERE instrument_code = %s", (symbol,))
    instrument_id = cur.fetchone()[0]

    cur.execute("SELECT mark_type_id FROM serenity.mark_type WHERE mark_type_code = %s", ("YahooDailyClose",))
    mark_type_id = cur.fetchone()[0]

    for index, row in marks.iterrows():
        cur.execute("INSERT INTO serenity.instrument_mark (instrument_id, mark_type_id, mark_time, mark) "
                    "VALUES (%s, %s, %s, %s)", (instrument_id, mark_type_id, index, float(row['Close'])))

    conn.commit()


if __name__ == '__main__':
    conn = connect_serenity_db()
    cur = conn.cursor()

    backfill_symbol("BTC-USD")
    backfill_symbol("ETH-USD")
    backfill_symbol("ZEC-USD")
Ejemplo n.º 7
0
def ws_fh_main(create_fh, uri_scheme: str, instance_id: str, journal_path: str, db: str, journal_books: bool = True,
               include_symbol: str = '*'):
    init_logging()
    logger = logging.getLogger(__name__)

    conn = connect_serenity_db()
    conn.autocommit = True
    cur = conn.cursor()

    instr_cache = InstrumentCache(cur, TypeCodeCache(cur))

    scheduler = RealtimeNetworkScheduler()
    registry = FeedHandlerRegistry()
    fh = create_fh(scheduler, instr_cache, include_symbol, instance_id)
    registry.register(fh)

    for instrument in fh.get_instruments():
        symbol = instrument.get_exchange_instrument_code()
        if not (symbol == include_symbol or include_symbol == '*'):
            continue

        # subscribe to FeedState in advance so we know when the Feed is ready to subscribe trades
        class SubscribeTrades(Event):
            def __init__(self, trade_symbol):
                self.trade_symbol = trade_symbol
                self.appender = None

            def on_activate(self) -> bool:
                if fh.get_state().get_value() == FeedHandlerState.LIVE:
                    feed = registry.get_feed(f'{uri_scheme}:{instance_id}:{self.trade_symbol}')
                    instrument_code = feed.get_instrument().get_exchange_instrument_code()
                    journal = Journal(Path(f'{journal_path}/{db}_TRADES/{instrument_code}'))
                    self.appender = journal.create_appender()

                    trades = feed.get_trades()
                    Do(scheduler.get_network(), trades, lambda: self.on_trade_print(trades.get_value()))
                return False

            def on_trade_print(self, trade):
                logger.info(trade)

                self.appender.write_double(datetime.utcnow().timestamp())
                self.appender.write_long(trade.get_trade_id())
                self.appender.write_long(trade.get_trade_id())
                self.appender.write_string(trade.get_instrument().get_exchange_instrument_code())
                self.appender.write_short(1 if trade.get_side() == Side.BUY else 0)
                self.appender.write_double(trade.get_qty())
                self.appender.write_double(trade.get_price())

        if journal_books:
            class SubscribeOrderBook(Event):
                def __init__(self, trade_symbol):
                    self.trade_symbol = trade_symbol
                    self.appender = None

                def on_activate(self) -> bool:
                    if fh.get_state().get_value() == FeedHandlerState.LIVE:
                        feed = registry.get_feed(f'{uri_scheme}:{instance_id}:{self.trade_symbol}')
                        instrument_code = feed.get_instrument().get_exchange_instrument_code()
                        journal = Journal(Path(f'{journal_path}/{db}_BOOKS/{instrument_code}'))
                        self.appender = journal.create_appender()

                        books = feed.get_order_books()
                        Do(scheduler.get_network(), books, lambda: self.on_book_update(books.get_value()))
                    return False

                def on_book_update(self, book: OrderBook):
                    self.appender.write_double(datetime.utcnow().timestamp())
                    if len(book.get_bids()) > 0:
                        self.appender.write_long(book.get_best_bid().get_qty())
                        self.appender.write_double(book.get_best_bid().get_px())
                    else:
                        self.appender.write_long(0)
                        self.appender.write_double(0)

                    if len(book.get_asks()) > 0:
                        self.appender.write_long(book.get_best_ask().get_qty())
                        self.appender.write_double(book.get_best_ask().get_px())
                    else:
                        self.appender.write_long(0)
                        self.appender.write_double(0)

            scheduler.get_network().connect(fh.get_state(), SubscribeOrderBook(symbol))

        scheduler.get_network().connect(fh.get_state(), SubscribeTrades(symbol))

    # async start the feedhandler
    asyncio.ensure_future(fh.start())

    # crash out on any exception
    asyncio.get_event_loop().set_exception_handler(custom_asyncio_error_handler)

    # go!
    asyncio.get_event_loop().run_forever()
Ejemplo n.º 8
0
def backfill_coinbasepro(api_key: str, api_secret: str, api_passphrase: str):
    conn = connect_serenity_db()
    cur = conn.cursor()
    type_code_cache = TypeCodeCache(cur)
    instrument_cache = InstrumentCache(cur, type_code_cache)
    exch_service = ExchangeEntityService(cur, type_code_cache,
                                         instrument_cache)
    auth_client = coinbasepro.AuthenticatedClient(key=api_key,
                                                  secret=api_secret,
                                                  passphrase=api_passphrase)

    # Coinbase Pro has a notion of account per currency for tracking balances, so we want to pull
    # out what it calls the profile, which is the parent exchange account
    profile_set = set()
    for account in auth_client.get_accounts():
        profile_set.add(account['profile_id'])

    exchange = type_code_cache.get_by_code(Exchange, "CoinbasePro")
    account_by_profile_id = {}
    for profile in profile_set:
        account = exch_service.get_or_create_account(
            ExchangeAccount(0, exchange, profile))
        account_by_profile_id[profile] = account

    # load up all the orders
    for order in auth_client.get_orders(status=['done']):
        order_uuid = order['id']

        # market orders have no price
        if 'price' in order:
            price = order['price']
        else:
            price = None

        # market orders that specify "funds" have no size
        if 'size' in order:
            size = order['size']
        else:
            size = order['filled_size']

        exchange_account = account_by_profile_id[order['profile_id']]
        instrument_type = type_code_cache.get_by_code(InstrumentType,
                                                      'CurrencyPair')
        instrument = instrument_cache.get_or_create_instrument(
            order['product_id'], instrument_type)
        exchange_instrument = instrument_cache.get_or_create_exchange_instrument(
            order['product_id'], instrument, exchange.get_type_code())
        side = type_code_cache.get_by_code(Side, order['side'].capitalize())

        if order['type'] is None:
            order['type'] = 'Market'

        order_type = type_code_cache.get_by_code(OrderType,
                                                 order['type'].capitalize())
        if 'time_in_force' in order:
            tif = type_code_cache.get_by_code(TimeInForce,
                                              order['time_in_force'])
        else:
            tif = type_code_cache.get_by_code(TimeInForce, 'Day')
        create_time = order['created_at']

        order = ExchangeOrder(0, exchange, exchange_instrument, order_type,
                              exchange_account, side, tif, order_uuid, price,
                              size, create_time)
        exch_service.get_or_create_exchange_order(order)

    conn.commit()

    # load up all the fills, linking back to the orders
    for product in ['BTC-USD', 'ETH-BTC']:
        for fill in auth_client.get_fills(product_id=product):
            order_id = fill['order_id']
            trade_id = fill['trade_id']
            price = fill['price']
            size = fill['size']
            fees = fill['fee']
            create_time = fill['created_at']

            order = exch_service.get_entity_by_ak(
                ExchangeOrder, (exchange.get_type_code(), order_id))
            fill = ExchangeFill(0, price, size, fees, trade_id, create_time)
            fill.set_order(order)
            exch_service.get_or_create_exchange_fill(fill)

        conn.commit()
Ejemplo n.º 9
0
def backfill_gemini(gemini_api_key: str, gemini_api_secret: str):
    conn = connect_serenity_db()
    cur = conn.cursor()
    type_code_cache = TypeCodeCache(cur)
    instrument_cache = InstrumentCache(cur, type_code_cache)
    exch_service = ExchangeEntityService(cur, type_code_cache,
                                         instrument_cache)
    client = gemini.PrivateClient(gemini_api_key, gemini_api_secret)

    for exchange_symbol in ('BTCUSD', 'ETHBTC', 'ZECBTC'):
        instrument_symbol = exchange_symbol[0:3] + '-' + exchange_symbol[3:]
        instrument_type = type_code_cache.get_by_code(InstrumentType,
                                                      'CurrencyPair')
        instrument = instrument_cache.get_or_create_instrument(
            instrument_symbol, instrument_type)
        exchange_instrument = instrument_cache.get_or_create_exchange_instrument(
            exchange_symbol.lower(), instrument, 'Gemini')

        conn.commit()

        exchange = type_code_cache.get_by_code(Exchange, 'Gemini')

        for trade in client.get_past_trades(exchange_symbol):
            fill_price = trade['price']
            quantity = trade['amount']
            fees = trade['fee_amount']
            side = type_code_cache.get_by_code(Side, trade['type'])
            trade_id = trade['tid']
            order_uuid = trade['order_id']
            create_time_ms = trade['timestampms']
            create_time = datetime.utcfromtimestamp(create_time_ms // 1000).\
                replace(microsecond=create_time_ms % 1000 * 1000)

            # because we cannot get historical exchange orders past 7 days, we need to synthesize limit orders
            exchange_account = ExchangeAccount(0, exchange, 'default')
            exchange_account = exch_service.get_or_create_account(
                exchange_account)
            order_type = type_code_cache.get_by_code(OrderType, 'Limit')
            tif = type_code_cache.get_by_code(TimeInForce, 'GTC')
            order = ExchangeOrder(0, exchange, exchange_instrument, order_type,
                                  exchange_account, side, tif, order_uuid,
                                  fill_price, quantity, create_time)
            order = exch_service.get_or_create_exchange_order(order)
            conn.commit()

            # create the fills and insert, linking back to the synthetic order
            fill = ExchangeFill(0, fill_price, quantity, fees, trade_id,
                                create_time)
            fill.set_order(order)
            exch_service.get_or_create_exchange_fill(fill)
            conn.commit()

    for transfer in client.api_query('/v1/transfers', {}):
        transfer_type = type_code_cache.get_by_code(ExchangeTransferType,
                                                    transfer['type'])
        transfer_method = type_code_cache.get_by_code(ExchangeTransferMethod,
                                                      "Blockchain")
        currency = instrument_cache.get_or_create_currency(
            transfer['currency'])
        quantity = transfer['amount']
        transfer_ref = transfer['txHash']
        transfer_time_ms = transfer['timestampms']
        transfer_time = datetime.utcfromtimestamp(transfer_time_ms // 1000). \
            replace(microsecond=transfer_time_ms % 1000 * 1000)

        transfer = ExchangeTransfer(0, exchange, transfer_type,
                                    transfer_method, currency, quantity,
                                    transfer_ref, transfer_time)
        exch_service.get_or_create_exchange_transfer(transfer)

    conn.commit()
Ejemplo n.º 10
0
def ws_fh_main(create_fh,
               uri_scheme: str,
               instance_id: str,
               journal_path: str,
               db: str,
               journal_books: bool = True,
               include_symbol: str = '*'):
    init_logging()
    logger = logging.getLogger(__name__)

    conn = connect_serenity_db()
    conn.autocommit = True
    cur = conn.cursor()

    instr_cache = InstrumentCache(cur, TypeCodeCache(cur))

    scheduler = RealtimeNetworkScheduler()
    registry = FeedHandlerRegistry()
    fh = create_fh(scheduler, instr_cache, include_symbol, instance_id)
    registry.register(fh)

    # register Prometheus metrics
    trade_counter = Counter('serenity_trade_counter',
                            'Number of trade prints received by feedhandler')
    book_update_counter = Counter(
        'serenity_book_update_counter',
        'Number of book updates received by feedhandler')

    for instrument in fh.get_instruments():
        symbol = instrument.get_exchange_instrument_code()
        if not (symbol == include_symbol or include_symbol == '*'):
            continue

        # subscribe to FeedState in advance so we know when the Feed is ready to subscribe trades
        class SubscribeTrades(Event):
            def __init__(self, trade_symbol):
                self.trade_symbol = trade_symbol
                self.tx_writer = None

            def on_activate(self) -> bool:
                if fh.get_state().get_value() == FeedHandlerState.LIVE:
                    feed = registry.get_feed(
                        f'{uri_scheme}:{instance_id}:{self.trade_symbol}')
                    instrument_code = feed.get_instrument(
                    ).get_exchange_instrument_code()
                    txlog = TransactionLog(
                        Path(f'{journal_path}/{db}_TRADES/{instrument_code}'))
                    self.tx_writer = txlog.create_writer()

                    trades = feed.get_trades()
                    Do(scheduler.get_network(), trades,
                       lambda: self.on_trade_print(trades.get_value()))
                return False

            def on_trade_print(self, trade):
                trade_counter.inc()
                logger.info(trade)

                trade_msg = capnp_def.TradeMessage.new_message()
                trade_msg.time = datetime.utcnow().timestamp()
                trade_msg.tradeId = trade.get_trade_id()
                trade_msg.side = capnp_def.Side.buy if trade.get_side(
                ) == Side.BUY else capnp_def.Side.sell
                trade_msg.size = trade.get_qty()
                trade_msg.price = trade.get_price()

                self.tx_writer.append_msg(trade_msg)

        if journal_books:

            class SubscribeOrderBook(Event):
                def __init__(self, trade_symbol):
                    self.trade_symbol = trade_symbol
                    self.tx_writer = None

                def on_activate(self) -> bool:
                    if fh.get_state().get_value() == FeedHandlerState.LIVE:
                        feed = registry.get_feed(
                            f'{uri_scheme}:{instance_id}:{self.trade_symbol}')
                        instrument_code = feed.get_instrument(
                        ).get_exchange_instrument_code()
                        txlog = TransactionLog(
                            Path(f'{journal_path}/{db}_BOOKS/{instrument_code}'
                                 ))
                        self.tx_writer = txlog.create_writer()

                        books = feed.get_order_books()
                        Do(scheduler.get_network(), books,
                           lambda: self.on_book_update(books.get_value()))
                    return False

                def on_book_update(self, book: OrderBook):
                    book_update_counter.inc()

                    book_msg = capnp_def.Level1BookUpdateMessage.new_message()
                    book_msg.time = datetime.utcnow().timestamp()
                    if len(book.get_bids()) > 0:
                        book_msg.bestBidQty = book.get_best_bid().get_qty()
                        book_msg.bestBidPx = book.get_best_bid().get_px()
                    else:
                        book_msg.bestBidQty = 0
                        book_msg.bestBidPx = 0

                    if len(book.get_asks()) > 0:
                        book_msg.bestAskQty = book.get_best_ask().get_qty()
                        book_msg.bestAskPx = book.get_best_ask().get_px()
                    else:
                        book_msg.bestAskQty = 0
                        book_msg.bestAskPx = 0

                    self.tx_writer.append_msg(book_msg)

            scheduler.get_network().connect(fh.get_state(),
                                            SubscribeOrderBook(symbol))

        scheduler.get_network().connect(fh.get_state(),
                                        SubscribeTrades(symbol))

    # launch the monitoring endpoint
    start_http_server(8000)

    # async start the feedhandler
    asyncio.ensure_future(fh.start())

    # crash out on any exception
    asyncio.get_event_loop().set_exception_handler(
        custom_asyncio_error_handler)

    # go!
    asyncio.get_event_loop().run_forever()
Ejemplo n.º 11
0
 def __init__(self):
     self.conn = connect_serenity_db()
     self.cur = self.conn.cursor()
Ejemplo n.º 12
0
    def __init__(self, config_path: str, strategy_dir: str):
        sys.path.append(strategy_dir)

        with open(config_path, 'r') as config_yaml:
            logger = logging.getLogger(__name__)

            logger.info('Serenity starting up')
            config = yaml.safe_load(config_yaml)
            api_version = config['api-version']
            if api_version != 'v1Beta':
                raise ValueError(f'Unsupported API version: {api_version}')

            self.engine_env = Environment(config['environment'])
            instance_id = self.engine_env.getenv('EXCHANGE_INSTANCE', 'prod')
            self.fh_registry = FeedHandlerRegistry()

            account = self.engine_env.getenv('EXCHANGE_ACCOUNT', 'Main')

            logger.info('Connecting to Serenity database')
            conn = connect_serenity_db()
            conn.autocommit = True
            cur = conn.cursor()

            scheduler = RealtimeNetworkScheduler()
            instrument_cache = InstrumentCache(cur, TypeCodeCache(cur))
            if 'feedhandlers' in config:
                logger.info('Registering feedhandlers')
                for feedhandler in config['feedhandlers']:
                    fh_name = feedhandler['exchange']
                    include_symbol = feedhandler.get('include_symbol', '*')
                    if fh_name == 'Phemex':
                        self.fh_registry.register(
                            PhemexFeedHandler(scheduler, instrument_cache,
                                              include_symbol, instance_id))
                    elif fh_name == 'CoinbasePro':
                        self.fh_registry.register(
                            CoinbaseProFeedHandler(scheduler, instrument_cache,
                                                   instance_id))
                    else:
                        raise ValueError(
                            f'Unsupported feedhandler type: {fh_name}')

            oms = OrderManagerService(scheduler,
                                      TimescaleDbTradeBookingService())
            op_service = OrderPlacerService(scheduler, oms)
            md_service = FeedHandlerMarketdataService(scheduler,
                                                      self.fh_registry,
                                                      instance_id)
            mark_service = PhemexMarkService(scheduler, instrument_cache,
                                             instance_id)
            self.xps = None

            extra_outputs_txt = self.engine_env.getenv('EXTRA_OUTPUTS')
            if extra_outputs_txt is None:
                extra_outputs = []
            else:
                extra_outputs = extra_outputs_txt.split(',')
            self.dcs = HDF5DataCaptureService(Mode.LIVE, scheduler,
                                              extra_outputs)

            if 'order_placers' in config:
                logger.info('Registering OrderPlacers')
                for order_placer in config['order_placers']:
                    op_name = order_placer['exchange']
                    if op_name == 'Phemex':
                        api_key = self.engine_env.getenv('PHEMEX_API_KEY')
                        api_secret = self.engine_env.getenv(
                            'PHEMEX_API_SECRET')
                        if not api_key:
                            raise ValueError('missing PHEMEX_API_KEY')
                        if not api_secret:
                            raise ValueError('missing PHEMEX_API_SECRET')

                        credentials = AuthCredentials(api_key, api_secret)
                        op_service.register_order_placer(
                            f'phemex:{instance_id}',
                            PhemexOrderPlacer(credentials, scheduler, oms,
                                              account, instance_id))

                        self.xps = PhemexExchangePositionService(
                            credentials, scheduler, instrument_cache, account,
                            instance_id)
                        self.ps = PositionService(scheduler, oms)
                    else:
                        raise ValueError(
                            f'Unsupported order placer: {op_name}')

            self.strategies = []
            self.strategy_names = []
            for strategy in config['strategies']:
                strategy_name = strategy['name']
                self.strategy_names.append(strategy_name)
                self.logger.info(f'Loading strategy: {strategy_name}')
                module = strategy['module']
                strategy_class = strategy['strategy-class']
                env = Environment(strategy['environment'],
                                  parent=self.engine_env)

                module = importlib.import_module(module)
                klass = getattr(module, strategy_class)
                strategy_instance = klass()
                ctx = StrategyContext(scheduler, instrument_cache, md_service,
                                      mark_service, op_service, self.ps,
                                      self.xps, self.dcs, env.values)
                self.strategies.append((strategy_instance, ctx))