Тестирование на истории с использованием Python и панд - тестирование только одной открытой позиции за раз - PullRequest
0 голосов
/ 10 мая 2018

Это долгое чтение, но я просмотрел множество примеров StackOverflow по созданию функций для итерации по фреймам данных и т. Д. И просто не смог найти ничего, что соответствовало бы моим потребностям. Я также использую Python и кодирование в целом только около 2 месяцев, поэтому я прошу прощения, если что-то неясно.

У меня есть фрейм данных с дневной ценовой историей, и я пытаюсь создать бэк-тест для сигналов покупки на основе этой стратегии:

Сначала мы ищем день, когда цена закрытия превышает цену закрытия как днем ​​ранее, так и днем ​​позже . Давайте назовем это «базовым днем».

Чтобы инициировать наш сигнал на покупку, мы ждем день, когда цена закрытия вернется выше «базового дня». Теперь у нас есть открытая позиция.

Мы удерживаем эту позицию до тех пор, пока не получим сигнал на продажу, противоположный тому, что искал наш сигнал на покупку. (т.е. цена закрытия ниже предыдущего дня, когда днем ​​ранее и днем ​​позже были выше)

Я хочу, чтобы за один раз была активна только одна покупка, пока мы не получим сигнал на продажу, и тогда процесс не начнется заново.

Ниже приведен пример кадра данных с небольшой частью данных, на которые я смотрю

import pandas as pd

data = {
'date': [1/3/2000,1/4/2000,1/5/2000,1/6/2000,1/7/2000,1/10/2000,1/11/2000,1/12/2000,1/13/2000,1/14/2000,1/18/2000,1/19/2000,1/20/2000,1/21/2000,1/24/2000,1/25/2000,1/26/2000,1/27/2000,1/28/2000,1/31/2000,2/1/2000,2/2/2000,2/3/2000,2/4/2000,2/7/2000,2/8/2000,2/9/2000,2/10/2000,2/11/2000,2/14/2000,2/15/2000,2/16/2000,2/17/2000,2/18/2000,2/22/2000,2/23/2000,2/24/2000,2/25/2000,2/28/2000,2/29/2000],

'close': [308.3,315.3,314.4,307.5,309.8,313.4,310.7,324.2,332.5,348.8,351.1,348.2,348.7,343.5,343,343.3,342.4,343,334.4,334.6,336,333.8,331.6,332.8,335.9,341.2,338.4,342.1,343.2,339.5,346.9,342,339.6,337.4,335,330.8,331.3,331.1,332.6,335.1]}

df = pd.DataFrame(data)
## Create columns to compare price to day before and day after
df['prev_close'] = df['close'].shift(1)
df['next_close'] = df['close'].shift(-1)


## BOOLEAN TO RETURN IF PRICE IS LOWER THAN PREVIOUS AND NEXT DAY
df['high_high'] = ((df['prev_close']) > df['close']) & ((df['next_close']) > df['close'])

## BOOLEAN TO RETURN TRUE IF PRICE IS GREATER THAN PREVIOUS AND NEXT DAY
df['low_low'] = ((df['prev_close']) < df['close']) & ((df['next_close']) < df['close'])


## RETURN PRICE OF MOST RECENT true IN low_low
df['comp_price'] = df['close'].where(df['low_low'] == True)
## FILL IN BLANKS WITH PREVIOUS VALUE TO KEEP COMPARISON PRICE ACTIVE
df['comp_price'].fillna(method='pad',inplace=True)

## CREATE SELL COMPARISON DATE TO REFERENCE WHEN CLOSING POSITION
df['sell_comp'] = df['close'].where(df['high_high'] == True)
df['sell_comp'].fillna(method='pad',inplace=True)

## CREATE BUY SIGNAL
df['buy_sig'] = df['close'] > df['comp_price']

## DESIGNATE FIRST INSTANCE OF BUY SIGNAL AS DAY TO OPEN POSITION
df['open_pos'] = (df['buy_sig'] == 1) & (df['buy_sig'].shift(1) != 1)
df['take_signal'] = (df['buy_sig'] == 1) & (df['open_pos'] == True)
df['open_pos_price'] = df['close'].where(df['take_signal'] == True)
df['open_pos_price'].fillna(method='pad',inplace=True)


## CREATE SELL SIGNAL
df['sell_sig'] = df['close'] < df['sell_comp']
## DESIGNATE FIRST INSTANCE OF SELL AS DAY TO CLOSE POSITION
df['close_pos'] = (df['sell_sig'] == True) & (df['sell_sig'].shift(1) == False)

## CREATE COLUMNS THAT ORGANIZE WHEN POSITION WAS OPENED
df['open_pos_date'] = df['date'].where((df['open_pos'] == True)&(df['take_signal'] == True))
df['open_pos_date'].fillna(method='pad',inplace=True)

