roboquant 2.4.1__tar.gz → 2.4.2__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-2.4.1 → roboquant-2.4.2}/PKG-INFO +2 -1
  2. {roboquant-2.4.1 → roboquant-2.4.2}/pyproject.toml +3 -2
  3. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/account.py +23 -4
  4. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/brokers/simbroker.py +15 -14
  5. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/monetary.py +24 -12
  6. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/order.py +12 -20
  7. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/timeseries.py +3 -3
  8. {roboquant-2.4.1 → roboquant-2.4.2}/LICENSE +0 -0
  9. {roboquant-2.4.1 → roboquant-2.4.2}/README.md +0 -0
  10. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/__init__.py +0 -0
  11. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ai/__init__.py +0 -0
  12. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ai/features.py +0 -0
  13. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ai/rl.py +0 -0
  14. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ai/strategies.py +0 -0
  15. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/alpaca/__init__.py +0 -0
  16. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/alpaca/broker.py +0 -0
  17. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/alpaca/feed.py +0 -0
  18. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/asset.py +0 -0
  19. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/brokers/__init__.py +0 -0
  20. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/brokers/broker.py +0 -0
  21. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/crypto/__init__.py +0 -0
  22. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/crypto/cryptobroker.py +0 -0
  23. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/crypto/cryptofeed.py +0 -0
  24. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/event.py +0 -0
  25. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/__init__.py +0 -0
  26. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/csvfeed.py +0 -0
  27. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/feed.py +0 -0
  28. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/historic.py +0 -0
  29. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/live.py +0 -0
  30. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/parquet.py +0 -0
  31. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/randomwalk.py +0 -0
  32. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/resources/us10.parquet +0 -0
  33. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/sql.py +0 -0
  34. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/util.py +0 -0
  35. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/feeds/yahoo.py +0 -0
  36. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ibkr/__init__.py +0 -0
  37. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ibkr/broker.py +0 -0
  38. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ibkr/types.py +0 -0
  39. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/__init__.py +0 -0
  40. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/alphabeta.py +0 -0
  41. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/basicjournal.py +0 -0
  42. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/feedmetric.py +0 -0
  43. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/journal.py +0 -0
  44. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/marketmetric.py +0 -0
  45. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/metric.py +0 -0
  46. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/metricsjournal.py +0 -0
  47. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/pnlmetric.py +0 -0
  48. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/pricemetric.py +0 -0
  49. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/runmetric.py +0 -0
  50. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/scorecard.py +0 -0
  51. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/journals/tensorboard.py +0 -0
  52. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/run.py +0 -0
  53. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/signal.py +0 -0
  54. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/__init__.py +0 -0
  55. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/buffer.py +0 -0
  56. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/cachedstrategy.py +0 -0
  57. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/emacrossover.py +0 -0
  58. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/ibsstrategy.py +0 -0
  59. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/multistrategy.py +0 -0
  60. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/strategy.py +0 -0
  61. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/strategies/tastrategy.py +0 -0
  62. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/ta/__init__.py +0 -0
  63. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/timeframe.py +0 -0
  64. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/traders/__init__.py +0 -0
  65. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/traders/flextrader.py +0 -0
  66. {roboquant-2.4.1 → roboquant-2.4.2}/roboquant/traders/trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roboquant
3
- Version: 2.4.1
3
+ Version: 2.4.2
4
4
  Summary: A fast algorithmic trading platform with support for AI based strategies
5
5
  Keywords: algorithmic,trading,machine learning,AI,investment,finance,crypto,stocks,exchange,forex
6
6
  Author: roboquant team
@@ -23,6 +23,7 @@ Requires-Dist: requests>2.30.0
23
23
  Requires-Dist: yfinance>1.5.0
24
24
  Requires-Dist: pyarrow>24.0.0
25
25
  Requires-Dist: ta-lib==0.6.5
26
+ Requires-Dist: pandas>=2.2.0
26
27
  Requires-Dist: torch>=2.9.0 ; extra == 'ai'
27
28
  Requires-Dist: tensorboard>=2.21.0 ; extra == 'ai'
28
29
  Requires-Dist: stable-baselines3>=2.9.0 ; extra == 'ai'
@@ -22,7 +22,7 @@ module-name = "roboquant"
22
22
 
23
23
  [project]
24
24
  name = "roboquant"
25
- version = "2.4.1"
25
+ version = "2.4.2"
26
26
  authors = [{ name = "roboquant team", email = "info@roboquant.org" }]
27
27
  description = "A fast algorithmic trading platform with support for AI based strategies"
