def test_not_in_when_item_is_in_list_raises_value_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(KeyError):
         PyCondition.not_in("a", ["a", 1], "item", "list")
示例#2
0
 def true():
     PyCondition.true(True, "this should be true")
示例#3
0
    def __init__(self, config: Optional[TradingNodeConfig] = None):
        if config is None:
            config = TradingNodeConfig()
        PyCondition.not_none(config, "config")
        PyCondition.type(config, TradingNodeConfig, "config")

        # Configuration
        self._config = config

        # Setup loop
        self._loop = asyncio.get_event_loop()
        self._executor = concurrent.futures.ThreadPoolExecutor()
        self._loop.set_default_executor(self._executor)
        self._loop.set_debug(config.loop_debug)

        # Components
        self._clock = LiveClock(loop=self._loop)
        self._uuid_factory = UUIDFactory()
        self.created_time = self._clock.utc_now()
        self._is_running = False

        # Identifiers
        self.trader_id = TraderId(config.trader_id)
        self.machine_id = socket.gethostname()
        self.instance_id = self._uuid_factory.generate()

        # Setup logging
        self._logger = LiveLogger(
            loop=self._loop,
            clock=self._clock,
            trader_id=self.trader_id,
            machine_id=self.machine_id,
            instance_id=self.instance_id,
            level_stdout=LogLevelParser.from_str_py(config.log_level.upper()),
        )

        self._log = LoggerAdapter(
            component_name=type(self).__name__,
            logger=self._logger,
        )

        self._log_header()
        self._log.info("Building...")

        if platform.system() != "Windows":
            # Windows does not support signal handling
            # https://stackoverflow.com/questions/45987985/asyncio-loops-add-signal-handler-in-windows
            self._setup_loop()

        ########################################################################
        # Build platform
        ########################################################################
        if config.cache_database is None or config.cache_database.type == "in-memory":
            cache_db = None
        elif config.cache_database.type == "redis":
            cache_db = RedisCacheDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
                serializer=MsgPackSerializer(timestamps_as_str=True),
                config=config.cache_database,
            )
        else:  # pragma: no cover (design-time error)
            raise ValueError(
                "The cache_db_type in the configuration is unrecognized, "
                "can one of {{'in-memory', 'redis'}}.",
            )

        self._msgbus = MessageBus(
            trader_id=self.trader_id,
            clock=self._clock,
            logger=self._logger,
        )

        self._cache = Cache(
            database=cache_db,
            logger=self._logger,
            config=config.cache,
        )

        self.portfolio = Portfolio(
            msgbus=self._msgbus,
            cache=self._cache,
            clock=self._clock,
            logger=self._logger,
        )

        self._data_engine = LiveDataEngine(
            loop=self._loop,
            msgbus=self._msgbus,
            cache=self._cache,
            clock=self._clock,
            logger=self._logger,
            config=config.data_engine,
        )

        self._exec_engine = LiveExecutionEngine(
            loop=self._loop,
            msgbus=self._msgbus,
            cache=self._cache,
            clock=self._clock,
            logger=self._logger,
            config=config.exec_engine,
        )
        self._exec_engine.load_cache()

        self._risk_engine = LiveRiskEngine(
            loop=self._loop,
            portfolio=self.portfolio,
            msgbus=self._msgbus,
            cache=self._cache,
            clock=self._clock,
            logger=self._logger,
            config=config.risk_engine,
        )

        self.trader = Trader(
            trader_id=self.trader_id,
            msgbus=self._msgbus,
            cache=self._cache,
            portfolio=self.portfolio,
            data_engine=self._data_engine,
            risk_engine=self._risk_engine,
            exec_engine=self._exec_engine,
            clock=self._clock,
            logger=self._logger,
        )

        if config.load_strategy_state:
            self.trader.load()

        # Setup persistence (requires trader)
        self.persistence_writers: List[Any] = []
        if config.persistence:
            self._setup_persistence(config=config.persistence)

        self._builder = TradingNodeBuilder(
            loop=self._loop,
            data_engine=self._data_engine,
            exec_engine=self._exec_engine,
            msgbus=self._msgbus,
            cache=self._cache,
            clock=self._clock,
            logger=self._logger,
            log=self._log,
        )

        self._log.info("INITIALIZED.")
        self.time_to_initialize = self._clock.delta(self.created_time)
        self._log.info(f"Initialized in {int(self.time_to_initialize.total_seconds() * 1000)}ms.")

        self._is_built = False
示例#4
0
 def type_or_none():
     PyCondition.type_or_none("hello", str, "world")
