roboquant 0.1.6__py3-none-any.whl

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 (48) hide show
  1. roboquant/__init__.py +33 -0
  2. roboquant/account.py +127 -0
  3. roboquant/brokers/__init__.py +2 -0
  4. roboquant/brokers/broker.py +18 -0
  5. roboquant/brokers/ibkrbroker.py +211 -0
  6. roboquant/brokers/simbroker.py +191 -0
  7. roboquant/config.py +20 -0
  8. roboquant/event.py +131 -0
  9. roboquant/feeds/__init__.py +13 -0
  10. roboquant/feeds/candlefeed.py +60 -0
  11. roboquant/feeds/csvfeed.py +95 -0
  12. roboquant/feeds/eventchannel.py +74 -0
  13. roboquant/feeds/feed.py +14 -0
  14. roboquant/feeds/feedutil.py +73 -0
  15. roboquant/feeds/historicfeed.py +58 -0
  16. roboquant/feeds/randomwalk.py +56 -0
  17. roboquant/feeds/sqllitefeed.py +103 -0
  18. roboquant/feeds/tiingohistoricfeed.py +125 -0
  19. roboquant/feeds/tiingolivefeed.py +126 -0
  20. roboquant/feeds/yahoofeed.py +51 -0
  21. roboquant/order.py +107 -0
  22. roboquant/roboquant.py +67 -0
  23. roboquant/strategies/__init__.py +7 -0
  24. roboquant/strategies/buffer.py +80 -0
  25. roboquant/strategies/candlestrategy.py +40 -0
  26. roboquant/strategies/emacrossover.py +57 -0
  27. roboquant/strategies/featureset.py +130 -0
  28. roboquant/strategies/multistrategy.py +45 -0
  29. roboquant/strategies/nopstrategy.py +13 -0
  30. roboquant/strategies/rnnstrategy.py +187 -0
  31. roboquant/strategies/smacrossover.py +45 -0
  32. roboquant/strategies/strategy.py +21 -0
  33. roboquant/timeframe.py +135 -0
  34. roboquant/trackers/__init__.py +6 -0
  35. roboquant/trackers/basictracker.py +64 -0
  36. roboquant/trackers/capmtracker.py +66 -0
  37. roboquant/trackers/equitytracker.py +18 -0
  38. roboquant/trackers/standardtracker.py +173 -0
  39. roboquant/trackers/tensorboardtracker.py +31 -0
  40. roboquant/trackers/tracker.py +18 -0
  41. roboquant/traders/__init__.py +2 -0
  42. roboquant/traders/flextrader.py +128 -0
  43. roboquant/traders/trader.py +17 -0
  44. roboquant-0.1.6.dist-info/LICENSE +201 -0
  45. roboquant-0.1.6.dist-info/METADATA +105 -0
  46. roboquant-0.1.6.dist-info/RECORD +48 -0
  47. roboquant-0.1.6.dist-info/WHEEL +5 -0
  48. roboquant-0.1.6.dist-info/top_level.txt +1 -0
