diff --git a/roboquant/account.py b/roboquant/account.py index b6d9adf..a089c54 100644 --- a/roboquant/account.py +++ b/roboquant/account.py @@ -76,11 +76,11 @@ def position_value(self, asset: Asset) -> float: def short_positions(self) -> dict[Asset, Position]: """Return al the short positions in the account""" - return {symbol: position for (symbol, position) in self.positions.items() if position.is_short} + return {asset: position for (asset, position) in self.positions.items() if position.is_short} def long_positions(self) -> dict[Asset, Position]: """Return al the long positions in the account""" - return {symbol: position for (symbol, position) in self.positions.items() if position.is_long} + return {asset: position for (asset, position) in self.positions.items() if position.is_long} def contract_value(self, asset: Asset, size: Decimal, price: float) -> float: """Contract value denoted in the base currency of the account""" diff --git a/roboquant/alpaca/feed.py b/roboquant/alpaca/feed.py index f016714..d1aab01 100644 --- a/roboquant/alpaca/feed.py +++ b/roboquant/alpaca/feed.py @@ -45,6 +45,11 @@ def _get_asset(symbol: str, asset_class: AssetClass) -> Asset: return Option(symbol) +def _assert_keys(api_key, secret_key): + assert api_key, "no api key provided or found" + assert secret_key, "no secret key provided or found" + + class AlpacaLiveFeed(LiveFeed): """Subscribe to live market data for stocks, cryptocurrencies or options""" @@ -55,6 +60,7 @@ def __init__(self, market: Literal["iex", "sip", "crypto", "option"] = "iex", ap config = Config() api_key = api_key or config.get("alpaca.public.key") secret_key = secret_key or config.get("alpaca.secret.key") + _assert_keys(api_key, secret_key) self.market = market match market: @@ -153,6 +159,7 @@ def __init__(self, api_key=None, secret_key=None, data_api_url=None, feed: DataF config = Config() api_key = api_key or config.get("alpaca.public.key") secret_key = secret_key or config.get("alpaca.secret.key") + _assert_keys(api_key, secret_key) self.client = StockHistoricalDataClient(api_key, secret_key, url_override=data_api_url) self.feed = feed @@ -189,6 +196,7 @@ def __init__(self, api_key=None, secret_key=None, data_api_url=None): config = Config() api_key = api_key or config.get("alpaca.public.key") secret_key = secret_key or config.get("alpaca.secret.key") + _assert_keys(api_key, secret_key) self.client = CryptoHistoricalDataClient(api_key, secret_key, url_override=data_api_url) def retrieve_bars(self, *symbols, start=None, end=None, resolution: TimeFrame | None = None): diff --git a/roboquant/brokers/broker.py b/roboquant/brokers/broker.py index ab52c78..3dccd59 100644 --- a/roboquant/brokers/broker.py +++ b/roboquant/brokers/broker.py @@ -46,8 +46,8 @@ def _update_account(account: Account, event: Event | None, price_type: str = "DE account.last_update = event.time - for symbol, position in account.positions.items(): - if price := event.get_price(symbol, price_type): + for asset, position in account.positions.items(): + if price := event.get_price(asset, price_type): position.mkt_price = price @@ -58,8 +58,8 @@ def _update_positions(account: Account, event: Event | None, price_type: str = " account.last_update = event.time - for symbol, position in account.positions.items(): - if price := event.get_price(symbol, price_type): + for asset, position in account.positions.items(): + if price := event.get_price(asset, price_type): position.mkt_price = price diff --git a/roboquant/feeds/feed.py b/roboquant/feeds/feed.py index 7ffa5c9..f7209de 100644 --- a/roboquant/feeds/feed.py +++ b/roboquant/feeds/feed.py @@ -72,7 +72,7 @@ def __background(): thread.start() return channel - def get_ohlcv(self, asset: Asset, timeframe: Timeframe | None = None) -> dict[str, list]: + def get_ohlcv(self, asset: Asset, timeframe: Timeframe | None = None) -> dict[str, list[float | datetime]]: """Get the OHLCV values for an asset in this feed. The returned value is a dict with the keys being "Date", "Open", "High", "Low", "Close", "Volume" and the values a list. @@ -91,7 +91,7 @@ def get_ohlcv(self, asset: Asset, timeframe: Timeframe | None = None) -> dict[st result["Volume"].append(item.ohlcv[4]) return result - def print_items(self, timeframe: Timeframe | None = None, timeout: float | None = None): + def print_items(self, timeframe: Timeframe | None = None, timeout: float | None = None) -> None: """Print the items in a feed to the console. This is mostly useful for debugging purposes to see what items a feed generates. """ @@ -102,7 +102,7 @@ def print_items(self, timeframe: Timeframe | None = None, timeout: float | None for item in event.items: print("======> ", item) - def count_events(self, timeframe: Timeframe | None = None, timeout: float | None = None, include_empty=False): + def count_events(self, timeframe: Timeframe | None = None, timeout: float | None = None, include_empty=False) -> int: """Count the number of events in a feed""" channel = self.play_background(timeframe) @@ -112,7 +112,7 @@ def count_events(self, timeframe: Timeframe | None = None, timeout: float | None events += 1 return events - def count_items(self, timeframe: Timeframe | None = None, timeout: float | None = None): + def count_items(self, timeframe: Timeframe | None = None, timeout: float | None = None) -> int: """Count the number of events in a feed""" channel = self.play_background(timeframe) @@ -121,7 +121,11 @@ def count_items(self, timeframe: Timeframe | None = None, timeout: float | None items += len(evt.items) return items - def to_dict(self, *assets: Asset, timeframe: Timeframe | None = None, price_type: str = "DEFAULT"): + def to_dict( + self, *assets: Asset, timeframe: Timeframe | None = None, price_type: str = "DEFAULT" + ) -> dict[str, list[float | None]]: + """Return the prices of one or more assets as a dict with the key being the synbol name.""" + assert assets, "provide at least 1 asset" result = {asset.symbol: [] for asset in assets} channel = self.play_background(timeframe) @@ -131,16 +135,14 @@ def to_dict(self, *assets: Asset, timeframe: Timeframe | None = None, price_type result[asset.symbol].append(price) return result - def plot( - self, asset: Asset, price_type: str = "DEFAULT", timeframe: Timeframe | None = None, plt: Any = pyplot, **kwargs - ): + def plot(self, asset: Asset, price_type: str = "DEFAULT", timeframe: Timeframe | None = None, plt: Any = pyplot, **kwargs): """ - Plot the prices of a symbol. + Plot the prices of a single asset. Parameters ---------- asset : Asset - The symbol for which to plot prices. + The asset for which to plot prices. price_type : str, optional The type of price to plot, e.g. open, close, high, low. (default is "DEFAULT") timeframe : Timeframe or None, optional diff --git a/roboquant/journals/alphabeta.py b/roboquant/journals/alphabeta.py index 8c846b4..b8c74d3 100644 --- a/roboquant/journals/alphabeta.py +++ b/roboquant/journals/alphabeta.py @@ -31,10 +31,10 @@ def __init__(self, window_size: int, price_type: str = "DEFAULT", risk_free_retu def __get_market_value(self, prices: dict[Asset, float]): cnt = 0 result = 0.0 - for symbol in prices.keys(): - if symbol in self.__last_prices: + for asset in prices.keys(): + if asset in self.__last_prices: cnt += 1 - result += prices[symbol] / self.__last_prices[symbol] + result += prices[asset] / self.__last_prices[asset] return 1.0 if cnt == 0 else result / cnt def __update(self, equity, prices): diff --git a/roboquant/strategies/buffer.py b/roboquant/strategies/buffer.py index b46d989..aec1c2a 100644 --- a/roboquant/strategies/buffer.py +++ b/roboquant/strategies/buffer.py @@ -87,8 +87,8 @@ def __init__(self, size: int): self.size = size def add_event(self, event: Event) -> set[Asset]: - """Add a new event and return all the symbols that have been added and are ready to be processed""" - symbols: set[Asset] = set() + """Add a new event and return all the assets that have been added and are ready to be processed""" + assets: set[Asset] = set() for item in event.items: if isinstance(item, Bar): asset = item.asset @@ -97,8 +97,8 @@ def add_event(self, event: Event) -> set[Asset]: ohlcv = self[asset] ohlcv.append(item.ohlcv) if ohlcv.is_full(): - symbols.add(asset) - return symbols + assets.add(asset) + return assets def ready(self): - return {symbol for symbol, ohlcv in self.items() if ohlcv.is_full()} + return {asset for asset, ohlcv in self.items() if ohlcv.is_full()} diff --git a/roboquant/strategies/multistrategy.py b/roboquant/strategies/multistrategy.py index 18d310a..4a9a5df 100644 --- a/roboquant/strategies/multistrategy.py +++ b/roboquant/strategies/multistrategy.py @@ -9,10 +9,10 @@ class MultiStrategy(Strategy): """Combine one or more signal strategies. The MultiStrategy provides additional control on how to handle conflicting - signals for the same symbols via the signal_filter: + signals for the same asset via the signal_filter: - - first: in case of multiple signals for the same symbol, the first one wins - - last: in case of multiple signals for the same symbol, the last one wins. + - first: in case of multiple signals for the same asset, the first one wins + - last: in case of multiple signals for the same asset, the last one wins. - mean: return the mean of the signal ratings. All signals will be ENTRY and EXIT. - none: return all signals. This is also the default. """ diff --git a/roboquant/strategies/tastrategy.py b/roboquant/strategies/tastrategy.py index 1b37072..8bd1fd5 100644 --- a/roboquant/strategies/tastrategy.py +++ b/roboquant/strategies/tastrategy.py @@ -36,6 +36,6 @@ def create_signals(self, event) -> list[Signal]: @abstractmethod def process_asset(self, asset: Asset, ohlcv: OHLCVBuffer) -> Signal | None: """ - Create zero or more orders for the provided symbol + Create zero or more orders for the provided asset """ ... diff --git a/roboquant/traders/flextrader.py b/roboquant/traders/flextrader.py index 5ee0f4d..7293efa 100644 --- a/roboquant/traders/flextrader.py +++ b/roboquant/traders/flextrader.py @@ -55,7 +55,7 @@ def log(self, rule: str, **kwargs): if logger.isEnabledFor(logging.INFO): extra = " ".join(f"{k}={v}" for k, v in kwargs.items()) logger.info( - "Discarded signal because %s [symbol=%s rating=%s type=%s position=%s %s]", + "Discarded signal because %s [asset=%s rating=%s type=%s position=%s %s]", rule, self.signal.asset, self.signal.rating, @@ -67,11 +67,11 @@ def log(self, rule: str, **kwargs): class FlexTrader(Trader): """Implementation of a Trader that has configurable rules to modify which signals are converted into orders. - This implementation will not generate orders if there is not a price in the event for the underlying symbol. + This implementation will not generate orders if there is not a price in the event for the underlying asset. The configurable parameters include: - - one_order_only: don't create new orders for a symbol if there is already an open orders for that same symbol + - one_order_only: don't create new orders for a asset if there is already an open orders for that same asset - size_fractions: enable fractional order sizes (if size_fractions is larger than 0), default is 0 - safety_margin_perc: the safety margin as percentage of equity that should remain available (to avoid margin calls), default is 0.05 (5%) @@ -221,7 +221,7 @@ def create_orders(self, signals: list[Signal], event: Event, account: Account) - def _get_orders(self, asset: Asset, size: Decimal, item: PriceItem, signal: Signal, dt: datetime) -> list[Order]: # pylint: disable=unused-argument - """Return zero or more orders for the provided symbol and size.""" + """Return zero or more orders for the provided asset and size.""" gtd = None if not self.valid_for else dt + self.valid_for return [Order(asset, size, item.price(), gtd)] diff --git a/tests/samples/sb3_strategy_quotes.py b/tests/samples/sb3_strategy_quotes.py index d2d84d9..46f6900 100644 --- a/tests/samples/sb3_strategy_quotes.py +++ b/tests/samples/sb3_strategy_quotes.py @@ -5,7 +5,6 @@ from roboquant.asset import Stock from roboquant.ml.features import EquityFeature, QuoteFeature from roboquant.ml.rl import TradingEnv, SB3PolicyStrategy -from roboquant.feeds.parquet import ParquetFeed from roboquant.timeframe import Timeframe # %% @@ -17,12 +16,8 @@ assert start < border < end # %% -feed = ParquetFeed("/tmp/jpm.parquet") -if not feed.exists(): - inputFeed = AlpacaHistoricStockFeed() - inputFeed.retrieve_quotes(asset.symbol, start=start, end=end) - feed.record(inputFeed) - +feed = AlpacaHistoricStockFeed() +feed.retrieve_quotes(asset.symbol, start=start, end=end) print("feed timeframe=", feed.timeframe()) obs_feature = QuoteFeature(asset).returns().normalize(20) diff --git a/tests/samples/talib_feature.py b/tests/samples/talib_feature.py index 8a4f334..d89dd7a 100644 --- a/tests/samples/talib_feature.py +++ b/tests/samples/talib_feature.py @@ -1,12 +1,11 @@ # %% +# pylint: disable=no-member import talib.stream as ta import roboquant as rq from roboquant.asset import Asset from roboquant.ml.features import TaFeature from roboquant.strategies.buffer import OHLCVBuffer -# pylint: disable=no-member - # %% class RSIFeature(TaFeature): diff --git a/tests/samples/talib_strategy.py b/tests/samples/talib_strategy.py index 313146e..a8e8e5b 100644 --- a/tests/samples/talib_strategy.py +++ b/tests/samples/talib_strategy.py @@ -1,11 +1,10 @@ # %% +# pylint: disable=no-member import talib.stream as ta import roboquant as rq from roboquant.signal import Signal from roboquant.strategies import OHLCVBuffer, TaStrategy -# pylint: disable=no-member - # %% class MyStrategy(TaStrategy): diff --git a/tests/samples/walkforward_yahoo.py b/tests/samples/walkforward_yahoo.py index 9427a96..9725eec 100644 --- a/tests/samples/walkforward_yahoo.py +++ b/tests/samples/walkforward_yahoo.py @@ -2,10 +2,10 @@ import roboquant as rq # %% -feed = rq.feeds.YahooFeed("JPM", "IBM", "F", start_date="2000-01-01") +feed = rq.feeds.YahooFeed("JPM", "IBM", "F", start_date="2000-01-01", end_date="2020-01-01") # %% -# split the feed timeframe in 4 equal parts +# split the feed timeframe into 4 parts timeframes = feed.timeframe().split(4) # %%