示例#5
0
    def default_fx_ccy(symbol: str, venue: Venue = None) -> Instrument:
        """
        Return a default FX currency pair instrument from the given instrument_id.

        Parameters
        ----------
        symbol : str
            The currency pair symbol.
        venue : Venue
            The currency pair venue.

        Returns
        -------
        Instrument

        Raises
        ------
        ValueError
            If the instrument_id.instrument_id length is not in range [6, 7].

        """
        if venue is None:
            venue = Venue("SIM")
        PyCondition.valid_string(symbol, "symbol")
        PyCondition.in_range_int(len(symbol), 6, 7, "len(symbol)")

        instrument_id = InstrumentId(
            symbol=Symbol(symbol),
            venue=venue,
        )

        base_currency = symbol[:3]
        quote_currency = symbol[-3:]

        # Check tick precision of quote currency
        if quote_currency == "JPY":
            price_precision = 3
        else:
            price_precision = 5

        return Instrument(
            instrument_id=instrument_id,
            asset_class=AssetClass.FX,
            asset_type=AssetType.SPOT,
            base_currency=Currency.from_str(base_currency),
            quote_currency=Currency.from_str(quote_currency),
            settlement_currency=Currency.from_str(quote_currency),
            is_inverse=False,
            price_precision=price_precision,
            size_precision=0,
            tick_size=Decimal(f"{1 / 10 ** price_precision:.{price_precision}f}"),
            multiplier=Decimal("1"),
            lot_size=Quantity("1000"),
            max_quantity=Quantity("1e7"),
            min_quantity=Quantity("1000"),
            max_price=None,
            min_price=None,
            max_notional=Money(50000000.00, USD),
            min_notional=Money(1000.00, USD),
            margin_init=Decimal("0.03"),
            margin_maint=Decimal("0.03"),
            maker_fee=Decimal("0.00002"),
            taker_fee=Decimal("0.00002"),
            financing={},
            timestamp_ns=0,
        )
 def test_not_none_when_arg_none_raises_type_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(TypeError):
         PyCondition.not_none(None, "param")
 def test_type_when_type_is_correct_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.type("a string", str, "param")
 def test_not_negative_int_when_arg_negative_raises_value_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(ValueError):
         PyCondition.not_negative_int(-1, "param")
 def test_not_negative_int_when_args_zero_or_positive_does_nothing(
         self, value):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.not_negative_int(value, "param")
 def test_empty_when_collection_not_empty_raises_value_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(ValueError):
         PyCondition.empty([1, 2], "some_collection")
 def test_empty_when_collection_empty_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.empty([], "some_collection")
 def test_key_not_in_when_key_not_in_dictionary_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.not_in("b", {"a": 1}, "key", "dict")
 def test_key_not_in_when_key_is_in_dictionary_raises_key_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(KeyError):
         PyCondition.not_in("a", {"a": 1}, "key", "dict")
 def test_not_in_when_item_not_in_list_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.not_in("b", ["a", 1], "item", "list")
 def test_valid_port_when_in_range_does_nothing(self):
     # Arrange
     # Act
     # Assert
     PyCondition.valid_port(55555, "port")
 def test_positive_when_args_positive_does_nothing(self, value):
     # Arrange
     # Act
     # Assert: AssertionError not raised
     PyCondition.positive(value, "param")
 def test_false_when_predicate_false_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.false(False, "this should be False")
 def test_true_when_predicate_true_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.true(True, "this should be True")
 def test_not_none_when_arg_not_none_does_nothing(self):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.not_none("something", "param")
 def test_positive_int_when_args_positive_does_nothing(self):
     # Arrange
     # Act
     # Assert: AssertionError not raised
     PyCondition.positive_int(1, "param")
 def test_type_or_none_when_type_is_incorrect_raises_type_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(TypeError):
         PyCondition.type("a string", int, "param")
 def test_in_range_int_when_args_in_range_does_nothing(
         self, value, start, end):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.in_range_int(value, start, end, "param")
    def __init__(
        self,
        strategies: List[TradingStrategy],
        config: Dict[str, object],
    ):
        """
        Initialize a new instance of the TradingNode class.

        Parameters
        ----------
        strategies : list[TradingStrategy]
            The list of strategies to run on the trading node.
        config : dict[str, object]
            The configuration for the trading node.

        Raises
        ------
        ValueError
            If strategies is None or empty.
        ValueError
            If config is None or empty.

        """
        PyCondition.not_none(strategies, "strategies")
        PyCondition.not_none(config, "config")
        PyCondition.not_empty(strategies, "strategies")
        PyCondition.not_empty(config, "config")

        self._config = config

        # Extract configs
        config_trader = config.get("trader", {})
        config_system = config.get("system", {})
        config_log = config.get("logging", {})
        config_cache_db = config.get("cache_database", {})
        config_cache = config.get("cache", {})
        config_data = config.get("data_engine", {})
        config_risk = config.get("risk_engine", {})
        config_exec = config.get("exec_engine", {})
        config_strategy = config.get("strategy", {})

        # System config
        self._timeout_connection = config_system.get("timeout_connection", 5.0)
        self._timeout_reconciliation = config_system.get(
            "timeout_reconciliation", 10.0)
        self._timeout_portfolio = config_system.get("timeout_portfolio", 10.0)
        self._timeout_disconnection = config_system.get(
            "timeout_disconnection", 5.0)
        self._check_residuals_delay = config_system.get(
            "check_residuals_delay", 5.0)
        self._load_strategy_state = config_strategy.get("load_state", True)
        self._save_strategy_state = config_strategy.get("save_state", True)

        # Setup loop
        self._loop = asyncio.get_event_loop()
        self._executor = concurrent.futures.ThreadPoolExecutor()
        self._loop.set_default_executor(self._executor)
        self._loop.set_debug(config_system.get("loop_debug", False))

        # Components
        self._clock = LiveClock(loop=self._loop)
        self._uuid_factory = UUIDFactory()
        self.system_id = self._uuid_factory.generate()
        self.created_time = self._clock.utc_now()
        self._is_running = False

        # Setup identifiers
        self.trader_id = TraderId(
            f"{config_trader['name']}-{config_trader['id_tag']}", )

        # Setup logging
        level_stdout = LogLevelParser.from_str_py(
            config_log.get("level_stdout"))

        self._logger = LiveLogger(
            loop=self._loop,
            clock=self._clock,
            trader_id=self.trader_id,
            system_id=self.system_id,
            level_stdout=level_stdout,
        )

        self._log = LoggerAdapter(
            component=self.__class__.__name__,
            logger=self._logger,
        )

        self._log_header()
        self._log.info("Building...")

        if platform.system() != "Windows":
            # Requires the logger to be initialized
            # Windows does not support signal handling
            # https://stackoverflow.com/questions/45987985/asyncio-loops-add-signal-handler-in-windows
            self._setup_loop()

        # Build platform
        # ----------------------------------------------------------------------

        if config_cache_db["type"] == "redis":
            cache_db = RedisCacheDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
                instrument_serializer=MsgPackInstrumentSerializer(),
                command_serializer=MsgPackCommandSerializer(),
                event_serializer=MsgPackEventSerializer(),
                config={
                    "host": config_cache_db["host"],
                    "port": config_cache_db["port"],
                },
            )
        else:
            cache_db = BypassCacheDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
            )

        cache = Cache(
            database=cache_db,
            logger=self._logger,
            config=config_cache,
        )

        self.portfolio = Portfolio(
            cache=cache,
            clock=self._clock,
            logger=self._logger,
        )

        self._data_engine = LiveDataEngine(
            loop=self._loop,
            portfolio=self.portfolio,
            cache=cache,
            clock=self._clock,
            logger=self._logger,
            config=config_data,
        )

        self._exec_engine = LiveExecutionEngine(
            loop=self._loop,
            portfolio=self.portfolio,
            cache=cache,
            clock=self._clock,
            logger=self._logger,
            config=config_exec,
        )

        self._risk_engine = LiveRiskEngine(
            loop=self._loop,
            exec_engine=self._exec_engine,
            portfolio=self.portfolio,
            cache=cache,
            clock=self._clock,
            logger=self._logger,
            config=config_risk,
        )

        # Wire up components
        self._exec_engine.register_risk_engine(self._risk_engine)
        self._exec_engine.load_cache()

        self.trader = Trader(
            trader_id=self.trader_id,
            strategies=strategies,
            portfolio=self.portfolio,
            data_engine=self._data_engine,
            risk_engine=self._risk_engine,
            exec_engine=self._exec_engine,
            clock=self._clock,
            logger=self._logger,
        )

        if self._load_strategy_state:
            self.trader.load()

        self._builder = TradingNodeBuilder(
            data_engine=self._data_engine,
            exec_engine=self._exec_engine,
            clock=self._clock,
            logger=self._logger,
            log=self._log,
        )

        self._log.info("state=INITIALIZED.")
        self.time_to_initialize = self._clock.delta(self.created_time)
        self._log.info(
            f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.")

        self._is_built = False
 def test_valid_string_given_none_raises_type_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(TypeError):
         PyCondition.valid_string(None, "param")
示例#25
0
 def none():
     PyCondition.none(None, "param")
 def test_false_when_predicate_true_raises_value_error(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(ValueError):
         PyCondition.false(True, "predicate")
示例#27
0
 def valid_string():
     PyCondition.valid_string("abc123", "string_param")
 def test_valid_string_with_valid_string_does_nothing(self, value):
     # Arrange
     # Act
     # Assert: ValueError not raised
     PyCondition.valid_string(value, "param")
 def test_callable_or_none_when_arg_is_callable_or_none_does_nothing(
         self, value):
     # Arrange, Act, Assert: ValueError not raised
     PyCondition.callable_or_none(value, "param")
 def test_raises_custom_exception(self):
     # Arrange
     # Act
     # Assert
     with pytest.raises(RuntimeError):
         PyCondition.true(False, "predicate", RuntimeError)