A Candlestick Strategy With Soldiers And Crows

References

█ OVERVIEW

In the article "Weekly & Daily MACD" in this issue, author Vitali Apirine introduces a novel approach to using the classic MACD indicator in a way that simulates calculations based on different timeframes while using just a daily-interval chart. He describes a number of ways to use this new indicator that allows traders to adapt it to differing markets and conditions.

█ STRATEGY

The bullish one white soldier and bearish one black crow patterns highlighted by Jerry D'Ambrosio and Barbara Star in their article in this issue, "A Candlestick Strategy with Soldiers and Crows," have been added to our Community Components library for easy reference in users' strategies. Here's the complete list of strategy rules:

  • Enter long next bar at open if following conditions are met:

    • Stock price greater than $1
    • 50-day simple moving average of volume is greater than 100,000
    • Yesterday's close was less than the day before
    • Yesterday's close was less than its open
    • Today's open is greater than yesterday's close
    • Today's close is greater than yesterday's open
    • Today's open is less than yesterday's open
    • As of yesterday's close, price had been closing lower for three days
  • Sell short next bar at open if following conditions are met:

    • Stock price is greater than $10
    • 50-day simple moving average of volume is greater than 100,000
    • Yesterday's close was higher than the close the day before
    • Yesterday's close was greater than yesterday's open
    • Today's open is less than yesterday's close
    • Today's close is less than yesterday's open
    • Today's open is higher than yesterday's open
    • As of yesterday's close, price had been closing higher for three days.
  • Exit long position if any condition is triggered:

    • Exit at market on two lower lows
    • Exit at market if either the 14-period stochastic is at or above than 80 or the 14-period RSI is at or above 70
    • Exit at a 3% stop-loss (if enabled)
    • Exit at a 5% take-profit (if enabled)
  • Cover short position if any condition is triggered:

    • Exit at market on two higher highs
    • Exit at market if either the 14-period stochastic is at or below 20 or the 14-period RSI is at or below 30
    • Exit at a 3% stop-loss (if enabled)
    • Exit at a 5% take-profit (if enabled)

png

png

Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100

import warnings
warnings.filterwarnings("ignore")

import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234





1234
Download data
##### Download data#S&P 500 (^GSPC),  Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)
#CBOE Volatility Index (^VIX) Chicago Options - Chicago Options Delayed Price. Currency in USD

#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT',  'CL=F', 'GC=F', '^TNX']

benchmark_tickers = ['^GSPC', '^VIX']
tickers = benchmark_tickers + ['GSK', 'BST', 'PFE']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
#     def history(self, period="1mo", interval="1d",
#                 start=None, end=None, prepost=False, actions=True,
#                 auto_adjust=True, back_adjust=False,
#                 proxy=None, rounding=False, tz=None, timeout=None, **kwargs):

dfs = {}

for ticker in tickers:
    cur_data = yf.Ticker(ticker)
    hist = cur_data.history(period="max", start='2000-01-01')
    print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
    dfs[ticker] = hist
