Back to Community
Lunar phase strategy revisited

Some time ago I submitted a rather silly strategy. It used the lunar phase to buy, well, mostly anything really. It was the simplest of strategies: on the close of the first (or second) day of the 7th 8th phase of the Moon -- GO LONG. Get out on the start of the 8th phase.

Seriously.

I added a simple filter using the MFI (money flow index) which helped to exclude some obvious ugly patches but for the most part this phenomena has been in place for the last 6 years. And remains so. This strategy works even better if you use hourly data (not tested here) and you wait until you've got a higher high within about 4 or 5 periods.

To date I have not figured out why this strategy works. Nor has anyone offered a logical rational. But it works. Here for your incredulous wonderment I present a 2.9 leveraged version.

If you figure out how to take those couple of nasty dips out of it... do share.

Clone Algorithm
56
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import talib
import zipline

MFILevel   = 50
MFIPeriods = 8
Leverage   = 2.9

def initialize(context):
    context.REF = symbols("DIA","CAT","AA","MCD","JNJ","AAPL")[0]
    set_commission(commission.PerShare(cost=0.01))
    schedule_function(CalculateMFI, date_rules.every_day(), time_rules.market_open())
    schedule_function(HandleEntry,  date_rules.every_day(), time_rules.market_open())
    context.S = {}
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def handle_data(context, data):
    for stock in data:
        if (stock not in context.S):
            context.S[stock]     = DataStock(stock, data[stock])
            context.S[stock].MFI = 50.0
        else:
            context.S[stock].Update(data[stock])
    removeThese = []
    for stock in context.S:
        if (stock not in data):
            removeThese.append(stock)
    for stock in removeThese:
        del context.S[stock]                    

    record(Leverage = context.account.leverage)
    record(MFI      = context.S[context.REF].MFI)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def HandleEntry(context, data):
    quoteDate = data[context.REF].datetime
    index = GetPhaseOfMoonEighth(quoteDate.month, quoteDate.day, quoteDate.year)
    
    for stock in context.S:
        if index == 7 and context.portfolio.positions[stock].amount == 0:
            if (context.S[stock].MFI > MFILevel):
                order_target_percent(stock, 1.0 / len(context.S) * Leverage)
        elif index == 1:
            order_target_percent(stock, 0) 
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def GetPhaseOfMoonEighth(month, day, year):
    ages = [18, 0, 11, 22, 3, 14, 25, 6, 17, 28, 9, 20, 1, 12, 23, 4, 15, 26, 7]
    offsets = [-1, 1, 0, 1, 2, 3, 4, 5, 7, 7, 9, 9]
    if day == 31:
        day = 1
    days_into_phase = ((ages[(year + 1) % 19] + ((day + offsets[month-1]) % 30) + (year < 1900)) % 30)
    index = int((days_into_phase + 2) * 16/59.0)
    if index > 7:
        index = 7
    index += 1
    return index

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateMFI(context, data):
    highDeck = history(300, '1d', 'high').dropna(axis=1)
    lowsDeck = history(300, '1d', 'low').dropna(axis=1)
    closDeck = history(300, '1d', 'close_price').dropna(axis=1)
    voluDeck = history(300, '1d', 'volume').dropna(axis=1)
    for stock in context.S:
        try:
            mfi = talib.MFI(highDeck[stock], lowsDeck[stock], closDeck[stock], voluDeck[stock], timeperiod=MFIPeriods)
            context.S[stock].MFI = mfi[-1]
            context.S[stock].Trigger += 1 if mfi[-1] > 50 else -1
        except:
            pass

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class DataStock(zipline.protocol.SIDData):
    def __init__(this, sid, dataSid):
        this.Update(dataSid)

    def Update(this, dataSid):
        this.Open   = dataSid.open_price
        this.High   = dataSid.high
        this.Low    = dataSid.low
        this.Close  = dataSid.close_price
        this.Volume = dataSid.volume
There was a runtime error.
1 response

I really love the idea but I'm not sure this has a real edge:

If you start it from the end of the market dip (April 2009) with Leverage=1 then it gets 46% of the benchmark returns, and on average it spends 41% of its time with any open position. If you just buy and hold SPY at 2.9 leverage over that period then you get max drawdown 30%, compared to 32% drawdown with the algo at 2.9 leverage. (And AAPL is always a nice pick). So I don't think this is extracting unusually good returns