28
28
  readme = "README.md"
@@ -59,7 +59,8 @@ dependencies = [
59
59
  "requests>2.30.0",
60
60
  "yfinance>1.5.0",
61
61
  "pyarrow>24.0.0",
62
- "ta-lib==0.6.5"
62
+ "ta-lib==0.6.5",
63
+ "pandas>=2.2.0"
63
64
  ]
64
65
 
65
66
  [project.optional-dependencies]
@@ -1,7 +1,9 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, asdict
2
2
  from datetime import datetime
3
3
  from decimal import Decimal
4
4
 
5
+ import pandas as pd
6
+
5
7
  from roboquant.asset import Asset
6
8
  from roboquant.monetary import USD, Amount, Currency, Wallet
7
9
  from roboquant.order import Order
@@ -24,8 +26,8 @@ class Trade:
24
26
  asset: Asset
25
27
  time: datetime
26
28
  size: Decimal
27
- price: float
28
- pnl: float
29
+ price: float = float("nan")
30
+ pnl: float = float("nan")
29
31
 
30
32
 
31
33
  @dataclass(slots=True)
@@ -40,9 +42,12 @@ class Position:
40
42
  avg_price: float
41
43
  """Average price paid denoted in the currency of the asset"""
42
44
 
43
- mkt_price: float
45
+ mkt_price: float = float("nan")
44
46
  """Latest market price denoted in the currency of the asset"""
45
47
 
48
+ pnl: float = float("nan")
49
+ """The pnl for this position"""
50
+
46
51
  @property
47
52
  def is_short(self):
48
53
  """Return True if this is a short position, False otherwise"""
@@ -282,3 +287,17 @@ class Account:
282
287
  f"last update : {self.last_update}"
283
288
  )
284
289
  return result
290
+
291
+
292
+ def trades_to_dataframe(self) -> pd.DataFrame:
293
+ """Return the trades as a dataframe"""
294
+ return pd.json_normalize([asdict(trade) for trade in self.trades])
295
+
296
+ def orders_to_dataframe(self)-> pd.DataFrame:
297
+ """Return the orders as a dataframe"""
298
+ return pd.json_normalize([asdict(order) for order in self.orders])
299
+
300
+ def positions_to_dataframe(self)-> pd.DataFrame:
301
+ """Return the positions as a dataframe"""
302
+ return pd.json_normalize([asdict(asset) | asdict(pos) for asset, pos in self.positions.items()])
303
+
@@ -134,8 +134,7 @@ class SimBroker(Broker):
134
134
  """Simulate a market execution and return a Trade object if the order has (partially) been executed."""
135
135
 
136
136
  price = self._get_execution_price(order, item)
137
- executable = price <= order.limit if order.is_buy else price >= order.limit
138
- if executable:
137
+ if order.is_executable(price):
139
138
  fill = self._get_fill(order, price)
140
139
  fee = self._get_fee(Trade(order.asset, time, fill, price, 0.0))
141
140
  pnl = self._pnl(order.asset, order.remaining, price) - fee
@@ -151,9 +150,10 @@ class SimBroker(Broker):
151
150
 
152
151
  account.last_update = event.time
153
152
 
154
- for asset, position in account.positions.items():
153
+ for asset, pos in account.positions.items():
155
154
  if price := event.get_price(asset, price_type):
156
- position.mkt_price = price
155
+ pos.mkt_price = price
156
+ pos.pnl = asset.contract_value(pos.size, pos.avg_price - price)
157
157
 
158
158
  def __next_order_id(self) -> str:
159
159
  """Generate a new order id. The order id is a simple integer that is incremented for each new order."""
@@ -181,9 +181,15 @@ class SimBroker(Broker):
181
181
  del self._orders[order.id]
182
182
  self._order_entry.pop(order.id)
183
183
 
184
- def _update_order(self, order: Order) -> None:
185
- """Remove an order from the account, called when an order is completed, expired or cancelled."""
186
- self._orders[order.id] = order
184
+
185
+ def _fill_order(self, order: Order, trade: Trade) -> None:
186
+ """Fill an order"""
187
+ new_fill = order.fill + trade.size
188
+ order = replace(order, fill = new_fill)
189
+ if order.remaining.is_zero():
190
+ self._remove_order(order)
191
+ else:
192
+ self._orders[order.id] = order
187
193
 
188
194
  def _order_is_expired(self, order: Order, time: datetime) -> bool:
189
195
  if order.tif == "GTC":
@@ -212,13 +218,8 @@ class SimBroker(Broker):
212
218
  if trade is not None:
