def pair_entry_rule(contract_group: ContractGroup, i: int, timestamps: np.ndarray, indicators: SimpleNamespace, signal: np.ndarray, account: Account, strategy_context: StrategyContextType) -> Sequence[Order]: timestamp = timestamps[i] assert(math.isclose(account.position(contract_group, timestamp), 0)) signal_value = signal[i] risk_percent = 0.1 orders = [] symbol = contract_group.name contract = contract_group.get_contract(symbol) if contract is None: contract = Contract.create(symbol, contract_group=contract_group) curr_equity = account.equity(timestamp) order_qty = np.round(curr_equity * risk_percent / indicators.c[i] * np.sign(signal_value)) print(f'order_qty: {order_qty} curr_equity: {curr_equity} timestamp: {timestamp}' f' risk_percent: {risk_percent} indicator: {indicators.c[i]} signal_value: {signal_value}') reason_code = ReasonCode.ENTER_LONG if order_qty > 0 else ReasonCode.ENTER_SHORT orders.append(MarketOrder(contract, timestamp, order_qty, reason_code=reason_code)) return orders
def test_account(): #if __name__ == "__main__": from pyqstrat.pq_types import Contract, ContractGroup, Trade from pyqstrat.orders import MarketOrder import math def get_close_price(contract, timestamps, idx, strategy_context): if contract.symbol == "IBM": price = idx + 10.1 elif contract.symbol == "MSFT": price = idx + 15.3 else: raise Exception(f'unknown contract: {contract}') return price ContractGroup.clear() Contract.clear() ibm_cg = ContractGroup.create('IBM') msft_cg = ContractGroup.create('MSFT') ibm_contract = Contract.create('IBM', contract_group = ibm_cg) msft_contract = Contract.create('MSFT', contract_group = msft_cg) timestamps = np.array(['2018-01-01 09:00', '2018-01-02 08:00', '2018-01-02 09:00', '2018-01-05 13:35'], dtype = 'M8[m]') account = Account([ibm_cg, msft_cg], timestamps, get_close_price, None) #account = Account([Contract(symbol)], timestamps, get_close_price) trade_1 = Trade(ibm_contract, np.datetime64('2018-01-02 08:00'), 10, 10.1, commission = 0.01, order = MarketOrder(ibm_contract, np.datetime64('2018-01-01 09:00'), 10)) trade_2 = Trade(ibm_contract, np.datetime64('2018-01-02 09:00'), -20, 15.1, commission = 0.02, order = MarketOrder(ibm_contract, np.datetime64('2018-01-01 09:00'), -20)) trade_3 = Trade(msft_contract, timestamps[1], 20, 13.2, commission = 0.04, order = MarketOrder(msft_contract, timestamps[1], 15)) trade_4 = Trade(msft_contract, timestamps[2], 20, 16.2, commission = 0.05, order = MarketOrder(msft_contract, timestamps[2], 20)) account.add_trades([trade_1, trade_2, trade_3, trade_4]) account.calc(np.datetime64('2018-01-05 13:35')) assert(len(account.df_trades()) == 4) assert(len(account.df_pnl()) == 6) assert(np.allclose(np.array([9.99, 61.96, 79.97, 103.91, 69.97, 143.91]), account.df_pnl().net_pnl.values, rtol = 0)) assert(np.allclose(np.array([10, 20, -10, 40, -10, 40]), account.df_pnl().position.values, rtol = 0)) assert(np.allclose(np.array([1000000. , 1000183.88, 1000213.88]), account.df_account_pnl().equity.values, rtol = 0))
def pair_exit_rule(contract_group: ContractGroup, i: int, timestamps: np.ndarray, indicators: SimpleNamespace, signal: np.ndarray, account: Account, strategy_context: StrategyContextType) -> Sequence[Order]: timestamp = timestamps[i] curr_pos = account.position(contract_group, timestamp) assert(not math.isclose(curr_pos, 0)) signal_value = signal[i] orders = [] symbol = contract_group.name contract = contract_group.get_contract(symbol) if contract is None: contract = Contract.create(symbol, contract_group=contract_group) if (curr_pos > 0 and signal_value == -1) or (curr_pos < 0 and signal_value == 1): order_qty = -curr_pos reason_code = ReasonCode.EXIT_LONG if order_qty < 0 else ReasonCode.EXIT_SHORT orders.append(MarketOrder(contract, timestamp, order_qty, reason_code=reason_code)) return orders
def test_strategy() -> Strategy: import math import numpy as np import pandas as pd import os from types import SimpleNamespace from pyqstrat.pq_types import Contract, ContractGroup, Trade try: # If we are running from unit tests ko_file_path = os.path.dirname(os.path.realpath(__file__)) + '/notebooks/support/coke_15_min_prices.csv.gz' pep_file_path = os.path.dirname(os.path.realpath(__file__)) + '/notebooks/support/pepsi_15_min_prices.csv.gz' except NameError: ko_file_path = '../notebooks/support/coke_15_min_prices.csv.gz' pep_file_path = '../notebooks/support/pepsi_15_min_prices.csv.gz' ko_prices = pd.read_csv(ko_file_path) pep_prices = pd.read_csv(pep_file_path) ko_prices['timestamp'] = pd.to_datetime(ko_prices.date) pep_prices['timestamp'] = pd.to_datetime(pep_prices.date) end_time = '2019-01-30 12:00' ko_prices = ko_prices.query(f'timestamp <= "{end_time}"') pep_prices = pep_prices.query(f'timestamp <= "{end_time}"') timestamps = ko_prices.timestamp.values ratio = ko_prices.c / pep_prices.c def zscore_indicator(contract_group: ContractGroup, timestamps: np.ndarray, indicators: SimpleNamespace, strategy_context: StrategyContextType) -> np.ndarray: # simple moving average ratio = indicators.ratio r = pd.Series(ratio).rolling(window=130) mean = r.mean() std = r.std(ddof=0) zscore = (ratio - mean) / std zscore = np.nan_to_num(zscore) return zscore def pair_strategy_signal(contract_group: ContractGroup, timestamps: np.ndarray, indicators: SimpleNamespace, parent_signals: SimpleNamespace, strategy_context: StrategyContextType) -> np.ndarray: # We don't need any indicators since the zscore is already part of the market data zscore = indicators.zscore signal = np.where(zscore > 1, 2, 0) signal = np.where(zscore < -1, -2, signal) signal = np.where((zscore > 0.5) & (zscore < 1), 1, signal) signal = np.where((zscore < -0.5) & (zscore > -1), -1, signal) if contract_group.name == 'PEP': signal = -1. * signal return signal def pair_entry_rule(contract_group: ContractGroup, i: int, timestamps: np.ndarray, indicators: SimpleNamespace, signal: np.ndarray, account: Account, strategy_context: StrategyContextType) -> Sequence[Order]: timestamp = timestamps[i] assert(math.isclose(account.position(contract_group, timestamp), 0)) signal_value = signal[i] risk_percent = 0.1 orders = [] symbol = contract_group.name contract = contract_group.get_contract(symbol) if contract is None: contract = Contract.create(symbol, contract_group=contract_group) curr_equity = account.equity(timestamp) order_qty = np.round(curr_equity * risk_percent / indicators.c[i] * np.sign(signal_value)) print(f'order_qty: {order_qty} curr_equity: {curr_equity} timestamp: {timestamp}' f' risk_percent: {risk_percent} indicator: {indicators.c[i]} signal_value: {signal_value}') reason_code = ReasonCode.ENTER_LONG if order_qty > 0 else ReasonCode.ENTER_SHORT orders.append(MarketOrder(contract, timestamp, order_qty, reason_code=reason_code)) return orders def pair_exit_rule(contract_group: ContractGroup, i: int, timestamps: np.ndarray, indicators: SimpleNamespace, signal: np.ndarray, account: Account, strategy_context: StrategyContextType) -> Sequence[Order]: timestamp = timestamps[i] curr_pos = account.position(contract_group, timestamp) assert(not math.isclose(curr_pos, 0)) signal_value = signal[i] orders = [] symbol = contract_group.name contract = contract_group.get_contract(symbol) if contract is None: contract = Contract.create(symbol, contract_group=contract_group) if (curr_pos > 0 and signal_value == -1) or (curr_pos < 0 and signal_value == 1): order_qty = -curr_pos reason_code = ReasonCode.EXIT_LONG if order_qty < 0 else ReasonCode.EXIT_SHORT orders.append(MarketOrder(contract, timestamp, order_qty, reason_code=reason_code)) return orders def market_simulator(orders: Sequence[Order], i: int, timestamps: np.ndarray, indicators: Dict[ContractGroup, SimpleNamespace], signals: Dict[ContractGroup, SimpleNamespace], strategy_context: StrategyContextType) -> Sequence[Trade]: trades = [] timestamp = timestamps[i] for order in orders: trade_price = np.nan cgroup = order.contract.contract_group ind = indicators[cgroup] o, h, l = ind.o[i], ind.h[i], ind.l[i] # noqa: E741 # l is ambiguous assert isinstance(order, MarketOrder), f'Unexpected order type: {order}' trade_price = 0.5 * (o + h) if order.qty > 0 else 0.5 * (o + l) if np.isnan(trade_price): continue trade = Trade(order.contract, order, timestamp, order.qty, trade_price, commission=0, fee=0) order.status = 'filled' print(f'trade: {trade}') trades.append(trade) return trades def get_price(contract: Contract, timestamps: np.ndarray, i: int, strategy_context: StrategyContextType) -> float: if contract.symbol == 'KO': return strategy_context.ko_price[i] elif contract.symbol == 'PEP': return strategy_context.pep_price[i] raise Exception(f'Unknown contract: {contract}') Contract.clear() ContractGroup.clear() ko_contract_group = ContractGroup.create('KO') pep_contract_group = ContractGroup.create('PEP') strategy_context = SimpleNamespace(ko_price=ko_prices.c.values, pep_price=pep_prices.c.values) strategy = Strategy(timestamps, [ko_contract_group, pep_contract_group], get_price, trade_lag=1, strategy_context=strategy_context) for tup in [(ko_contract_group, ko_prices), (pep_contract_group, pep_prices)]: for column in ['o', 'h', 'l', 'c']: strategy.add_indicator(column, tup[1][column], contract_groups=[tup[0]]) strategy.add_indicator('ratio', ratio) strategy.add_indicator('zscore', zscore_indicator, depends_on=['ratio']) strategy.add_signal('pair_strategy_signal', pair_strategy_signal, depends_on_indicators=['zscore']) # ask pqstrat to call our trading rule when the signal has one of the values [-2, -1, 1, 2] strategy.add_rule('pair_entry_rule', pair_entry_rule, signal_name='pair_strategy_signal', sig_true_values=[-2, 2], position_filter='zero') strategy.add_rule('pair_exit_rule', pair_exit_rule, signal_name='pair_strategy_signal', sig_true_values=[-1, 1], position_filter='nonzero') strategy.add_market_sim(market_simulator) strategy.run_indicators() strategy.run_signals() strategy.run_rules() metrics = strategy.evaluate_returns(plot=False, display_summary=False, return_metrics=True) assert metrics is not None assert(round(metrics['gmean'], 6) == -0.062878) assert(round(metrics['sharpe'], 4) == -9.7079) # -7.2709) assert(round(metrics['mdd_pct'], 6) == 0.002574) # -0.002841) return strategy
#def test_account(): if __name__ == "__main__": from pyqstrat.pq_types import Contract, ContractGroup, Trade from pyqstrat.orders import MarketOrder import math def get_close_price(contract, timestamps, idx, strategy_context): if contract.symbol == "IBM": price = idx + 10.1 elif contract.symbol == "MSFT": price = idx + 15.3 else: raise Exception(f'unknown contract: {contract}') return price ibm_cg = ContractGroup.create('IBM') msft_cg = ContractGroup.create('MSFT') ibm_contract = Contract.create('IBM', contract_group=ibm_cg) msft_contract = Contract.create('MSFT', contract_group=msft_cg) timestamps = np.array([ '2018-01-01 09:00', '2018-01-02 08:00', '2018-01-02 09:00', '2018-01-05 13:35' ], dtype='M8[m]') account = Account([ibm_cg, msft_cg], timestamps, get_close_price, None) #account = Account([Contract(symbol)], timestamps, get_close_price) trade_1 = Trade(ibm_contract, np.datetime64('2018-01-02 08:00'), 10, 10.1,
trade = Trade(order.contract, timestamp, order.qty, trade_price, order = order, commission = 0, fee = 0) order.status = 'filled' print(f'trade: {trade}') trades.append(trade) return trades def get_price(contract, timestamps, i, strategy_context): if contract.symbol == 'KO': return strategy_context.ko_price[i] elif contract.symbol == 'PEP': return strategy_context.pep_price[i] raise Exception(f'Unknown contract: {contract}') ko_contract_group = ContractGroup.create('KO') pep_contract_group = ContractGroup.create('PEP') strategy_context = SimpleNamespace(ko_price = ko_prices.c.values, pep_price = pep_prices.c.values) strategy = Strategy(timestamps, [ko_contract_group, pep_contract_group], get_price, strategy_context = strategy_context) for tup in [(ko_contract_group, ko_prices), (pep_contract_group, pep_prices)]: for column in ['o', 'h', 'l', 'c']: strategy.add_indicator(column, tup[1][column], contract_groups = [tup[0]]) strategy.add_indicator('ratio', ratio) strategy.add_indicator('zscore', zscore_indicator, depends_on = ['ratio']) strategy.add_signal('pair_strategy_signal', pair_strategy_signal, depends_on_indicators = ['zscore']) # ask pqstrat to call our trading rule when the signal has one of the values [-2, -1, 1, 2] strategy.add_rule('pair_entry_rule', pair_entry_rule,