trdr 0.0.2__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.
- trdr/__init__.py +0 -0
- trdr/conftest.py +148 -0
- trdr/core/__init__.py +0 -0
- trdr/core/bar_provider/__init__.py +0 -0
- trdr/core/bar_provider/base_bar_provider.py +115 -0
- trdr/core/bar_provider/exceptions.py +40 -0
- trdr/core/bar_provider/models.py +38 -0
- trdr/core/bar_provider/yf_bar_provider/__init__.py +0 -0
- trdr/core/bar_provider/yf_bar_provider/test_yf_bar_provider.py +73 -0
- trdr/core/bar_provider/yf_bar_provider/yf_bar_provider.py +301 -0
- trdr/core/broker/__init__.py +0 -0
- trdr/core/broker/alpaca_broker/README.md +68 -0
- trdr/core/broker/alpaca_broker/__init__.py +0 -0
- trdr/core/broker/alpaca_broker/alpaca_broker.py +404 -0
- trdr/core/broker/base_broker.py +391 -0
- trdr/core/broker/exceptions.py +6 -0
- trdr/core/broker/mock_broker/__init__.py +0 -0
- trdr/core/broker/mock_broker/mock_broker.py +130 -0
- trdr/core/broker/mock_broker/test_mock_broker.py +394 -0
- trdr/core/broker/mock_broker/test_mock_broker_pdt_validation.py +277 -0
- trdr/core/broker/models.py +145 -0
- trdr/core/broker/pdt/README.md +82 -0
- trdr/core/broker/pdt/__init__.py +0 -0
- trdr/core/broker/pdt/base_pdt_strategy.py +32 -0
- trdr/core/broker/pdt/exceptions.py +16 -0
- trdr/core/broker/pdt/models.py +40 -0
- trdr/core/broker/pdt/nun_strategy.py +86 -0
- trdr/core/broker/pdt/test_pdt_evaluation.py +247 -0
- trdr/core/broker/pdt/wiggle_strategy.py +65 -0
- trdr/core/broker/pdt/yolo_strategy.py +48 -0
- trdr/core/broker/test_order_model.py +407 -0
- trdr/core/broker/test_position_model.py +638 -0
- trdr/core/security_provider/__init__.py +0 -0
- trdr/core/security_provider/base_security_provider.py +108 -0
- trdr/core/security_provider/models.py +197 -0
- trdr/core/security_provider/security_provider.py +43 -0
- trdr/core/security_provider/test_security_model.py +74 -0
- trdr/core/security_provider/test_security_provider.py +30 -0
- trdr/core/shared/__init__.py +0 -0
- trdr/core/shared/exceptions.py +2 -0
- trdr/core/shared/models.py +299 -0
- trdr/core/trading_context/__init__.py +0 -0
- trdr/core/trading_context/exceptions.py +2 -0
- trdr/core/trading_context/test_trading_context.py +481 -0
- trdr/core/trading_context/trading_context.py +318 -0
- trdr/core/trading_engine/__init__.py +0 -0
- trdr/core/trading_engine/trading_engine.py +222 -0
- trdr/dsl/__init__.py +0 -0
- trdr/dsl/dsl_ast.py +375 -0
- trdr/dsl/dsl_loader.py +99 -0
- trdr/dsl/lexer.py +202 -0
- trdr/dsl/parser.py +288 -0
- trdr/test_utils/__init__.py +0 -0
- trdr/test_utils/fake_yf_download.py +38 -0
- trdr/test_utils/order_generator.py +149 -0
- trdr/test_utils/position_generator.py +152 -0
- trdr/test_utils/security_generator.py +162 -0
- trdr/test_utils/strategies/always_buy.trdr +13 -0
- trdr/test_utils/strategies/always_sell.trdr +13 -0
- trdr/test_utils/strategies/test_strat.trdr +28 -0
- trdr/test_utils/test_symbols.py +505 -0
- trdr-0.0.2.dist-info/METADATA +14 -0
- trdr-0.0.2.dist-info/RECORD +64 -0
- trdr-0.0.2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from typing import List, Tuple, Optional
|
|
2
|
+
from datetime import timedelta, timezone
|
|
3
|
+
import yfinance as yf
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from opentelemetry import trace
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from ..exceptions import (
|
|
10
|
+
BarProviderException,
|
|
11
|
+
InsufficientBarsException,
|
|
12
|
+
NoBarsForSymbolException,
|
|
13
|
+
BarConversionException,
|
|
14
|
+
)
|
|
15
|
+
from ..base_bar_provider import BaseBarProvider
|
|
16
|
+
from ..models import Bar, TradingDateTime, Money
|
|
17
|
+
from ...shared.models import Timeframe
|
|
18
|
+
|
|
19
|
+
# Disable yfinance logging
|
|
20
|
+
logger = logging.getLogger("yfinance")
|
|
21
|
+
logger.disabled = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class YFBarProvider(BaseBarProvider):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
*args,
|
|
28
|
+
**kwargs,
|
|
29
|
+
):
|
|
30
|
+
"""Disabled constructor - use YFBarProvider.create() instead."""
|
|
31
|
+
raise TypeError("Use YFBarProvider.create() instead to create a new bar provider")
|
|
32
|
+
|
|
33
|
+
async def _initialize(self, symbols: List[str]) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Initialize the YFBarProvider instance.
|
|
36
|
+
|
|
37
|
+
This function is called by the T.create() method implemented in the base class.
|
|
38
|
+
Use that method to create a new bar provider.
|
|
39
|
+
"""
|
|
40
|
+
with self._tracer.start_as_current_span("YFBarProvider._initialize") as span:
|
|
41
|
+
self._no_data_errors = ["YFTzMissingError", "YFPricesMissingError", "JSONDecodeError", "YFRateLimitError"]
|
|
42
|
+
if not symbols:
|
|
43
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
44
|
+
e = BarProviderException("Symbols must contain at least one symbol")
|
|
45
|
+
span.record_exception(e)
|
|
46
|
+
raise e
|
|
47
|
+
try:
|
|
48
|
+
await self._refresh_data(symbols)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
span.add_event("refresh_data_error")
|
|
51
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
52
|
+
raise
|
|
53
|
+
else:
|
|
54
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
55
|
+
|
|
56
|
+
async def _refresh_data(self, symbols: List[str]) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Refresh the stock data for the symbols.
|
|
59
|
+
|
|
60
|
+
This function is called by the factory method in the base class upon instantiation
|
|
61
|
+
of a new provider.
|
|
62
|
+
"""
|
|
63
|
+
with self._tracer.start_as_current_span("YFBarProvider._refresh_data") as span:
|
|
64
|
+
span.set_attribute("number_of_symbols_requested", len(symbols))
|
|
65
|
+
try:
|
|
66
|
+
symbols_with_data, data = await self._fetch_batch_stock_data(symbols.copy())
|
|
67
|
+
span.set_attribute("number_of_symbols_with_no_data", len(symbols) - len(symbols_with_data))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
span.add_event("data_fetch_error")
|
|
70
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
71
|
+
raise e
|
|
72
|
+
else:
|
|
73
|
+
for symbol in symbols_with_data:
|
|
74
|
+
try:
|
|
75
|
+
symbol_data = data.xs(symbol, level=0, axis=1)
|
|
76
|
+
bars = self._convert_df_to_bars(symbol, symbol_data)
|
|
77
|
+
self._data_cache[symbol] = bars
|
|
78
|
+
except BarConversionException as e:
|
|
79
|
+
continue
|
|
80
|
+
except Exception as e:
|
|
81
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
82
|
+
raise e
|
|
83
|
+
span.set_attribute(
|
|
84
|
+
"number_of_symbols_with_data_that_failed_to_convert_to_bars",
|
|
85
|
+
len(symbols_with_data) - len(self._data_cache.keys()),
|
|
86
|
+
)
|
|
87
|
+
span.set_attribute("number_of_symbols_with_data", len(self._data_cache.keys()))
|
|
88
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
89
|
+
span.add_event("refresh_complete")
|
|
90
|
+
|
|
91
|
+
async def _fetch_batch_stock_data(
|
|
92
|
+
self,
|
|
93
|
+
symbols: List[str],
|
|
94
|
+
) -> Tuple[List[str], pd.DataFrame]:
|
|
95
|
+
"""
|
|
96
|
+
Fetch batch stock data from Yahoo Finance.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
pd.DataFrame: DataFrame containing the stock data.
|
|
100
|
+
"""
|
|
101
|
+
with self._tracer.start_as_current_span("YFBarProvider._fetch_batch_stock_data") as span:
|
|
102
|
+
end_datetime = TradingDateTime.now().timestamp
|
|
103
|
+
start_datetime = end_datetime - timedelta(days=300)
|
|
104
|
+
span.set_attribute("total_symbols_to_fetch_data_for", len(symbols))
|
|
105
|
+
span.set_attribute("start_datetime", str(start_datetime))
|
|
106
|
+
span.set_attribute("end_datetime", str(end_datetime))
|
|
107
|
+
span.set_attribute("date_range_days", (end_datetime - start_datetime).days)
|
|
108
|
+
span.add_event("begin_data_fetch")
|
|
109
|
+
data = yf.download(
|
|
110
|
+
symbols,
|
|
111
|
+
start=start_datetime,
|
|
112
|
+
end=end_datetime,
|
|
113
|
+
group_by="ticker",
|
|
114
|
+
interval=Timeframe.d1.to_yf_interval(),
|
|
115
|
+
)
|
|
116
|
+
span.add_event("data_fetch_complete")
|
|
117
|
+
if yf.shared._ERRORS:
|
|
118
|
+
"""
|
|
119
|
+
If a symbol has one of the following errors associated with it in the yf.shared._ERRORS dictionary, it means that we received no data for that symbol. We should therefore remove it from self._symbols.
|
|
120
|
+
The JSONDecodeError is the manifestation of a rate limit.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
symbols_with_no_data = [
|
|
124
|
+
symbol
|
|
125
|
+
for symbol, error in yf.shared._ERRORS.items()
|
|
126
|
+
if any(no_data_error in error for no_data_error in self._no_data_errors)
|
|
127
|
+
]
|
|
128
|
+
for symbol in symbols_with_no_data:
|
|
129
|
+
if symbol in symbols:
|
|
130
|
+
symbols.remove(symbol)
|
|
131
|
+
|
|
132
|
+
# Gather any other errors.
|
|
133
|
+
other_errors = [
|
|
134
|
+
error
|
|
135
|
+
for error in yf.shared._ERRORS.values()
|
|
136
|
+
if all(no_data_error not in error for no_data_error in self._no_data_errors)
|
|
137
|
+
]
|
|
138
|
+
if other_errors:
|
|
139
|
+
error_msg = "; ".join(other_errors)
|
|
140
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
141
|
+
e = BarProviderException(f"YFinance errors: {error_msg}")
|
|
142
|
+
span.record_exception(e)
|
|
143
|
+
raise e
|
|
144
|
+
else:
|
|
145
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
146
|
+
if symbols_with_no_data:
|
|
147
|
+
span.set_attribute("symbols_with_no_data", symbols_with_no_data)
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
Our yf object is long lived across the lifetime of the bar provider. Therefore, we should reset the errors dictionary. We need it clean as we will need to inspect errors produced by calls to yf.download()
|
|
151
|
+
"""
|
|
152
|
+
yf.shared._ERRORS = {}
|
|
153
|
+
|
|
154
|
+
return symbols, data
|
|
155
|
+
|
|
156
|
+
def _convert_df_to_bars(self, symbol: str, df: pd.DataFrame) -> List[Bar]:
|
|
157
|
+
"""
|
|
158
|
+
Convert a DataFrame to a list of Bar objects.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
df (pd.DataFrame): DataFrame containing the stock data.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List[Bar]: List of Bar objects.
|
|
165
|
+
"""
|
|
166
|
+
with self._tracer.start_as_current_span("YFBarProvider._convert_df_to_bars") as span:
|
|
167
|
+
span.set_attribute("symbol", symbol)
|
|
168
|
+
|
|
169
|
+
bars = []
|
|
170
|
+
total_rows = len(df)
|
|
171
|
+
bar_creation_errors = 0
|
|
172
|
+
|
|
173
|
+
for date, row in df.iterrows():
|
|
174
|
+
try:
|
|
175
|
+
utc_timestamp = pd.Timestamp(date).to_pydatetime().replace(tzinfo=timezone.utc)
|
|
176
|
+
bar = Bar(
|
|
177
|
+
trading_datetime=TradingDateTime.from_utc(utc_timestamp),
|
|
178
|
+
open=Money(amount=Decimal(float(row["Open"]))),
|
|
179
|
+
high=Money(amount=Decimal(float(row["High"]))),
|
|
180
|
+
low=Money(amount=Decimal(float(row["Low"]))),
|
|
181
|
+
close=Money(amount=Decimal(float(row["Close"]))),
|
|
182
|
+
volume=int(row["Volume"]),
|
|
183
|
+
)
|
|
184
|
+
bars.append(bar)
|
|
185
|
+
except Exception as lower_e:
|
|
186
|
+
bar_creation_errors += 1
|
|
187
|
+
if bar_creation_errors / total_rows > 0.05:
|
|
188
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
189
|
+
e = BarConversionException(
|
|
190
|
+
f"failed to convert {bar_creation_errors} out of {total_rows} rows to Bars"
|
|
191
|
+
)
|
|
192
|
+
span.set_attribute("exception.cause", lower_e)
|
|
193
|
+
span.record_exception(e)
|
|
194
|
+
raise e from lower_e
|
|
195
|
+
span.set_attribute("bars_created", len(bars))
|
|
196
|
+
span.set_attribute("bar_creation_errors", bar_creation_errors)
|
|
197
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
198
|
+
|
|
199
|
+
return bars
|
|
200
|
+
|
|
201
|
+
def get_symbols(self) -> List[str]:
|
|
202
|
+
return list(self._data_cache.keys())
|
|
203
|
+
|
|
204
|
+
async def get_current_bar(self, symbol: str) -> Bar:
|
|
205
|
+
with self._tracer.start_as_current_span("YFBarProvider.get_current_bar") as span:
|
|
206
|
+
span.set_attribute("symbol", symbol)
|
|
207
|
+
span.add_event("begin_current_bar_data_fetch")
|
|
208
|
+
data = yf.download(
|
|
209
|
+
symbol,
|
|
210
|
+
period=Timeframe.d1.to_yf_interval(),
|
|
211
|
+
interval=Timeframe.m15.to_yf_interval(),
|
|
212
|
+
group_by="ticker",
|
|
213
|
+
)
|
|
214
|
+
span.add_event("current_bar_data_fetch_complete")
|
|
215
|
+
if yf.shared._ERRORS:
|
|
216
|
+
error = yf.shared._ERRORS.get(symbol, None)
|
|
217
|
+
if not error or not any(no_data_error in error for no_data_error in self._no_data_errors):
|
|
218
|
+
"""
|
|
219
|
+
If we receive an error not associated with the symbol of interest, we should raise an exception.
|
|
220
|
+
"""
|
|
221
|
+
# Gather any other errors.
|
|
222
|
+
errors = [error for error in yf.shared._ERRORS.values()]
|
|
223
|
+
error_msg = "; ".join(errors)
|
|
224
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
225
|
+
e = BarProviderException(f"Received an error not related to no data errors: {error_msg}")
|
|
226
|
+
span.record_exception(e)
|
|
227
|
+
raise e
|
|
228
|
+
if any(no_data_error in error for no_data_error in self._no_data_errors):
|
|
229
|
+
"""
|
|
230
|
+
If we didn't receive any data for the symbol of interest we can't construct the current bar.
|
|
231
|
+
"""
|
|
232
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
233
|
+
e = NoBarsForSymbolException(f"{symbol}")
|
|
234
|
+
span.record_exception(e)
|
|
235
|
+
raise e
|
|
236
|
+
"""
|
|
237
|
+
Our yf object is long lived across the lifetime of the bar provider. Therefore, we should reset the errors dictionary. We need it clean as we will need to inspect errors produced by calls to yf.download()
|
|
238
|
+
"""
|
|
239
|
+
yf.shared._ERRORS = {}
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
symbol_data = data.xs(symbol, level=0, axis=1)
|
|
243
|
+
bars = self._convert_df_to_bars(symbol, symbol_data)
|
|
244
|
+
most_recent_bar = bars[-1]
|
|
245
|
+
most_recent_bar.trading_datetime = TradingDateTime.now()
|
|
246
|
+
except Exception as e:
|
|
247
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
248
|
+
raise e
|
|
249
|
+
else:
|
|
250
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
251
|
+
return most_recent_bar
|
|
252
|
+
|
|
253
|
+
async def get_bars(
|
|
254
|
+
self,
|
|
255
|
+
symbol: str,
|
|
256
|
+
lookback: Optional[int] = None,
|
|
257
|
+
) -> List[Bar]:
|
|
258
|
+
"""
|
|
259
|
+
Get the bars for a symbol with a specified lookback period.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
symbol (str): The symbol to get the bars for.
|
|
263
|
+
lookback (int): The number of bars to look back.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
List[Bar]: List of bars for the symbol.
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
NoBarsForSymbolException: If we didn't receive any data for the symbol of interest.
|
|
270
|
+
InsufficientBarsException: If the number of bars requested is greater than the number of bars available.
|
|
271
|
+
"""
|
|
272
|
+
with self._tracer.start_as_current_span("YFBarProvider.get_bars") as span:
|
|
273
|
+
span.set_attribute("symbol", symbol)
|
|
274
|
+
if lookback is not None:
|
|
275
|
+
span.set_attribute("requested_lookback", lookback)
|
|
276
|
+
if not self._data_cache.get(symbol, None):
|
|
277
|
+
span.add_event("no_data_found_for_symbol")
|
|
278
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
279
|
+
e = NoBarsForSymbolException(symbol)
|
|
280
|
+
span.record_exception(e)
|
|
281
|
+
raise e
|
|
282
|
+
if lookback is None:
|
|
283
|
+
lookback = len(self._data_cache[symbol])
|
|
284
|
+
if len(self._data_cache[symbol]) < lookback:
|
|
285
|
+
span.set_attribute("lookback_available_for_symbol", len(self._data_cache[symbol]))
|
|
286
|
+
span.add_event("lookback_too_large_for_symbol")
|
|
287
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
288
|
+
e = InsufficientBarsException(
|
|
289
|
+
f"Only {len(self._data_cache[symbol])} bars available for symbol: {symbol}"
|
|
290
|
+
)
|
|
291
|
+
span.record_exception(e)
|
|
292
|
+
raise e
|
|
293
|
+
try:
|
|
294
|
+
bars = self._data_cache[symbol][-lookback:]
|
|
295
|
+
except Exception as e:
|
|
296
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
297
|
+
span.record_exception(e)
|
|
298
|
+
raise
|
|
299
|
+
else:
|
|
300
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
301
|
+
return bars
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ALPACA Broker Implementation
|
|
2
|
+
|
|
3
|
+
The ALPACA broker implementation allows TRDR to connect with the Alpaca trading API for real and paper trading.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
To use the ALPACA broker, you must set the following environment variables:
|
|
8
|
+
|
|
9
|
+
- `ALPACA_API_KEY`: Your Alpaca API key
|
|
10
|
+
- `ALPACA_SECRET_KEY`: Your Alpaca secret key
|
|
11
|
+
- `ALPACA_BASE_URL`: The Alpaca API base URL
|
|
12
|
+
- For paper trading: `https://paper-api.alpaca.markets`
|
|
13
|
+
- For live trading: `https://api.alpaca.markets`
|
|
14
|
+
|
|
15
|
+
## Important Notes
|
|
16
|
+
|
|
17
|
+
- The broker will automatically check if you're using a live trading URL and will display a warning if detected.
|
|
18
|
+
- Ensure your API keys have the appropriate permissions for the operations you intend to perform.
|
|
19
|
+
- The broker handles:
|
|
20
|
+
- Account information retrieval
|
|
21
|
+
- Position management
|
|
22
|
+
- Order placement and cancellation
|
|
23
|
+
- Pattern Day Trading (PDT) rule compliance through the PDT strategies
|
|
24
|
+
|
|
25
|
+
## Example Usage
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
import os
|
|
30
|
+
from trdr.core.bar_provider.yf_bar_provider.yf_bar_provider import YFBarProvider
|
|
31
|
+
from trdr.core.security_provider.security_provider import SecurityProvider
|
|
32
|
+
from trdr.core.broker.alpaca_broker.alpaca_broker import AlpacaBroker
|
|
33
|
+
from trdr.core.trading_engine.trading_engine import TradingEngine
|
|
34
|
+
from trdr.core.trading_context.trading_context import TradingContext
|
|
35
|
+
from trdr.core.broker.pdt.nun_strategy import NunStrategy
|
|
36
|
+
|
|
37
|
+
# Set environment variables
|
|
38
|
+
os.environ["ALPACA_API_KEY"] = "your_api_key"
|
|
39
|
+
os.environ["ALPACA_SECRET_KEY"] = "your_secret_key"
|
|
40
|
+
os.environ["ALPACA_BASE_URL"] = "https://paper-api.alpaca.markets" # For paper trading
|
|
41
|
+
|
|
42
|
+
async def main():
|
|
43
|
+
try:
|
|
44
|
+
pdt_strategy = NunStrategy.create()
|
|
45
|
+
async with await AlpacaBroker.create(pdt_strategy=pdt_strategy) as broker:
|
|
46
|
+
bar_provider = await YFBarProvider.create(["TSLA"])
|
|
47
|
+
security_provider = await SecurityProvider.create(bar_provider)
|
|
48
|
+
context = await TradingContext.create(security_provider, broker)
|
|
49
|
+
engine = await TradingEngine.create("my-strategy", context)
|
|
50
|
+
await engine.execute()
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(e)
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
asyncio.run(main())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## PDT Rule Compliance
|
|
59
|
+
|
|
60
|
+
The ALPACA broker works with TRDR's Pattern Day Trading rule compliance strategies. You can specify which PDT strategy to use when creating the broker:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Example with Nun Strategy
|
|
64
|
+
broker = await AlpacaBroker.create(pdt_strategy=NunStrategy.create())
|
|
65
|
+
|
|
66
|
+
# Example with YOLO Strategy
|
|
67
|
+
broker = await AlpacaBroker.create(pdt_strategy=YoloStrategy.create())
|
|
68
|
+
```
|
|
File without changes
|