## CREATE COLUMNS SHOW DATE AND PRICE OF CLOSING POSITION
df['close_pos_price'] = df['close'].where(df['close_pos'] == True)
df['close_pos_date'] = df['date'].where((df['close_pos'] == True))

## CALCULATE GAIN FOR TRADE
df['gain'] = (df['close_pos_price'] - df['open_pos_price']).where((df['close_pos_price'] > 0)& (df['open_pos_price'] > 0))

Затем я создал еще один фрейм данных, который отображал результаты при получении сигналов на продажу, чтобы впоследствии я мог превратить результаты в кортежи и выполнить итерацию, чтобы добавить стоимость транзакции и т. Д., Чтобы закончить для целей построения графиков.

strat_df = df.loc[(df['close_pos'] == True)&(df['sell_sig'] == True), ['open_pos_date','open_pos_price', 'close_pos_date','close_pos_price','gain']]

Я вижу несколько экземпляров одного и того же open_pos_date с разными значениями close_pos_date. Где-то по пути я разрешаю работать нескольким открытым позициям.

Я хочу оставить свой первый сигнал на покупку в качестве единственной позиции, игнорируя все остальные сигналы на покупку, пока не получу сигнал на продажу. В этот момент я хочу найти новый сигнал на покупку и удерживать эту позицию только до тех пор, пока не получу новую продажу.

Вероятно, я создал больше столбцов, чем необходимо, но мне было трудно найти способ получить уникальный сигнал для открытия позиции, а затем сравнить цену с той, какой будет эта цена, когда я получу сигнал на продажу. Если кто-то может порекомендовать более чистый способ сделать это, я бы с радостью отказался от своей первой попытки и попробовал.

1 Ответ

0 голосов
/ 10 мая 2018

Хотя обычно вы хотите избежать итерации по строкам информационного кадра, так как он довольно медленный и неэффективный, я считаю, что это обычно лучший метод при тестировании на истории.Поскольку ваши позиции и значения портфеля зависят от значений T-1 для расчета значений при T, обычно необходимо переходить от строки к строке, и это намного проще.

import pandas as pd
data = {'date': ['1/3/2000','1/4/2000','1/5/2000','1/6/2000','1/7/2000','1/10/2000',
                '1/11/2000','1/12/2000','1/13/2000','1/14/2000','1/18/2000','1/19/2000','1/20/2000','1/21/2000',
                 '1/24/2000','1/25/2000','1/26/2000','1/27/2000','1/28/2000','1/31/2000','2/1/2000','2/2/2000',
                 '2/3/2000','2/4/2000','2/7/2000','2/8/2000','2/9/2000','2/10/2000','2/11/2000','2/14/2000',
                 '2/15/2000','2/16/2000','2/17/2000','2/18/2000','2/22/2000','2/23/2000','2/24/2000','2/25/2000',
                 '2/28/2000','2/29/2000'],
'close': [308.3,315.3,314.4,307.5,309.8,313.4,310.7,324.2,332.5,348.8,351.1,348.2,348.7,343.5,343,343.3,342.4,343,
          334.4,334.6,336,333.8,331.6,332.8,335.9,341.2,338.4,342.1,343.2,339.5,346.9,342,339.6,337.4,335,330.8,331.3,
          331.1,332.6,335.1]}

df = pd.DataFrame(data)
df = df.set_index(['date'])
df['pos'] = 0

base_buy = 999999.0
base_sell = 0.0
for i in range(2, df.shape[0] - 1):

    px_m1 = df.iloc[i - 1].loc['close']
    px = df.iloc[i].loc['close']
    px_p1 = df.iloc[i + 1].loc['close']
    pos = df.iloc[i - 1].loc['pos']

    #base_buy
    if px > px_m1 and px > px_p1 and pos == 0:
        base_buy = px

    #entry signal
    if px > base_buy and pos == 0:
        pos = 1.0
        base_sell = 0.0

    #base_sell
    if px < px_m1 and px < px_p1 and pos == 1:
        base_sell = px

    #exit signal
    if px < base_sell and pos == 1.0:
        pos = 0.0
        base_buy = 999999.0

    df.iloc[i, 1] = pos

print(df)

output:

           close  pos
date                 
1/3/2000   308.3  0.0
1/4/2000   315.3  0.0
1/5/2000   314.4  0.0
1/6/2000   307.5  0.0
1/7/2000   309.8  0.0
1/10/2000  313.4  0.0
1/11/2000  310.7  0.0
1/12/2000  324.2  1.0
1/13/2000  332.5  1.0
1/14/2000  348.8  1.0
1/18/2000  351.1  1.0
1/19/2000  348.2  1.0
1/20/2000  348.7  1.0
1/21/2000  343.5  0.0
1/24/2000  343.0  0.0
1/25/2000  343.3  0.0
...