Skip to content

Commit

Permalink
updated
Browse files Browse the repository at this point in the history
  • Loading branch information
abbass2 committed Oct 29, 2023
1 parent ba7f7b3 commit 6e3e3ae
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 2,172 deletions.
256 changes: 126 additions & 130 deletions pyqstrat/notebooks/getting_started.ipynb

Large diffs are not rendered by default.

1,994 changes: 25 additions & 1,969 deletions pyqstrat/notebooks/multiple_contracts.ipynb

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions pyqstrat/pq_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,12 @@ def cancel(self) -> None:
@dataclass(kw_only=True)
class MarketOrder(Order):
def __post_init__(self):
if not np.isfinite(self.qty) or math.isclose(self.qty, 0):
raise ValueError(f'order qty must be finite and nonzero: {self.qty}')
try:
if not np.isfinite(self.qty) or math.isclose(self.qty, 0):
raise ValueError(f'order qty must be finite and nonzero: {self.qty}')
except Exception as ex:
_logger.info(ex)
import pdb; pdb.set_trace()

def __repr__(self):
timestamp = pd.Timestamp(self.timestamp).to_pydatetime()
Expand Down
6 changes: 4 additions & 2 deletions pyqstrat/pq_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def infer_frequency(timestamps: np.ndarray) -> float:
for period in ['D', 'M', 'm', 's']:
ret = try_frequency(timestamps, period, threshold)
if math.isfinite(ret): return ret
assert_(False, f'could not infer frequency from timestamps: {timestamps}')
assert_(False, f'could not infer frequency from timestamps: {timestamps[:100]} ...')
return math.nan # will never execute but keeps mypy happy


Expand Down Expand Up @@ -676,7 +676,9 @@ def assert_(condition: bool, msg: str | None = None) -> None:
using the python optimization switch
'''
if msg is None: msg = ''
if not condition: raise PQException(msg)
if not condition:
import pdb; pdb.set_trace()
raise PQException(msg)


class Paths:
Expand Down
25 changes: 22 additions & 3 deletions pyqstrat/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def __init__(self,
pnl_calc_time: int = 16 * 60 + 1,
trade_lag: int = 0,
run_final_calc: bool = True,
log_trades: bool = True,
log_orders: bool = False,
strategy_context: StrategyContextType | None = None) -> None:
'''
Args:
Expand All @@ -85,6 +87,8 @@ def __init__(self,
strategy_context: A storage class where you can store key / value pairs relevant to this strategy.
For example, you may have a pre-computed table of correlations that you use in the indicator or trade rule functions.
If not set, the __init__ function will create an empty member strategy_context object that you can access.
log_trades: If set, we log orders as they are created
log_orders: If set, we log trades as they are created
'''
self.name = 'main' # Set by portfolio when running multiple strategies
increasing_ts: bool = bool(np.all(np.diff(timestamps.astype(int)) > 0))
Expand All @@ -99,6 +103,8 @@ def __init__(self,
assert_(trade_lag >= 0, f'trade_lag cannot be negative: {trade_lag}')
self.trade_lag = trade_lag
self.run_final_calc = run_final_calc
self.log_trades = log_trades
self.log_orders = log_orders
self.indicators: dict[str, IndicatorType] = {}
self.signals: dict[str, SignalType] = {}
self.signal_values: dict[str, SimpleNamespace] = defaultdict(types.SimpleNamespace)
Expand Down Expand Up @@ -413,6 +419,12 @@ def _run_iteration(self, i: int) -> None:

for j, (rule_function, contract_group, params) in enumerate(rules):
orders = self._get_orders(i, rule_function, contract_group, params)
if self.log_orders and len(orders) > 0:
if len(orders) > 1:
_logger.info('ORDERS:' + ''.join([f'\n {order}' for order in orders]))
else:
_logger.info(f'ORDER: {orders[0]}')

self._orders += orders
self._current_orders += orders
# _logger.info(f'current_orders: {self._current_orders}')
Expand Down Expand Up @@ -447,9 +459,9 @@ def _get_orders(self, idx: int, rule_function: RuleType, contract_group: Contrac
if position_filter is not None:
curr_pos = self.account.position(contract_group, self.timestamps[idx])
if position_filter == 'zero' and not math.isclose(curr_pos, 0): return []
if position_filter == 'nonzero' and math.isclose(curr_pos, 0): return []
if position_filter == 'positive' and (curr_pos < 0 or math.isclose(curr_pos, 0)): return []
if position_filter == 'negative' and (curr_pos > 0 or math.isclose(curr_pos, 0)): return []
elif position_filter == 'nonzero' and math.isclose(curr_pos, 0): return []
elif position_filter == 'positive' and (curr_pos < 0 or math.isclose(curr_pos, 0)): return []
elif position_filter == 'negative' and (curr_pos > 0 or math.isclose(curr_pos, 0)): return []

orders = rule_function(contract_group, idx, self.timestamps, indicator_values, signal_values, self.account,
self._current_orders, self.strategy_context)
Expand Down Expand Up @@ -491,6 +503,13 @@ def _sim_market(self, i: int) -> None:
self.indicator_values,
self.signal_values,
self.strategy_context)

if self.log_trades and len(trades) > 0:
if len(trades) > 1:
_logger.info('TRADES:' + ''.join([f'\n {trade}' for trade in trades]))
else:
_logger.info(f'TRADE: {trades[0]}')

if len(trades): self.account.add_trades(trades)
self._trades += trades
except Exception as e:
Expand Down
14 changes: 13 additions & 1 deletion pyqstrat/strategy_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class StrategyBuilder:
signals: list[tuple[str, SignalType, Sequence[ContractGroup] | None, Sequence[str] | None, Sequence[str] | None]]
rules: list[tuple[str, RuleType, str, Sequence[Any] | None, str | None]]
market_sims: list[MarketSimulatorType]
log_trades: bool
log_orders: bool

def __init__(self, data: pd.DataFrame | None = None) -> None:
if data is not None: assert_(len(data) > 0, 'data cannot be empty')
Expand All @@ -87,6 +89,8 @@ def __init__(self, data: pd.DataFrame | None = None) -> None:
self.signals = []
self.rules = []
self.market_sims = []
self.log_trades = True
self.log_orders = False

def set_timestamps(self, timestamps: np.ndarray) -> None:
assert_(np.issubdtype(timestamps.dtype, np.datetime64), f'timestamps must be np.datetime64: {timestamps}')
Expand All @@ -104,6 +108,12 @@ def set_trade_lag(self, trade_lag: int) -> None:
def set_strategy_context(self, context: StrategyContextType) -> None:
self.strategy_context = context

def set_log_trades(self, log_trades: bool) -> None:
self.log_trades = log_trades

def set_log_orders(self, log_orders: bool) -> None:
self.log_orders = log_orders

def add_contract(self, symbol: str) -> Contract:
if Contract.exists(symbol):
contract = Contract.get(symbol)
Expand Down Expand Up @@ -186,7 +196,9 @@ def __call__(self) -> Strategy:
self.starting_equity,
self.pnl_calc_time,
self.trade_lag,
True,
True,
self.log_trades,
self.log_orders,
self.strategy_context)

assert_(self.rules is not None and len(self.rules) > 0, 'rules cannot be empty or None')
Expand Down
Loading

0 comments on commit 6e3e3ae

Please sign in to comment.