Overall Statistics
Total Trades
418
Average Win
1.51%
Average Loss
-0.78%
Compounding Annual Return
23.101%
Drawdown
14.400%
Expectancy
0.308
Net Profit
59.487%
Sharpe Ratio
1.159
Probabilistic Sharpe Ratio
55.292%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
1.94
Alpha
0.18
Beta
-0.015
Annual Standard Deviation
0.155
Annual Variance
0.024
Information Ratio
0.59
Tracking Error
0.255
Treynor Ratio
-11.876
Total Fees
$775.65
# https://quantpedia.com/Screener/Details/61
from QuantConnect.Data import SubscriptionDataSource
from QuantConnect.Python import PythonData
from datetime import date, timedelta, datetime
from decimal import Decimal
import numpy as np
import pandas as pd
import math
#import sys

class WeatherImportTest(QCAlgorithm):

    def ExitPositions(self):
        
        #If the market is open, use liquidate.
        if self.Securities["SPY"].Exchange.ExchangeOpen == True:
            #Liquidate all positions.
            self.Liquidate()
        
        #The above self.Liquidate only works during regular market hours. If there are still open positions, use limit orders to liquidate.
        invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        
        if len(invested) > 0:
            for symbol in invested:
                security_holding = self.Portfolio[symbol]
                quantity = security_holding.Quantity
                if quantity > 0:
                    limit_price = self.Securities[symbol].Price * 0.99
                    self._limitTicket = self.LimitOrder(symbol, -1 * quantity, limit_price)
        
        #Cancel any open orders too. Including sell orders, because if they haven't sold already, we probably need to adjust the price anyways.
        openOrders = self.Transactions.GetOpenOrders()
        if len(openOrders)> 0:
            for x in openOrders:
                if x.Quantity > 0: #only cancel buy orders
                    self.Transactions.CancelOrder(x.Id)
                    
        return

    def getTradingMonth(self):
        
        if self.Time.month < 4:
            return True
        elif self.Time.month > 10:
            return True
        else:
            return False
            
        return False
        
    def getHDDDelta(self, data):
    
        if data.ContainsKey("adjusted_hdd_delta"):
            if not self.hdd_deltaWindow.IsReady: 
                self.hdd_deltaWindow.Add(self.Securities["adjusted_hdd_delta"].Price)
                return False
                # self.Debug(str('adding to window'))
            elif int(self.Securities["adjusted_hdd_delta"].Price) == 0:
                return False
                #self.Debug(str('delta = 0'))
            elif int(self.Securities["adjusted_hdd_delta"].Price) != int(self.hdd_deltaWindow[1]):
                self.hdd_deltaWindow.Add(self.Securities["adjusted_hdd_delta"].Price)
                #self.Debug(str(self.Time) + ' ' +  str(self.hdd_deltaWindow[0]) + ' ' + str(self.hdd_deltaWindow[1]) + ' ' + str(self.hdd_deltaWindow[2]))
                return float(self.Securities["adjusted_hdd_delta"].Price)
            else:
                return False

        return False

    def Buy(self, thisSecurity, cashPercentage, limit):
        
        #If the market is open, use a market order.
        if self.Securities["SPY"].Exchange.ExchangeOpen == True:
            self.SetHoldings(thisSecurity, cashPercentage)
            shares = self.Portfolio[thisSecurity].Quantity
            limit_price = self.Securities[thisSecurity].Price
            
            #Add stop loss and take profit
            self.LimitOrder(thisSecurity, -shares, limit_price * (1.10))
            self.StopMarketOrder(thisSecurity, -shares, limit_price * (0.90))
            
        #If the market is in extended hours, use a limit order (market orders do not work during extended hours).
        else:
            return False #Enable this line to only do afternoon trades
            equity      = round(float(float(self.Portfolio.Cash) * cashPercentage),2)
            limit_price = self.Securities[thisSecurity].Price * limit
            if limit_price > 0:
                shares      = round(math.floor(equity / limit_price),2)
                if shares > 0:
                    #self.Debug(str(self.Time) + " Cash="+str(self.Portfolio.Cash) + ' CashPct=' + str(cashPercentage) + ' Equity=' + str(equity) + ' LimitPrice=' + str(limit_price) + ' Shares= ' + str(shares))
                    self.LimitOrder(thisSecurity, shares, limit_price)
                    #take profit
                    self.LimitOrder(thisSecurity, -shares, limit_price * (1.10))
                    #stop loss
                    self.StopMarketOrder(thisSecurity, -shares, limit_price * (0.9))


                    
                    self.Debug("post order")
        return

    def Initialize(self):
        self.SetStartDate(2018, 1, 1)  # Set Start Date
        self.SetEndDate(2020, 3, 30)
        self.SetCash(10000)  # Set Strategy Cash
        self.AddEquity("BOIL", Resolution.Minute, Market.USA, True, 0, True)
        self.AddEquity("KOLD", Resolution.Minute, Market.USA, True, 0, True)
        self.AddEquity("UGAZ", Resolution.Minute, Market.USA, True, 0, True)
        self.AddEquity("DGAZ", Resolution.Minute, Market.USA, True, 0, True)
        self.AddEquity("SPY",  Resolution.Minute, Market.USA, True, 0, True)
   
        self.Securities["BOIL"].SetDataNormalizationMode(DataNormalizationMode.Adjusted)
        self.Securities["KOLD"].SetDataNormalizationMode(DataNormalizationMode.Adjusted)
        self.Securities["UGAZ"].SetDataNormalizationMode(DataNormalizationMode.Adjusted)
        self.Securities["DGAZ"].SetDataNormalizationMode(DataNormalizationMode.Adjusted)
        self.Securities["SPY"].SetDataNormalizationMode(DataNormalizationMode.Adjusted)
        
        #states = (self.GetParameter("states"))
        self.AddData(Weather, "adjusted_hdd_delta", Resolution.Minute, fillDataForward=False)
        
        # Every day at [time], liquidate all positions and cancel open orders.
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9,55), Action(self.ExitPositions))
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15,55), Action(self.ExitPositions))
        
        self._limitTicket = None
        self.hdd_deltaWindow = RollingWindow[float](5)
        
        self.SetTimeZone(TimeZones.NewYork)
        
        #self.SetWarmUp(timedelta(7))
        #self.OnWarmupFinished(timedelta(7))

    def OnData(self, data):
        
        #Declare variables 
        min_hdd_delta = int(self.GetParameter("min_hdd_delta"))
        buy_limit = float(self.GetParameter("buy_limit"))
        sell_limit = float(self.GetParameter("sell_limit"))
        
        marketOpenToday = self.Securities["BOIL"].Exchange.Hours.IsDateOpen(self.Time) #Check if market is open today (at any time)
        tradingMonth = self.getTradingMonth()
        
        #Since our hdd_delta (the "price") can't be negative, we had to reset the HDD delta so all deltas are positive. 1509.25 is the "true zero" in the raw data from the csv.
        #E.g. if the hdd_delta is > 1509.25, there's an increase in heating degree days.
        zero = 1509.25
        hdd_delta = self.getHDDDelta(data)
        
        #If this is a trading month, and if the market is open today, and we have a hdd_delta, then trade.
        if tradingMonth and marketOpenToday and bool(hdd_delta):
            
            #If there are no open orders, initiate new trade.
            #if len(self.Transactions.GetOpenOrders()) == 0: 
                
            #If the number of HDDs INCREASED, then the price of natgas will go up. Buy BOIL and sell KOLD.
            if hdd_delta > zero + min_hdd_delta:
                self.ExitPositions()
                self.Buy("BOIL", 0.95, buy_limit)
                
            #If the number of HDDs DECREASED, then the price of natgas will go up. Buy BOIL and sell KOLD.
            elif hdd_delta < zero - min_hdd_delta:
                self.ExitPositions()
                self.Buy("KOLD", 0.95, buy_limit)
                    
            else:
                pass
                

class Weather(PythonData):

    def GetSource(self, config, date, isLive):
        source = "https://www.dropbox.com/s/pytvxgb9pg272pw/quantconnect_hdd_export_dadstates.csv?dl=1" #Dad States
        #source = "https://www.dropbox.com/s/9wgmkfrcci50kgl/quantconnect_hdd_export_allstates.csv?dl=1" #All States
            
        return SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);

    def Reader(self, config, line, date, isLive):
        # If first character is not digit, pass
        if not (line.strip() and line[0].isdigit()): return None

        data = line.split(',')
        weather = Weather()
        weather.Symbol = config.Symbol
        #weather.Time = datetime.strptime(data[0], '%Y%m%d %H:%M') + timedelta(hours=0) 
        weather.Time = datetime.strptime(data[0], '%Y-%m-%d %H:%M:%S') + timedelta(hours=-1) 
        
        weather.Value = float(data[8]) #dadstates
        #weather.Value = float(data[4]) #allstates
            

        #self.Log(str(weather.Time) + " " + str(weather.Value))
        
        return weather
# Your New Python File