roboquant 0.2.7__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {roboquant-0.2.7 → roboquant-0.2.8}/PKG-INFO +3 -2
  2. {roboquant-0.2.7 → roboquant-0.2.8}/pyproject.toml +12 -2
  3. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/__init__.py +1 -1
  4. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/brokers/ibkr.py +4 -1
  5. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/event.py +1 -1
  6. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/__init__.py +1 -0
  7. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/alpacafeed.py +11 -23
  8. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/csvfeed.py +13 -3
  9. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/feedutil.py +5 -2
  10. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/historic.py +1 -1
  11. roboquant-0.2.8/roboquant/feeds/live.py +35 -0
  12. roboquant-0.2.8/roboquant/feeds/sp500.json +2527 -0
  13. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/tiingo.py +6 -22
  14. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/yahoo.py +2 -0
  15. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/basicjournal.py +6 -2
  16. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/ml/envs.py +89 -139
  17. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/ml/features.py +163 -36
  18. roboquant-0.2.8/roboquant/ml/strategies.py +59 -0
  19. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/ml/torch.py +23 -38
  20. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/run.py +2 -2
  21. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/__init__.py +0 -1
  22. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/barstrategy.py +6 -3
  23. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/emacrossover.py +5 -4
  24. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/traders/flextrader.py +40 -8
  25. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/traders/sizing.py +2 -2
  26. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant.egg-info/PKG-INFO +3 -2
  27. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant.egg-info/SOURCES.txt +2 -1
  28. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant.egg-info/requires.txt +2 -1
  29. roboquant-0.2.8/roboquant.egg-info/top_level.txt +4 -0
  30. roboquant-0.2.7/roboquant/ml/strategies.py +0 -46
  31. roboquant-0.2.7/roboquant/strategies/nopstrategy.py +0 -12
  32. roboquant-0.2.7/roboquant.egg-info/top_level.txt +0 -1
  33. {roboquant-0.2.7 → roboquant-0.2.8}/LICENSE +0 -0
  34. {roboquant-0.2.7 → roboquant-0.2.8}/README.md +0 -0
  35. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/account.py +0 -0
  36. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/brokers/__init__.py +0 -0
  37. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/brokers/broker.py +0 -0
  38. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/brokers/simbroker.py +0 -0
  39. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/config.py +0 -0
  40. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/aggregate.py +0 -0
  41. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/eventchannel.py +0 -0
  42. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/feed.py +0 -0
  43. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/randomwalk.py +0 -0
  44. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/feeds/sqllitefeed.py +0 -0
  45. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/__init__.py +0 -0
  46. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/alphabeta.py +0 -0
  47. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/feedmetric.py +0 -0
  48. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/journal.py +0 -0
  49. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/metric.py +0 -0
  50. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/metricsjournal.py +0 -0
  51. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/pnlmetric.py +0 -0
  52. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/pricemetric.py +0 -0
  53. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/runmetric.py +0 -0
  54. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/journals/tensorboardjournal.py +0 -0
  55. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/ml/__init__.py +0 -0
  56. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/order.py +0 -0
  57. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/signal.py +0 -0
  58. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/buffer.py +0 -0
  59. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/multistrategy.py +0 -0
  60. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/smacrossover.py +0 -0
  61. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/strategies/strategy.py +0 -0
  62. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/timeframe.py +0 -0
  63. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/traders/__init__.py +0 -0
  64. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant/traders/trader.py +0 -0
  65. {roboquant-0.2.7 → roboquant-0.2.8}/roboquant.egg-info/dependency_links.txt +0 -0
  66. {roboquant-0.2.7 → roboquant-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: roboquant
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: A fast algo-trading platform
5
5
  Author-email: roboquant team <info@roboquant.org>
6
6
  Project-URL: Homepage, https://roboquant.org
@@ -28,7 +28,8 @@ Requires-Dist: requests>=2.31.0
28
28
  Provides-Extra: torch
29
29
  Requires-Dist: torch>=2.1.0; extra == "torch"
30
30
  Requires-Dist: tensorboard>=2.15.2; extra == "torch"
31
- Requires-Dist: stable-baselines3>=2.2.1; extra == "torch"
31
+ Requires-Dist: stable-baselines3[extra]>=2.2.1; extra == "torch"
32
+ Requires-Dist: sb3-contrib>=2.2.1; extra == "torch"
32
33
  Provides-Extra: yahoo
33
34
  Requires-Dist: yfinance~=0.2.36; extra == "yahoo"
34
35
  Provides-Extra: ibkr
@@ -10,6 +10,10 @@ testpaths = [
10
10
 
11
11
  [tool.pyright]
12
12
  reportOptionalOperand = "none"
13
+ exclude = ["samples/*.py"]
14
+
15
+ [tool.pylint.MASTER]
16
+ ignore-paths = 'samples'
13
17
 
14
18
  [tool.pylint.'MESSAGES CONTROL']
15
19
  max-line-length = 127
@@ -22,6 +26,12 @@ max-attributes = 10
22
26
  requires = ["setuptools>=61.0"]
23
27
  build-backend = "setuptools.build_meta"
24
28
 
29
+ [tool.setuptools.packages.find]
30
+ exclude = ["docs*", "tests*", "samples*"]
31
+
32
+ [tool.setuptools.package-data]
33
+ "*" = ["*.json"]
34
+
25
35
  [project]
26
36
  name = "roboquant"
27
37
  dynamic = ["version"]
@@ -54,11 +64,11 @@ dependencies = [
54
64
  version = {attr = "roboquant.__version__"}
55
65
 
56
66
  [project.optional-dependencies]
57
- torch = ["torch>=2.1.0", "tensorboard>=2.15.2", "stable-baselines3>=2.2.1"]
67
+ torch = ["torch>=2.1.0", "tensorboard>=2.15.2", "stable-baselines3[extra]>=2.2.1", "sb3-contrib>=2.2.1"]
58
68
  yahoo = ["yfinance~=0.2.36"]
59
69
  ibkr = ["nautilus-ibapi~=10.19.2"]
60
70
  alpaca = ["alpaca-py"]
61
- all = [ "roboquant[torch,yahoo,ibkr]" ]
71
+ all = ["roboquant[torch,yahoo,ibkr]"]
62
72
 
63
73
 
64
74
  [project.urls]
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.7"
1
+ __version__ = "0.2.8"
2
2
 
3
3
  from roboquant import brokers
4
4
  from roboquant import feeds
@@ -14,7 +14,7 @@ from ibapi.wrapper import EWrapper
14
14
  from roboquant.account import Account, Position
15
15
  from roboquant.event import Event
16
16
  from roboquant.order import Order, OrderStatus
17
- from .broker import Broker, _update_positions
17
+ from roboquant.brokers.broker import Broker, _update_positions
18
18
 
19
19
  assert VERSION["major"] == 10 and VERSION["minor"] == 19, "Wrong version of the IBAPI found"
20
20
 
@@ -144,6 +144,7 @@ class IBKRBroker(Broker):
144
144
  self.__api = api
145
145
  self.__has_new_orders_since_sync = False
146
146
  self.price_type = "DEFAULT"
147
+ self.sleep_after_cancel = 0.0
147
148
 
148
149
  # Start the handling in a thread
149
150
  self.__api_thread = threading.Thread(target=api.run, daemon=True)
@@ -211,6 +212,8 @@ class IBKRBroker(Broker):
211
212
  if order.size.is_zero():
212
213
  assert order.id is not None, "can only cancel orders with an id"
213
214
  self.__api.cancelOrder(int(order.id), "")
215
+ if self.sleep_after_cancel:
216
+ time.sleep(self.sleep_after_cancel)
214
217
  else:
215
218
  if order.id is None:
216
219
  order.id = self.__api.get_next_order_id()
@@ -111,7 +111,7 @@ class Event:
111
111
  """
112
112
 
113
113
  def __init__(self, time: datetime, items: list[Any]):
114
- assert time.tzname() == "UTC", time.tzname()
114
+ assert time.tzname() == "UTC", "event with non UTC timezone"
115
115
  self.time = time
116
116
  self.items = items
117
117
 
@@ -7,6 +7,7 @@ from .historic import HistoricFeed
7
7
  from .randomwalk import RandomWalk
8
8
  from .sqllitefeed import SQLFeed
9
9
  from .tiingo import TiingoLiveFeed, TiingoHistoricFeed
10
+ from .alpacafeed import AlpacaLiveFeed
10
11
 
11
12
  try:
12
13
  from .yahoo import YahooFeed
@@ -1,7 +1,6 @@
1
1
  from array import array
2
2
  from datetime import timedelta
3
3
  import threading
4
- import time
5
4
  from typing import Literal
6
5
 
7
6
  from alpaca.data import DataFeed
@@ -11,11 +10,10 @@ from alpaca.data.live.option import OptionDataStream
11
10
 
12
11
  from roboquant.config import Config
13
12
  from roboquant.event import Event, Quote, Trade, Bar
14
- from roboquant.feeds.eventchannel import EventChannel
15
- from roboquant.feeds.feed import Feed
13
+ from roboquant.feeds.live import LiveFeed
16
14
 
17
15
 
18
- class AlpacaLiveFeed(Feed):
16
+ class AlpacaLiveFeed(LiveFeed):
19
17
 
20
18
  __one_minute = str(timedelta(minutes=1))
21
19
 
@@ -38,34 +36,24 @@ class AlpacaLiveFeed(Feed):
38
36
 
39
37
  thread = threading.Thread(None, self.stream.run, daemon=True)
40
38
  thread.start()
41
- self._channel = None
42
-
43
- def play(self, channel: EventChannel):
44
- self._channel = channel
45
- while not channel.is_closed:
46
- time.sleep(1)
47
- self._channel = None
48
39
 
49
40
  async def close(self):
50
41
  await self.stream.close()
51
42
 
52
43
  async def __handle_trades(self, data):
53
- if self._channel:
54
- item = Trade(data.symbol, data.price, data.size)
55
- event = Event(data.timestamp, [item])
56
- self._channel.put(event)
44
+ item = Trade(data.symbol, data.price, data.size)
45
+ event = Event(data.timestamp, [item])
46
+ self.put(event)
57
47
 
58
48
  async def __handle_bars(self, data):
59
- if self._channel:
60
- item = Bar(data.symbol, array("f", [data.open, data.high, data.low, data.close, data.volume]), self.__one_minute)
61
- event = Event(data.timestamp, [item])
62
- self._channel.put(event)
49
+ item = Bar(data.symbol, array("f", [data.open, data.high, data.low, data.close, data.volume]), self.__one_minute)
50
+ event = Event(data.timestamp, [item])
51
+ self.put(event)
63
52
 
64
53
  async def __handle_quotes(self, data):
65
- if self._channel:
66
- item = Quote(data.symbol, array("f", [data.ask_price, data.ask_size, data.bid_price, data.bid_size]))
67
- event = Event(data.timestamp, [item])
68
- self._channel.put(event)
54
+ item = Quote(data.symbol, array("f", [data.ask_price, data.ask_size, data.bid_price, data.bid_size]))
55
+ event = Event(data.timestamp, [item])
56
+ self.put(event)
69
57
 
70
58
  def subscribe_trades(self, *symbols: str):
71
59
  self.stream.subscribe_trades(self.__handle_trades, *symbols)
@@ -1,4 +1,5 @@
1
1
  import csv
2
+ import sys
2
3
  import logging
3
4
  import os
4
5
  import pathlib
@@ -87,9 +88,18 @@ class CSVFeed(HistoricFeed):
87
88
 
88
89
  class StooqCSVFeed(CSVFeed):
89
90
  def __init__(self):
90
- super().__init__(
91
- path, columns=columns, time_offset="21:00:00+00:00", datetime_fmt="%Y%m%d", endswith=".txt", frequency="1d"
92
- )
91
+ # from Python 3.11 onwards we can use the fast standard ISO parsing
92
+ if sys.version_info >= (3, 11):
93
+ super().__init__(path, columns=columns, time_offset="21:00:00+00:00", endswith=".txt", frequency="1d")
94
+ else:
95
+ super().__init__(
96
+ path,
97
+ columns=columns,
98
+ time_offset="21:00:00+00:00",
99
+ datetime_fmt="%Y%m%d",
100
+ endswith=".txt",
101
+ frequency="1d",
102
+ )
93
103
 
94
104
  def _get_symbol(self, filename: str):
95
105
  base = pathlib.Path(filename).stem.upper()
@@ -23,8 +23,11 @@ def get_symbol_prices(
23
23
  return x, y
24
24
 
25
25
 
26
- def get_symbol_ohlcv(feed: Feed, symbol: str, timeframe: Timeframe | None = None) -> dict[str, list]:
27
- """Get the OHLCV values for a single symbol from a feed"""
26
+ def get_ohlcv(feed: Feed, symbol: str, timeframe: Timeframe | None = None) -> dict[str, list]:
27
+ """Get the OHLCV values for a symbol from a feed.
28
+ The returned value is a dict with the keys being "Date", "Open", "High", "Low", "Close", "Volume"
29
+ and the values a list.
30
+ """
28
31
 
29
32
  result = {column: [] for column in ["Date", "Open", "High", "Low", "Close", "Volume"]}
30
33
  channel = feed.play_background(timeframe)
@@ -11,7 +11,7 @@ from .feed import Feed
11
11
 
12
12
  class HistoricFeed(Feed, ABC):
13
13
  """
14
- Abstract base class for other feeds that produce historic price-items.
14
+ Abstract base class for feeds that produce historic price-items.
15
15
  """
16
16
 
17
17
  def __init__(self):
@@ -0,0 +1,35 @@
1
+ from datetime import datetime, timedelta
2
+ import time
3
+
4
+ from roboquant.event import Event
5
+ from .eventchannel import ChannelClosed, EventChannel
6
+ from .feed import Feed
7
+
8
+
9
+ class LiveFeed(Feed):
10
+ """
11
+ Abstract base class for feeds that produce live price-items. It will ensure that
12
+ events that are published are monotonic in time (so always increasing).
13
+ """
14
+
15
+ def __init__(self):
16
+ super().__init__()
17
+ self._channel = None
18
+ self._last_time = datetime.fromisoformat("1900-01-01T00:00:00+00:00")
19
+ self._min_change = timedelta(microseconds=1)
20
+
21
+ def play(self, channel: EventChannel):
22
+ self._channel = channel
23
+ while not channel.is_closed:
24
+ time.sleep(1)
25
+ self._channel = None
26
+
27
+ def put(self, event: Event):
28
+ if self._channel:
29
+ try:
30
+ if event.time <= self._last_time:
31
+ event.time = self._last_time + self._min_change
32
+ self._channel.put(event)
33
+ self._last_time = event.time
34
+ except ChannelClosed:
35
+ pass