213
219
  logger.info("executed order=%s trx=%s", order, trade)
214
220
  self._update_account(trade)
215
- new_fill = order.fill + trade.size
216
- order = replace(order, fill = new_fill)
217
- if not order.remaining:
218
- logger.info("completed order %s", order)
219
- self._remove_order(order)
220
- else:
221
- self._update_order(order)
221
+ self._fill_order(order, trade)
222
+
222
223
 
223
224
  def _calculate_open_orders(self) -> Wallet:
224
225
  """Calculate the buying power required for the open orders"""
@@ -19,8 +19,8 @@ logger = logging.getLogger(__name__)
19
19
 
20
20
 
21
21
  class Currency(str):
22
- """Currency class represents a monetary currency and is a subclass of `str`. It doesn't
23
- differentiate between fiat- and crypto-currencies.
22
+ """Represents a monetary currency and is a subclass of `str`.
23
+ It doesn't differentiate between fiat- and crypto-currencies.
24
24
 
25
25
  It is possible to create an `Amount` using a combination of a `number` and a `Currency`:
26
26
  ```
@@ -138,18 +138,20 @@ class CurrencyConverter(ABC):
138
138
 
139
139
 
140
140
  class NoConversion(CurrencyConverter):
141
- """The default currency converter that doesn't convert between currencies. If you don't trade in different currencies, this
142
- will be sufficient. It will raise an exception if a conversion is required."""
141
+ """The default currency converter that doesn't convert between currencies.
142
+ If you don't trade in different currencies, this will be sufficient.
143
+ It will raise an exception if a conversion is required during a run."""
143
144
 
144
145
  def convert(self, amount: "Amount", to_currency: Currency, dt: datetime) -> float:
145
- """Raise an error as no conversion is supported."""
146
+ """Raise an error as no conversions are supported."""
146
147
  raise NotImplementedError("The default NoConversion doesn't support any conversions")
147
148
 
148
149
 
149
150
  class ECBConversion(CurrencyConverter):
150
151
  """CurrencyConverter that retrieves its exchange rates from the ECB (European Central Bank).
151
- These exchange rates are based on the Euro and are updated daily by the ECB. They don't contain
152
- any historical data from before the Euro was introduced in 1999.
152
+ These exchange rates are based on the Euro and are updated daily by the ECB.
153
+ They don't contain any historical data from before the Euro was introduced in 1999 and also
154
+ there is not support for crypto-currencies.
153
155
  """
154
156
 
155
157
  __file_name = Path.home() / ".roboquant" / "eurofxref-hist.csv"
@@ -158,7 +160,7 @@ class ECBConversion(CurrencyConverter):
158
160
  """Initialize the ECBConversion.
159
161
 
160
162
  Args:
161
- force_download (bool): Force download of the latest exchange rates if True.
163
+ force_download: Force download of the latest exchange rates if True.
162
164
  """
163
165
  self._rates: Dict[Currency, List[Any]] = {}
164
166
  if force_download or not self.up_to_date():
@@ -400,6 +402,8 @@ class Amount:
400
402
  """
401
403
  return f"{self.value:{format_spec}}@{self.currency}"
402
404
 
405
+ def __bool__(self) -> bool:
406
+ return self.value.__bool__()
403
407
 
404
408
  class Wallet(defaultdict[Currency, float]):
405
409
  """A wallet holds monetary values of different currencies.
@@ -500,6 +504,14 @@ class Wallet(defaultdict[Currency, float]):
500
504
  result.update(self)
501
505
  return result
502
506
 
507
+ def deposit(self, amount: Amount) -> None:
508
+ """Deposit an amount into this wallet"""
509
+ self[amount.currency] += amount.value
510
+
511
+ def withdraw(self, amount: Amount) -> None:
512
+ """Withdraw an amount from this wallet"""
513
+ self[amount.currency] -= amount.value
514
+
503
515
  def convert_to(self, currency: Currency, dt: datetime) -> float:
504
516
  """Convert all the amounts held in this wallet to a single currency and return the value.
505
517
 
@@ -514,16 +526,16 @@ class Wallet(defaultdict[Currency, float]):
514
526
 
515
527
  def __repr__(self) -> str:
516
528
  """Return a string representation of the wallet without any formatting on the
517
- monetary values.
529
+ monetary values. Zero amounts will not be shown.
518
530
 
519
531
  Returns:
520
532
  The string representation.
521
533
  """
522
- return " + ".join([f"{a}" for a in self.amounts()])
534
+ return " + ".join([f"{a}" for a in self.amounts() if a])
523
535
 
524
536
  def __format__(self, format_spec: str) -> str:
525
537
  """Return a formatted string representation of the wallet. All the amounts
526
- will be formatted according to the format_spec.
538
+ will be formatted according to the format_spec. Zero amounts will not be shown.
527
539
 
528
540
  Args:
529
541
  format_spec: The format specification, same as is used for float.
@@ -532,4 +544,4 @@ class Wallet(defaultdict[Currency, float]):
532
544
  Returns:
533
545
  The formatted string representation.
534
546
  """
535
- return " + ".join([f"{a:{format_spec}}" for a in self.amounts()])
547
+ return " + ".join([f"{a:{format_spec}}" for a in self.amounts() if a])
@@ -21,7 +21,7 @@ class Order:
21
21
  """
22
22
 
23
23
  asset: Asset
24
- """The asset of this order."""
24
+ """The underlying asset of this order."""
25
25
 
26
26
  size: Decimal
27
27
  """The size (number of contracts) of the order. Positive for buy orders, negative for sell orders.
@@ -95,7 +95,7 @@ class Order:
95
95
 
96
96
  def value(self) -> float:
97
97
  """
98
- Return the total contract value of this order, it ignores the already filled part of the order.
98
+ The total contract value of this order, it ignores the already filled part of the order.
99
99
 
100
100
  Returns:
101
101
  float: The total contract value of the order.
@@ -104,7 +104,7 @@ class Order:
104
104
 
105
105
  def remaining_value(self) -> float:
106
106
  """
107
- Return the remaining contract value of this order denoted in the currency of the asset.
107
+ The remaining contract value of this order denoted in the currency of the asset.
108
108
 
109
109
  Returns:
110
110
  float: The remaining contract value of the order.
@@ -113,7 +113,7 @@ class Order:
113
113
 
114
114
  def amount(self) -> Amount:
115
115
  """
116
- Return the total value of this order as a single Amount denoted in the currency of the asset.
116
+ The total market value of this order as a single Amount denoted in the currency of the asset.
117
117
 
118
118
  Returns:
119
119
  Amount: The total value of the order.
@@ -123,39 +123,31 @@ class Order:
123
123
  @property
124
124
  def is_buy(self) -> bool:
125
125
  """
126
- Return True if this is a BUY order, False otherwise.
127
-
128
- Returns:
129
- bool: True if this is a BUY order, False otherwise.
126
+ True if this is a BUY order, False otherwise.
130
127
  """
131
128
  return self.size > 0
132
129
 
133
130
  @property
134
131
  def is_sell(self) -> bool:
135
132
  """
136
- Return True if this is a SELL order, False otherwise.
137
-
138
- Returns:
139
- bool: True if this is a SELL order, False otherwise.
133
+ True if this is a SELL order, False otherwise.
140
134
  """
141
135
  return self.size < 0
142
136
 
143
137
  @property
144
138
  def remaining(self) -> Decimal:
145
139
  """
146
- Return the remaining order size that still needs to be filled.
147
-
148
- Returns:
149
- Decimal: The remaining order size.
140
+ The remaining order size that still needs to be filled.
150
141
  """
151
142
  return self.size - self.fill
152
143
 
153
144
  @property
154
145
  def is_cancellation(self) -> bool:
155
146
  """
156
- Return True if this is a cancellation order, False otherwise.
157
-
158
- Returns:
159
- bool: True if this is a cancellation order, False otherwise.
147
+ True if this is a cancellation order, False otherwise.
160
148
  """
161
149
  return self.size.is_zero()
150
+
151
+ def is_executable(self, price: float) -> bool:
152
+ """Will this order execute given the provided price"""
153
+ return price <= self.limit if self.is_buy else price >= self.limit
@@ -2,10 +2,11 @@
2
2
  from dataclasses import dataclass
3
3
  from datetime import datetime
4
4
 
5
- from roboquant.timeframe import Timeframe
5
+ import pandas as pd
6
6
 
7
+ from roboquant.timeframe import Timeframe
7
8
 
8
- @dataclass
9
+ @dataclass(slots=True, frozen=True)
9
10
  class TimeSeries:
10
11
  """A time series contains a name, a timeline and the values of the metric at each point in time. It
11
12
  is used in several places in roboquant.
@@ -48,7 +49,6 @@ class TimeSeries:
48
49
  """Return the timeseries as a Pandas dataframe optionally with the time being the index
49
50
  and the value being the column.
50
51
  """
51
- import pandas as pd
52
52
 
53
53
  d = {
54
54
  "time": self.timeline,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes