示例#1
0
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))
示例#2
0
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