Overall Statistics
Total Trades
4
Average Win
0.68%
Average Loss
-0.36%
Compounding Annual Return
-37.556%
Drawdown
2.100%
Expectancy
-0.047
Net Profit
-1.489%
Sharpe Ratio
-2.717
Probabilistic Sharpe Ratio
17.037%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
1.86
Alpha
0.136
Beta
0.939
Annual Standard Deviation
0.104
Annual Variance
0.011
Information Ratio
6.793
Tracking Error
0.024
Treynor Ratio
-0.3
Total Fees
$9.20
Estimated Strategy Capacity
$11000.00
Lowest Capacity Asset
SPY 2ZXQTQXDXTEPY|SPY R735QTJ8XC9X
Portfolio Turnover
45.24%
#region imports
from AlgorithmImports import *
#endregion
import numpy as np
import math
from position import *
import datetime as dt

class HipsterVioletCat(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 2, 27)  # Set Start Date
        self.SetEndDate(2015, 3, 10)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash

        # ------------------------------------
        # Perameters
        # ------------------------------------

        self.equity = self.AddEquity("SPY", Resolution.Minute)
        self.option= self.AddOption("SPY", Resolution.Minute)

        self.option.SetFilter(self.ContractUniverse)
        self.option.PriceModel = OptionPriceModels.CrankNicolsonFD()  # both European & American, automatically
        
        # record data on each incoming data
        self.data = None

        # record contract subscribed
        self.contractsAdded = set()
        self.optionHoldings = dict()

        # instantiate trade manager
        self.trade_manager = TradeManager(self)

        self.Schedule.On(self.DateRules.EveryDay(self.equity.Symbol),
            self.TimeRules.AfterMarketOpen("SPY", 30), Action(self.TradeOption))

        

    def OnData(self, data):
        if self.Time.time()>dt.time(10,1): return
        self.data = data
        self.chain =self.data.OptionChains.get(self.option.Symbol) 
    
    def TradeOption(self):
        if self.chain is None: return 
        
        if not self.Portfolio.Invested:
            self.trade_manager.TradeOption(self.getContractByDelta(-0.4, OptionRight.Put),-15)


    def ContractUniverse(self, universe):
        return universe.IncludeWeeklys().Expiration(TimeSpan.FromDays(1),
                                           TimeSpan.FromDays(45)).Strikes(-5,-5)

    def getContractByDelta(self,targetDelta, right= OptionRight.Call):
        ## Filter the Call/Put options contracts
        filteredContracts = [x for x in self.chain if x.Right == right] 

        ## Sort the contracts according to their closeness to our desired expiry
        contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: p.Expiry, reverse=False)
        closestExpirationDate = contractsSortedByExpiration[0].Expiry                                        
                                            
        ## Get all contracts for selected expiration
        contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
    
        ## Get the contract with the contract with the closest delta
        closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-targetDelta))

        return closestContract   
#region imports
from AlgorithmImports import *
#endregion
class TradeManager:

    def __init__(self, algorithm):
        self.Positions     = {}
        self.Algorithm  = algorithm
        self.nextTradeId = 0
        
    def OpenPosition(self):
        return [t for t in self.Positions.values() if t.IsOpen()]
    
    def OpenOptionPosition(self):
        return [t for t in self.Positions.values() if t.IsOpen() and 
            (t.AssetType()==SecurityType.Option or t.AssetType()==SecurityType.IndexOption)]
    
    def GetPositionByDelta(self, delta):
        if len(self.OpenOptionPosition()) ==0: return None
        return sorted(self.OpenOptionPosition(), key=lambda x: abs(x.Delta() - delta))[0]
    
    def GetPositionByUnitDelta(self, delta):
        if len(self.OpenOptionPosition()) ==0: return None
        return sorted(self.OpenOptionPosition(), key=lambda x: abs(x.UnitDelta() - delta))[0]
    

    def PortfolioDelta(self):
        return sum([t.Delta() for t in self.Positions.values() if t.IsOpen()])
    
    # Place a trade in the direction signalled
    def TradeOption(self, contract, qty):
        symbol = contract.Symbol
        price  = self.Algorithm.Securities[symbol].Price
        asset  = self.Algorithm.Securities[contract.UnderlyingSymbol].Price
        
        tag = f"Underlying Price: {asset} | Opened at ${price}"
        ticket  = self.Algorithm.MarketOrder(symbol, qty, False, tag)
        if not contract.Symbol in self.Positions:
            self.Positions[contract.Symbol]=OptionPosition(contract, self.Algorithm)
        return ticket

    def SetHoldings(self, symbol, proportion):
        qty = self.Algorithm.CalculateOrderQuantity(symbol, proportion)
        return self.TradeEquity(symbol, qty)

    def TradeEquity(self, symbol, qty):
        price  = self.Algorithm.Securities[symbol].Price
        tag = f"{symbol}#{qty}|Opened at ${price}"
        ticket  = self.Algorithm.MarketOrder(symbol, qty, False, tag)
        if not symbol in self.Positions:
            self.Positions[symbol]=Position(symbol, self.Algorithm)
        return ticket

    def Close(self, symbol, qty=None, reason=None):
        self.Positions[symbol].Close(qty, reason)
        if not self.Positions[symbol].IsOpen(): 
            del self.Positions[symbol]

#TODO: add a position base class
class Position:
    def __init__(self, symbol, algorithm):  
        self.Symbol    = symbol
        self.Algorithm = algorithm
        self.Security  = self.Algorithm.Securities[self.Symbol]
        self.position  = self.Algorithm.Portfolio[self.Symbol]

    #TODO: add check for order status, see if fully executed    
    def AssetType(self):
        return self.position.Type
    
    def AveragePrice(self):
        return self.position.AveragePrice

    def Quantity(self):
        return self.position.Quantity

    def MarketPrice(self):
        return self.Security.Holdings.Price
 
    def UnrealizedProfitPercent(self):
        return self.position.UnrealizedProfitPercent

    def UnrealizedProfit(self):
        return self.position.UnrealizedProfit

    def IsOpen(self):
        return self.position.Invested

    def Close(self, qty=None, reason=None):
        tag = f"Close | {reason} | {self.UnrealizedProfit()} Net"
        if not qty:
            self.Algorithm.Liquidate(self.Symbol, tag)
        if self.Quantity()*qty > 0:
            qty = -1*qty
        self.Algorithm.MarketOrder(self.Symbol, qty, False, tag)
    
    def Delta(self):
        return self.Security.SymbolProperties.ContractMultiplier* self.Quantity()

#TODO: add Greeks interface, e.g. delta, gamma extra
#TODO: rename class objects
class OptionPosition(Position):
    def __init__(self, contract, algorithm): 
        super().__init__(contract.Symbol, algorithm)
        self.Algorithm  = algorithm
        self.Contract   = contract

    def ExpiringWithIn(self, time_to_eixpiry = timedelta(minutes=10)): 
        expiry = self.Symbol.ID.Date + timedelta(hours=16)
        return True if ((expiry - self.Algorithm.Time) < time_to_eixpiry) else False
        
    def _Greeks(self):
        return self.Security.EvaluatePriceModel(self.Algorithm.CurrentSlice, self.Contract).Greeks

    def UnitDelta(self):
        return self._Greeks().Delta
    
    def Delta(self):
        return self.UnitDelta()*self.Security.SymbolProperties.ContractMultiplier* self.Quantity()

#TODO: add new underlying position
#TODO: add spread position