roboquant/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ from .account import Account, OptionAccount, Position
2
+ from .roboquant import Roboquant
3
+
4
+ from .event import Event, PriceItem, Candle, Trade, Quote
5
+ from .order import Order, OrderStatus
6
+ from .timeframe import Timeframe
7
+ from .config import Config
8
+
9
+
10
+ from roboquant.brokers import Broker, SimBroker
11
+ from roboquant.traders import Trader, FlexTrader
12
+ from roboquant.trackers import Tracker, StandardTracker, BasicTracker, CAPMTracker, EquityTracker, TensorboardTracker
13
+ from roboquant.strategies import (
14
+ Strategy,
15
+ EMACrossover,
16
+ SMACrossover,
17
+ CandleStrategy,
18
+ NOPStrategy,
19
+ NumpyBuffer,
20
+ OHLCVBuffer,
21
+ )
22
+ from roboquant.feeds import (
23
+ Feed,
24
+ CSVFeed,
25
+ SQLFeed,
26
+ RandomWalk,
27
+ YahooFeed,
28
+ TiingoLiveFeed,
29
+ TiingoHistoricFeed,
30
+ CandleFeed,
31
+ EventChannel,
32
+ feedutil
33
+ )
roboquant/account.py ADDED
@@ -0,0 +1,127 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from roboquant.order import Order
5
+ from prettytable import PrettyTable
6
+
7
+
8
+ @dataclass(slots=True, frozen=True)
9
+ class Position:
10
+ """Position of a symbol"""
11
+
12
+ size: Decimal
13
+ """Position size"""
14
+
15
+ avg_price: float
16
+ """Average price paid denoted in the currency of the symbol"""
17
+
18
+
19
+ class Account:
20
+ """The account maintains the following state during a run:
21
+
22
+ - Available cash for trading (also sometimes referred to as buying power)
23
+ - Open positions
24
+ - Open orders
25
+ - Total equity value of the account
26
+ - Last time the account was updated
27
+
28
+ Only the broker updates the state of the account and does this only during its `sync` method.
29
+ """
30
+
31
+ def __init__(self):
32
+ self.buying_power: float = 0.0
33
+ self.positions: dict[str, Position] = {}
34
+ self.orders: list[Order] = []
35
+ self.last_update: datetime = datetime.fromisoformat("1900-01-01T00:00:00+00:00")
36
+ self.equity = 0.0
37
+
38
+ def get_value(self, symbol: str, size: Decimal, price: float) -> float:
39
+ """Return the total value of the provided contract size denoted in the base currency of the account.
40
+ The default implementation returns `size * price`.
41
+
42
+ The bahavior of this method can be changed for symbols denoted in a different currency and/or contract size by providing
43
+ a different value_calculator.
44
+ """
45
+ return float(size) * price
46
+
47
+ def mkt_value(self, prices: dict[str, float]) -> float:
48
+ """Return the the market value of all the open positions in the account using the provided prices."""
49
+ return sum([self.get_value(symbol, pos.size, prices[symbol]) for symbol, pos in self.positions.items()], 0.0)
50
+
51
+ def unrealized_pnl(self, prices: dict[str, float]) -> float:
52
+ return sum(
53
+ [
54
+ self.get_value(symbol, pos.size, prices[symbol] - pos.avg_price)
55
+ for symbol, pos in self.positions.items()
56
+ ],
57
+ 0.0,
58
+ )
59
+
60
+ def has_open_order(self, symbol: str) -> bool:
61
+ """Return True if there an open order for the symbol, False otherwise"""
62
+
63
+ for order in self.orders:
64
+ if order.symbol == symbol and not order.closed:
65
+ return True
66
+ return False
67
+
68
+ def get_position_size(self, symbol) -> Decimal:
69
+ pos = self.positions.get(symbol)
70
+ return pos.size if pos else Decimal(0)
71
+
72
+ def open_orders(self):
73
+ """Return a list with the open orders"""
74
+ return [order for order in self.orders if not order.closed]
75
+
76
+ def __repr__(self) -> str:
77
+ p = PrettyTable(["account", "value"], align="r", float_format="12.2")
78
+ p.add_row(["buying power", self.buying_power])
79
+ p.add_row(["equity", self.equity])
80
+ p.add_row(["positions", len(self.positions)])
81
+ p.add_row(["orders", len(self.orders)])
82
+ p.add_row(["last update", self.last_update.strftime("%Y-%m-%d %H:%M:%S")])
83
+ result = p.get_string() + "\n\n"
84
+
85
+ p = PrettyTable(["symbol", "position size", "avg price"], align="r", float_format="12.2")
86
+ for symbol, pos in self.positions.items():
87
+ p.add_row([symbol, pos.size, pos.avg_price])
88
+ result += p.get_string() + "\n\n"
89
+
90
+ p = PrettyTable(
91
+ ["symbol", "order size", "order id", "limit", "status", "closed"], align="r", float_format="12.2"
92
+ )
93
+ for order in self.orders:
94
+ p.add_row([order.symbol, order.size, order.id, order.limit, order.status.name, order.closed])
95
+ result += p.get_string() + "\n"
96
+
97
+ return result
98
+
99
+
100
+ class OptionAccount(Account):
101
+ """
102
+ This account handles common option contracts of size 100 and 10. Serves as an example.
103
+ If no contract size is registered for a symbol, it creates one based on the symbol name.
104
+ """
105
+
106
+ def __init__(self):
107
+ super().__init__()
108
+ self._contract_sizes: dict[str, float] = {}
109
+
110
+ def register(self, symbol: str, contract_size: float = 100.0):
111
+ """Register a certain contract-size for a symbol"""
112
+ self._contract_sizes[symbol] = contract_size
113
+
114
+ def get_value(self, symbol: str, size: Decimal, price: float) -> float:
115
+ contract_size = self._contract_sizes.get(symbol)
116
+
117
+ # If nithng registered we try to defer the contract size from the symbol
118
+ if contract_size is None:
119
+ if len(symbol) == 21:
120
+ # OCC compliant option symbol
121
+ symbol = symbol[0:6].rstrip()
122
+ contract_size = 10.0 if symbol[-1] == "7" else 100.0
123
+ else:
124
+ # not an option symbol
125
+ contract_size = 1.0
126
+
127
+ return contract_size * float(size) * price
@@ -0,0 +1,2 @@
1
+ from .broker import Broker
2
+ from .simbroker import SimBroker
@@ -0,0 +1,18 @@
1
+
2
+ from typing import Protocol
3
+ from roboquant.account import Account
4
+ from roboquant.event import Event
5
+ from roboquant.order import Order
6
+
7
+
8
+
9
+ class Broker(Protocol):
10
+ """A broker handles the placed orders and communicates its state through the account object"""
11
+
12
+ def place_orders(self, *orders: Order):
13
+ """Place zero or more orders at this broker."""
14
+ ...
15
+
16
+ def sync(self, event: Event | None = None) -> Account:
17
+ """Sync the state, and return an updated account to reflect the latest state."""
18
+ ...
@@ -0,0 +1,211 @@
1
+ from datetime import datetime, timezone, timedelta
2
+ from decimal import Decimal
3
+ import logging
4
+ import time
5
+ import threading
6
+
7
+ from roboquant.account import Account, Position
8
+ from roboquant.order import Order, OrderStatus
9
+ from roboquant.event import Event
10
+ from .broker import Broker
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ try:
15
+ from ibapi.client import EClient
16
+ from ibapi.wrapper import EWrapper
17
+ from ibapi.contract import Contract
18
+ from ibapi.order import Order as IBKROrder
19
+ from ibapi.account_summary_tags import AccountSummaryTags
20
+ from ibapi import VERSION
21
+ assert VERSION["major"] == 10 and VERSION["minor"] == 19, "Wrong version of the IBAPI found"
22
+ except:
23
+ logger.fatal("Couldn't import IBAPI package, you need to manually install this")
24
+ pass
25
+
26
+
27
+ class _IBApi(EWrapper, EClient):
28
+
29
+ def __init__(self):
30
+ EClient.__init__(self, self)
31
+ self.orders: dict[str, Order] = {}
32
+ self.positions: dict[str, Position] = {}
33
+ self.account = {"EquityWithLoanValue": 0.0, "AvailableFunds": 0.0}
34
+ self.__account_end = threading.Condition()
35
+ self.__order_id = 0
36
+
37
+ def nextValidId(self, orderId: int):
38
+ self.__order_id = orderId
39
+ logger.debug("The next valid order id is: %s", orderId)
40
+
41
+ def get_next_order_id(self):
42
+ result = str(self.__order_id)
43
+ self.__order_id += 1
44
+ return result
45
+
46
+ def position(self, account: str, contract: Contract, position: Decimal, avgCost: float):
47
+ logger.debug("position=%s symbol=%s avgCost=%s", position, contract.localSymbol, avgCost)
48
+ symbol = contract.localSymbol or contract.symbol
49
+ self.positions[symbol] = Position(position, avgCost)
50
+
51
+ def accountSummary(self, reqId: int, account: str, tag: str, value: str, currency: str):
52
+ logger.debug("account %s=%s", tag, value)
53
+ self.account[tag] = float(value)
54
+
55
+ def accountSummaryEnd(self, reqId: int):
56
+ with self.__account_end:
57
+ self.__account_end.notify_all()
58
+
59
+ def openOrder(self, orderId: int, contract, order: IBKROrder, orderState):
60
+ logger.debug(
61
+ "openOrder orderId=%s status=%s size=%s limit=%s",
62
+ orderId,
63
+ orderState.status,
64
+ order.totalQuantity,
65
+ order.lmtPrice,
66
+ )
67
+ size = order.totalQuantity if order.action == "BUY" else -order.totalQuantity
68
+ symbol = contract.localSymbol
69
+ rq_order = Order(symbol, size) if not order.lmtPrice else Order(symbol, size, order.lmtPrice)
70
+ rq_order.id = str(orderId)
71
+ self.orders[rq_order.id] = rq_order
72
+
73
+ def reqAccountSummary(self):
74
+ buyingpower_tag = AccountSummaryTags.BuyingPower
75
+ equity_tag = AccountSummaryTags.NetLiquidation
76
+ with self.__account_end:
77
+ super().reqAccountSummary(1, "All", f"{buyingpower_tag},{equity_tag}")
78
+ self.__account_end.wait()
79
+
80
+ def get_buying_power(self):
81
+ buyingpower_tag = AccountSummaryTags.BuyingPower
82
+ return self.account[buyingpower_tag] or 0.0
83
+
84
+ def get_equity(self):
85
+ equity_tag = AccountSummaryTags.NetLiquidation
86
+ return self.account[equity_tag] or 0.0
87
+
88
+ def orderStatus(
89
+ self,
90
+ orderId,
91
+ status,
92
+ filled,
93
+ remaining,
94
+ avgFillPrice,
95
+ permId,
96
+ parentId,
97
+ lastFillPrice,
98
+ clientId,
99
+ whyHeld,
100
+ mktCapPrice,
101
+ ):
102
+ logger.debug("order status orderId=%s status=%s", orderId, status)
103
+ id = str(orderId)
104
+ if id in self.orders:
105
+ order = self.orders[id]
106
+ match status:
107
+ case "Submitted":
108
+ order.status = OrderStatus.ACTIVE
109
+ case "Cancelled":
110
+ order.status = OrderStatus.CANCELLED
111
+ case "Filled":
112
+ order.status = OrderStatus.FILLED
113
+ else:
114
+ logger.warn(f"recieved status for unknown order id=%s status=%s", orderId, status)
115
+
116
+
117
+ class IBKRBroker(Broker):
118
+ """
119
+ Atttributes
120
+ ==========
121
+ contract_mapping
122
+ store how symbols map to IBKR contracts. If a symbol is not found, the symbol is assumed to represent a US stock
123
+
124
+ """
125
+
126
+ def __init__(self, host="127.0.0.1", port=4002, account=None, client_id=123) -> None:
127
+ self.__account = account or Account()
128
+ self.contract_mapping: dict[str, Contract] = {}
129
+ api = _IBApi()
130
+ api.connect(host, port, client_id)
131
+ self.__api = api
132
+ self._has_new_orders_since_sync = False
133
+
134
+ # Start the handling in a thread
135
+ self.__api_thread = threading.Thread(target=api.run, daemon=False)
136
+ self.__api_thread.start()
137
+ time.sleep(3.0)
138
+
139
+ def _should_sync(self, now):
140
+ return self._has_new_orders_since_sync or now - self.__account.last_update > timedelta(seconds=30)
141
+
142
+ def sync(self, event: Event | None = None) -> Account:
143
+ logger.debug("start sync")
144
+ now = datetime.now(timezone.utc)
145
+
146
+ if event:
147
+ # Lets make sure we don't use IBKRBroker by mistake during a back-test.
148
+ if now - event.time > timedelta(minutes=30):
149
+ logger.critical("received event from the past, now=%s event-time=%s", now, event.time)
150
+ raise ValueError(f"received event to far in the past now={now} event-time={event.time}")
151
+
152
+ api = self.__api
153
+ acc = self.__account
154
+ if self._should_sync(now):
155
+ acc.last_update = now
156
+ self._has_new_orders_since_sync = False
157
+
158
+ api.reqPositions()
159
+ api.reqOpenOrders()
160
+ api.reqAccountSummary()
161
+
162
+ acc.positions = {k: v for k, v in api.positions.items() if not v.size.is_zero()}
163
+ acc.orders = [order for order in api.orders.values()]
164
+ acc.buying_power = api.get_buying_power()
165
+ acc.equity = api.get_equity()
166
+
167
+ logger.debug("end sync")
168
+ return acc
169
+
170
+ def place_orders(self, *orders: Order):
171
+
172
+ self._has_new_orders_since_sync = len(orders) > 0
173
+
174
+ for order in orders:
175
+ assert not order.closed, "cannot place a closed order"
176
+ if order.size.is_zero():
177
+ assert order.id is not None
178
+ self.__api.cancelOrder(int(order.id), "")
179
+ else:
180
+ if order.id is None:
181
+ order.id = self.__api.get_next_order_id()
182
+ self.__api.orders[order.id] = order
183
+ ibkrorder = self._get_order(order)
184
+ contract = self.contract_mapping.get(order.symbol) or self._get_default_contract(order.symbol)
185
+ self.__api.placeOrder(int(order.id), contract, ibkrorder)
186
+
187
+ def _get_default_contract(self, symbol: str) -> Contract:
188
+ """If no contract can be found in the `contract_mapping` dict, this method is called to get a default IBKR contract
189
+ for the provided symbol.
190
+
191
+ The default implementation assumes it is a US stock symbol with SMART exchange routing.
192
+ """
193
+
194
+ c = Contract()
195
+ c.symbol = symbol
196
+ c.secType = "STK"
197
+ c.currency = "USD"
198
+ c.exchange = "SMART" # use smart routing by default
199
+ return c
200
+
201
+ def _get_order(self, order: Order):
202
+ o = IBKROrder()
203
+ o.action = "BUY" if order.is_buy else "SELL"
204
+ o.totalQuantity = abs(order.size)
205
+ o.tif = "GTC"
206
+ if order.limit:
207
+ o.orderType = "LMT"
208
+ o.lmtPrice = order.limit
209
+ else:
210
+ o.orderType = "MKT"
211
+ return o
@@ -0,0 +1,191 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime, timedelta
3
+ from decimal import Decimal
4
+
5
+ from roboquant.order import Order, OrderStatus
6
+ from ..event import Event
7
+ from ..account import Account, Position
8
+ from .broker import Broker
9
+
10
+
11
+ @dataclass(slots=True, frozen=True)
12
+ class _Trx:
13
+ """transaction for an executed trade"""
14
+
15
+ symbol: str
16
+ size: Decimal
17
+ price: float # is denoted in the currency of the symbol
18
+ fee: float = 0.0 # is denoted in base currency of the account
19
+
20
+
21
+ @dataclass
22
+ class _OrderState:
23
+ order: Order
24
+ accepted: datetime | None = None
25
+
26
+
27
+ class SimBroker(Broker):
28
+ """Implementation of a Broker that simulates order handling and trade execution.
29
+
30
+ This class can be extended to support different types of use-cases, like margin trading.
31
+ """
32
+
33
+ __order_id = 0
34
+
35
+ def __init__(
36
+ self, initial_deposit=1000000.0, account=None, price_type="DEFAULT", slippage=0.001, clean_up_orders=True
37
+ ):
38
+ super().__init__()
39
+ self.initial_deposit = initial_deposit
40
+ self._account = account or Account()
41
+ self._modify_orders: list[Order] = []
42
+ self._account.buying_power = initial_deposit
43
+ self.slippage = slippage
44
+ self.price_type = price_type
45
+ self._prices: dict[str, float] = {}
46
+ self._orders: dict[str, _OrderState] = {}
47
+ self.clean_up_orders = clean_up_orders
48
+
49
+ def _update_account(self, trx: _Trx):
50
+ """Update a position and cash based on a new transaction"""
51
+ acc = self._account
52
+ symbol = trx.symbol
53
+ acc.buying_power -= acc.get_value(symbol, trx.size, trx.price)
54
+
55
+ size = acc.get_position_size(symbol)
56
+
57
+ if size.is_zero():
58
+ # opening of position
59
+ acc.positions[symbol] = Position(trx.size, trx.price)
60
+ else:
61
+ new_size: Decimal = size + trx.size
62
+ if new_size.is_zero():
63
+ # closing of position
64
+ del acc.positions[symbol]
65
+ elif new_size.is_signed() != size.is_signed():
66
+ # reverse of position
67
+ acc.positions[symbol] = Position(new_size, trx.price)
68
+ else:
69
+ # increase of position size
70
+ old_price = acc.positions[symbol].avg_price
71
+ avg_price = (old_price * float(size) + trx.price * float(trx.size)) / (float(size + trx.size))
72
+ acc.positions[symbol] = Position(new_size, avg_price)
73
+
74
+ def get_execution_price(self, order, item) -> float:
75
+ """Return the execution price to use for an order based on the price item.
76
+
77
+ The default implementation is a fixed slippage percentage based on the configured price_type.
78
+ """
79
+
80
+ price = item.get_price(self.price_type)
81
+ correction = self.slippage if order.is_buy else -self.slippage
82
+ return price * (1.0 + correction)
83
+
84
+ def get_fee(self, order) -> float:
85
+ """Return the fee (or rebate) for the execution of an order. The fee is denoted in the base
86
+ currency of the account.
87
+
88
+ The default implementation returns 0.0, so no fee is used.
89
+ """
90
+ return 0.0
91
+
92
+ def _simulate_market(self, order: Order, item) -> _Trx | None:
93
+ """Simulate a market for the three order types"""
94
+
95
+ price = self.get_execution_price(order, item)
96
+ if self.is_executable(order, price):
97
+ fee = self.get_fee(order)
98
+ return _Trx(order.symbol, order.size, price, fee)
99
+
100
+ def next_order_id(self):
101
+ result = str(SimBroker.__order_id)
102
+ SimBroker.__order_id += 1
103
+ return result
104
+
105
+ def _has_expired(self, state: _OrderState) -> bool:
106
+ if state.accepted is None:
107
+ return False
108
+ else:
109
+ return self._account.last_update - state.accepted > timedelta(days=180)
110
+
111
+ def is_executable(self, order, price) -> bool:
112
+ """Is this order executable given the provided price.
113
+ A market order is always executable, a limit order only when the limit is below the BUY price or
114
+ above the SELL price"""
115
+ if order.limit is None:
116
+ return True
117
+ if order.is_buy and price <= order.limit:
118
+ return True
119
+ if order.is_sell and price >= order.limit:
120
+ return True
121
+
122
+ return False
123
+
124
+ def _update_mkt_prices(self, price_items):
125
+ """track the latest market prices for all open positions"""
126
+ for symbol in self._account.positions.keys():
127
+ if (item := price_items.get(symbol)) is not None:
128
+ self._prices[symbol] = item.get_price(self.price_type)
129
+
130
+ def place_orders(self, *orders: Order):
131
+ """Place new orders at this broker. The order gets assigned a unique id if it hasn't one already.
132
+
133
+ There is no trading simulation yet performed or account updated. Orders placed at time `t`, will be
134
+ processed during time `t+1`. This protects against future bias.
135
+ """
136
+ for order in orders:
137
+ assert not order.closed, "cannot place closed orders"
138
+ if order.id is None:
139
+ order.id = self.next_order_id()
140
+ assert order.id not in self._orders
141
+ self._orders[order.id] = _OrderState(order)
142
+ else:
143
+ assert order.id in self._orders, "existing order id not found"
144
+ self._modify_orders.append(order)
145
+
146
+ def _process_modify_order(self):
147
+ for order in self._modify_orders:
148
+ state = self._orders[order.id] # type: ignore
149
+ if state.order.closed:
150
+ continue
151
+ elif order.is_cancellation:
152
+ state.order.status = OrderStatus.CANCELLED
153
+ else:
154
+ state.order.size = order.size or state.order.size
155
+ state.order.limit = order.limit or state.order.limit
156
+ self._modify_orders = []
157
+
158
+ def _process_create_orders(self, prices):
159
+ for state in self._orders.values():
160
+ order = state.order
161
+ if order.closed:
162
+ continue
163
+ if self._has_expired(state):
164
+ order.status = OrderStatus.EXPIRED
165
+ else:
166
+ if (item := prices.get(order.symbol)) is not None:
167
+ state.accepted = state.accepted or self._account.last_update
168
+ trx = self._simulate_market(order, item)
169
+ if trx is not None:
170
+ self._update_account(trx)
171
+ order.status = OrderStatus.FILLED
172
+
173
+ def sync(self, event: Event | None = None) -> Account:
174
+ """This will perform the trading simulation for open orders and update the account accordingly."""
175
+
176
+ acc = self._account
177
+ if event:
178
+ acc.last_update = event.time
179
+
180
+ prices = event.price_items if event else {}
181
+
182
+ if self.clean_up_orders:
183
+ self._orders = {id: state for id, state in self._orders.items() if not state.order.closed}
184
+
185
+ self._process_modify_order()
186
+ self._process_create_orders(prices)
187
+ self._update_mkt_prices(prices)
188
+
189
+ acc.equity = acc.mkt_value(self._prices) + acc.buying_power
190
+ acc.orders = [state.order for state in self._orders.values()]
191
+ return acc
roboquant/config.py ADDED
@@ -0,0 +1,20 @@
1
+ from configparser import ConfigParser
2
+ import os.path
3
+ import os
4
+
5
+
6
+ class Config:
7
+ """Access to roboquant configuration file"""
8
+
9
+ def __init__(self, path=None):
10
+ path = path or os.path.expanduser("~/.roboquant/.env")
11
+ with open(path, "r") as f:
12
+ config_string = "[default]\n" + f.read()
13
+ self.config = ConfigParser()
14
+ self.config.read_string(config_string)
15
+
16
+ def get(self, key):
17
+ for key2, value in os.environ.items():
18
+ final_key = key2.lower().replace("_", ".")
19
+ if final_key == key: return value
20
+ return self.config.get("default", key)