2022-09-10 19:00:08.298576 ^GSPC (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 19:00:08.689645 ^VIX (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 19:00:09.066685 GSK (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 19:00:09.381786 BST (1980, 7) 2014-10-29 00:00:00 2022-09-09 00:00:00
2022-09-10 19:00:09.814302 PFE (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
ticker = 'PFE'
dfs[ticker].tail(5)
Open High Low Close Volume Dividends Stock Splits
Date
2022-09-02 46.740002 46.799999 45.529999 45.700001 14662700 0.0 0.0
2022-09-06 45.959999 46.439999 45.529999 45.759998 17153500 0.0 0.0
2022-09-07 45.700001 46.209999 45.380001 46.130001 15378900 0.0 0.0
2022-09-08 46.020000 47.119999 45.869999 47.080002 18271000 0.0 0.0
2022-09-09 47.200001 47.990002 47.099998 47.840000 17501700 0.0 0.0
Calculate the technical indicators and Find signals
"""
    Function: __C_Crow
    // _C_Crow Function
    // TASC OCT 2017
    // A Candlestick Strategy
    // With Soldiers and Crows
    // D'Ambrosio and Star

    _C_Crow = Close[1] > Close[2] and
            Close[1] > Open[1] and 
            Open < Close[1] and
            Close < Open[1] and 
            Open > Open[1] and
            Close[1] > Close[2] and 
            Close[2] > Close[3] ;
"""
"""
One Black Crow:
minPrice:= 10;    {minimum price}
minVol:= 100000; {minimum volume}
trend:= Sum( C > Ref(C, -1), 3) = 3; 
Ref(C > O, -1) AND O < Ref(C, -1) AND
C < Ref(O, -1) AND O > Ref(O, -1) AND
Ref(trend, -1) AND C > minPrice AND
Mov(V, 50, S) > minVol



"""
def _scan_crow(c, o):
    crow_ = True
    crow_ = crow_ & (c.shift(1)>c.shift(2)) 
    crow_ = crow_ & (c.shift(1)>o.shift(1)) 
    crow_ = crow_ & (o<c.shift(1)) 
    crow_ = crow_ & (c<o.shift(1)) 
    crow_ = crow_ & (o>o.shift(1)) 
    crow_ = crow_ & (c.shift(2)>c.shift(3))
    return crow_



"""

    Function: _C_Soldier
    // _C_Soldier Function
    // TASC OCT 2017
    // A Candlestick Strategy
    // With Soldiers and Crows
    // D'Ambrosio and Star

    _C_Soldier = Close[1] < Close[2] and
                Close[1] < Open[1] and 
                Open > Close[1] and
                Close > Open[1] and 
                Open < Open[1] and
                Close[1] < Close[2] and 
                Close[2] < Close[3] ;

"""
"""
One White Soldier:
minPrice:= 1;    {minimum price}
minVol:= 100000; {minimum volume}
trend:= Sum( C < Ref(C, -1), 3) = 3; 
Ref(C < O, -1) AND O > Ref(C, -1) AND
C > Ref(O, -1) AND O < Ref(O, -1) AND
Ref(trend, -1) AND C > minPrice AND
Mov(V, 50, S) > minVol

"""


def _scan_soldier(c, o):
    soldier_ = True
    soldier_ = soldier_ & (c.shift(1)<c.shift(2)) 
    soldier_ = soldier_ & (c.shift(1)<o.shift(1)) 
    soldier_ = soldier_ & (o>c.shift(1)) 
    soldier_ = soldier_ & (c>o.shift(1)) 
    soldier_ = soldier_ & (o<o.shift(1)) 
    soldier_ = soldier_ & (c.shift(2)<c.shift(3))
    return soldier_

from core.finta import TA
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
df_ref = dfs['^GSPC'][[ 'Close']]
df_ref = df_ref.round(2)
df_ref.columns = ['ref_close']

print(df.shape, df_ref.shape)
df = df.merge(df_ref, left_index=True, right_index=True, how='inner')
print(df.shape)
df.isna().sum()
(5710, 5) (5710, 1)
(5710, 6)





Open         0
High         0
Low          0
Close        0
Volume       0
ref_close    0
dtype: int64
df_ta = TA.BBANDS(df, period = 20, std_multiplier=2.2, column="close")
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

df_ta = TA.XSII(df, slow_period = 102, fast_period = 7)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

df_ta = TA.RSMK(df, rsmk_period = 60, ema_period = 5, column = "close", ref_column = "ref_close")
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

df_ta = TA.SQZMI(df, period = 20,  column = "close")
df['SQZMI'] = df_ta

del df_ta
gc.collect()
38
df['SOLDIER'] = _scan_soldier(df['Close'], df['Open'])
df['CROW'] = _scan_crow(df['Close'], df['Open'])
df_ta = TA.SMA(df, period = 50, column = "volume") 
df['MAV'] = df_ta.values
df[['SOLDIER', 'CROW', 'SQZMI']].value_counts()
SOLDIER  CROW   SQZMI
False    False  False    4865
                True      682
True     False  False      80
False    True   False      63
True     False  True       11
False    True   True        9
dtype: int64
min_avg_volume = 100000 
df['B'] = (df['SOLDIER'] & (df['MAV']>min_avg_volume)).astype(int)*((df['High'] + df['Low'])/2)
df['S'] = (df['CROW'] & (df['MAV']>min_avg_volume)).astype(int)*((df['High'] + df['Low'])/2)
df['B2'] = (df['SOLDIER'] & (df['MAV']>min_avg_volume) & df['SQZMI']).astype(int)*((df['High'] + df['Low'])/2)
display(df.head(5))
display(df.tail(5))
Open High Low Close Volume ref_close BB_UPPER BB_MIDDLE BB_LOWER BBWIDTH PERCENT_B XSII1 XSII2 XSII3 XSII4 RSMK SQZMI SOLDIER CROW MAV B S B2
Date
1999-12-31 14.25 14.31 14.11 14.22 5939817 1469.25 NaN NaN NaN NaN NaN NaN NaN 15.2154 13.2246 NaN False False False NaN 0.0 0.0 0.0
2000-01-03 14.06 14.20 13.87 13.98 12873345 1455.22 NaN NaN NaN NaN NaN NaN NaN 14.9586 13.0014 NaN False False False NaN 0.0 0.0 0.0
2000-01-04 13.70 13.81 13.16 13.46 14208974 1399.42 NaN NaN NaN NaN NaN NaN NaN 14.4022 12.5178 NaN False False False NaN 0.0 0.0 0.0
2000-01-05 13.54 13.98 13.51 13.68 12981591 1402.11 NaN NaN NaN NaN NaN NaN NaN 14.6376 12.7224 NaN False False False NaN 0.0 0.0 0.0
2000-01-06 13.70 14.36 13.68 14.17 11115273 1403.45 NaN NaN NaN NaN NaN 14.17851 13.62249 15.1619 13.1781 NaN False False False NaN 0.0 0.0 0.0
Open High Low Close Volume ref_close BB_UPPER BB_MIDDLE BB_LOWER BBWIDTH PERCENT_B XSII1 XSII2 XSII3 XSII4 RSMK SQZMI SOLDIER CROW MAV B S B2
Date
2022-09-02 46.74 46.80 45.53 45.70 14662700 3924.26 51.663455 48.1505 44.637545 0.145916 0.151220 46.90878 45.06922 52.314323 45.469459 -10.218471 False False False 17821424.0 0.0 0.0 0.0
2022-09-06 45.96 46.44 45.53 45.76 17153500 3908.19 51.579166 47.9600 44.340834 0.150924 0.196063 46.81800 44.98200 52.168463 45.342683 -9.751431 False False False 17677630.0 0.0 0.0 0.0
2022-09-07 45.70 46.21 45.38 46.13 15378900 3979.87 51.374440 47.7775 44.180560 0.150570 0.270986 46.81800 44.98200 52.061739 45.249923 -9.580215 False False False 17662122.0 0.0 0.0 0.0
2022-09-08 46.02 47.12 45.87 47.08 18271000 4006.18 51.062513 47.6340 44.205487 0.143952 0.419207 47.08677 45.24023 52.031775 45.223879 -8.916949 False False False 17699184.0 0.0 0.0 0.0
2022-09-09 47.20 47.99 47.10 47.84 17501700 4067.36 51.025194 47.6115 44.197806 0.143398 0.533468 47.37849 45.52051 52.030341 45.222633 -8.579216 False False False 17770704.0 0.0 0.0 0.0
df.columns
Index(['Open', 'High', 'Low', 'Close', 'Volume', 'ref_close', 'BB_UPPER',
       'BB_MIDDLE', 'BB_LOWER', 'BBWIDTH', 'PERCENT_B', 'XSII1', 'XSII2',
       'XSII3', 'XSII4', 'RSMK', 'SQZMI', 'SOLDIER', 'CROW', 'MAV', 'B', 'S',
       'B2'],
      dtype='object')
Visual
from core.visuals import *
start = -1500
end = df.shape[0]
# df_sub = df.iloc[start:end]
df_sub = df[(df.index<='2016-06-02') & (df.index>='2015-09-02')]
names = {'main_title': f'{ticker}'}
lines0 = basic_lines(df_sub[['XSII1', 'XSII2', 'XSII3', 'XSII4']], 
                     colors = [], 
                     **dict(panel=0, width=1.5, secondary_y=False))

lines1 = basic_lines(df_sub[['RSMK']], 
                     colors = ['cadetblue'], 
                     **dict(panel=1, width=1, secondary_y=False))

lines3 = basic_lines(df_sub[[ 'S']],
                     colors = ['black'], 
                     **dict(panel=0, type='scatter', marker=r'${S}$' , markersize=100, secondary_y=False))

lines2 = basic_lines(df_sub[[ 'B', 'B2']],
                     colors = ['navy', 'red'], 
                     **dict(panel=0, type='scatter', marker=r'${B}$' , markersize=100, secondary_y=False))


lines_ = dict(**lines0, **lines1)
lines_.update(lines2)
lines_.update(lines3)

shadows_ = basic_shadows(bands=[-5, 5], nsamples=df_sub.shape[0], **dict(panel=1, color="lightskyblue",alpha=0.1,interpolate=True))

fig_config_ = dict(figratio=(18,10), volume=False, volume_panel=2,panel_ratios=(5,2), tight_layout=True, returnfig=True,)

ax_cfg_ = {0:dict(basic=[6, 2, ['XSII1', 'XSII2', 'XSII3', 'XSII4']], 
                 title=dict(label = 'XSII', fontsize=9, style='italic',  loc='left'), 
                ),
           2:dict(basic=[1, 0, ['RSMK']]
                ),
          }


names = {'main_title': f'{ticker}'}

aa_, bb_ = make_panels(main_data = df_sub[['Open', 'High', 'Low', 'Close', 'Volume']], 
                       added_plots = lines_,
                       fill_betweens = shadows_, 
                       fig_config = fig_config_, 
                       axes_config = ax_cfg_,  
                       names = names)

png