Overall Statistics
Total Trades
7
Average Win
0%
Average Loss
0%
Compounding Annual Return
16.263%
Drawdown
4.500%
Expectancy
0
Net Profit
9.374%
Sharpe Ratio
1.829
Probabilistic Sharpe Ratio
74.562%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.063
Beta
0.427
Annual Standard Deviation
0.061
Annual Variance
0.004
Information Ratio
-0.042
Tracking Error
0.072
Treynor Ratio
0.263
Total Fees
$7.00
Estimated Strategy Capacity
$160000000.00
Lowest Capacity Asset
AMD R735QTJ8XC9X
/*
	This program was developed by Quantify and is a template program.
	Usage and marketing of this program is permitted.
	
	www.quantify-co.com
*/

namespace QuantConnect.Algorithm.CSharp
{
    public class i_kamanu3 : QCAlgorithm
    {
    	// BACKTESTING PARAMETERS
    	// =================================================================================================================
    	
    	// general settings:
    	
    	// set starting cash
    	private int starting_cash = 100000;
    	
    	// backtesting start date time:
    	// date setting variables
    	private int start_year = 2021;
    	private int start_month = 5;
    	private int start_day = 1;
    	
    	// backtesting end date time:
    	// determines whether there is a specified end date
    	// if false it will go to the current date (if 'true' it will go to the specified date)
    	private bool enable_end_date = false;
    	// date setting variables
    	private int end_year = 2021;
    	private int end_month = 1;
    	private int end_day = 1;
    	
    	
    	// universe settings:
    	
    	// number of symbols you want to be observed by the universe at any given time
    	// updates based on the universe resolution set
    	// recommended universe resolution is daily
    	private int stockCount = 15;
    	
    	// data update resolution
    	// changes how often the data updates and algorithm looks for entry
    	// determines how often the function OnData runs
    	// list of resolutions:
    	// Resolution.Tick; Resolution.Second; Resolution.Minute; Resolution.Hour; Resolution.Daily
    	private readonly Resolution resolution = Resolution.Hour;
    	
    	// portfolio allocation
		// percent of portfolio to allocate to overall (evenly divided between securities)
		private readonly decimal portfolioAllocation = 0.5m;
    	
    	// API settings:
    	
    	// NASDAQ API key:
    	private readonly string apiKey = "9xMzJxNYmHaXix2xTpKt";
    	
    	// Data entry from call:
    	// see documentation for details: https://data.nasdaq.com/data/VOL-us-equity-historical-option-implied-volatilities/documentation
    	private readonly string apiDataEntry = "IvMean90";
    	
    	
    	// algorithm parameters:
    	
		// high IV select (selects top n% of stocks with the highest IV values)
    	// note this value is in decimal form: 0.5 = 50%
    	private readonly decimal highIVSelection = 0.5m;
    	
    	// drawdown percentile
    	// percent of current IV based on the 12 month historical high IV of a security
    	// note this value is in decimal form: 0.5 = 50%
    	private readonly decimal securityIVPercentile = 0.5m;
    	
    	// =================================================================================================================
		
		// creates new universe variable setting
		private List<StockData> universe = new List<StockData>();
		
		// security changes variable
		private SecurityChanges securityChanges = SecurityChanges.None;
		
		// connection object
		private Connection connection;
		
		// month of the current universe
		private int currentMonth = 0;
		
		// determines if universe changed
		private bool load = false;
		
        public override void Initialize()
        {
        	// set start date
            SetStartDate(start_year, start_month, start_day);
            // set end date
            if(enable_end_date)
            	SetEndDate(end_year, end_month, end_day);
            // set starting cash
            SetCash(starting_cash);
            
            // add coarse selection for universe
            AddUniverse(CoarseFilterFunction);
            
            // schedule data load for Monday morning before market open
            Schedule.On(DateRules.Every(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday), 
            		TimeRules.At(09, 30), LoadSymbols);
            
            // define connection
            connection = new Connection(this, apiKey, apiDataEntry);
        }

		// filter based on CoarseFundamental
	    public IEnumerable<Symbol> CoarseFilterFunction(IEnumerable<CoarseFundamental> coarse) {
	    	// check if it is the first of the month, otherwise return current universe
	    	if(Time.Month == currentMonth) {
	    		return Universe.Unchanged;
	    	}
	    	
	    	currentMonth = Time.Month;
	    	load = true;
	    	// returns the highest DollarVolume stocks
	    	// returns "totalNumberOfStocks" amount of stocks
	        return (from stock in coarse
        			orderby stock.DollarVolume descending
        			select stock.Symbol).Take(stockCount);
	    }

        // OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        // Slice object keyed by symbol containing the stock data
        private List<StockData> buffer = new List<StockData>();
        public override void OnData(Slice data) {
        	// if no securities in buffer then return
        	if(buffer.Count() == 0)
        		return;
        		
        	// set holdings for all securities in buffer than meet conditional
        	foreach(StockData sd in buffer)
        		if(sd.percentile < securityIVPercentile && !Securities[sd.ticker].Invested)
        			SetHoldings(sd.ticker, portfolioAllocation / buffer.Count());
        			
        	// clear buffer
        	buffer.Clear();
        }
        
        // Loads all Historical IV data into the symbols for the month
        public void LoadSymbols() {
        	if(!load)
        		return;
        	
        	// load each symbol with historical IV data (note last value is current IV)
        	foreach(StockData sd in universe) {
        		sd.iv = connection.Load(sd.parsed, Time);
        		
        		// if no elements then submit error
        		if(sd.iv.Count() == 0) {
        			Error($"No elements recorded for: {sd.parsed}");
        			continue;
        		}
        		
        		// get and remove last element
        		sd.current = sd.iv[sd.iv.Count() - 1];
        		sd.iv.Remove(sd.iv.Count() - 1);
        		
        		// find max
        		decimal max = sd.iv[0];
        		foreach(decimal d in sd.iv)
        			if(d > max)
        				max = d;
        		
        		// determines if the maximum is faulty
        		if(max == 0.0m)
        			sd.percentile = Decimal.MaxValue;
        		else
        			sd.percentile = sd.current / max;
        		
        		// calculate percentile based on maximum iv
        		//Debug($"Percentile - {sd.parsed} = {sd.percentile} [{sd.current} / {max}]");
        	}
        	
        	// selection amount
        	int n1 = Decimal.ToInt32(Math.Ceiling(highIVSelection * stockCount));
        	var highIVSecurities = (from sd in universe
        				orderby sd.current descending
        				select sd).Take(n1);
        	
        	// push all securities to the buffer
        	foreach(StockData sd in highIVSecurities)
        		buffer.Add(sd);
        		
        	// update universe to loaded status
        	load = false;
        }
        
        // OnSecuritiesChanged runs when the universe updates current securities
        public override void OnSecuritiesChanged(SecurityChanges changes) {
            securityChanges = changes;
            // remove stocks from list that get removed from universe
            foreach (var security in securityChanges.RemovedSecurities) {
            	List<StockData> stockDatas = universe.Where(x=>x.ticker == security.Symbol).ToList();
            	if (stockDatas.Count >= 1) {
            		// check to see if position is open and if so close position
            		if(Portfolio[stockDatas.First().ticker].Invested) {
            			// closes position
            			Liquidate(stockDatas.First().ticker);
            			Log($"Liquidated {stockDatas.First().parsed} on removal.");
            			
            		}
                	Log($"Removed {stockDatas.First().parsed} from universe.");
            		
            		// removes stock from list if it is removed from the universe
                	universe.Remove(stockDatas.First());
            	}
            }
            
            // add new securities to universe list
            foreach(var security in securityChanges.AddedSecurities) {
            	// create StockData variable for security
            	StockData sd = new StockData();
            	sd.ticker = security.Symbol;
            	// removes QC code
            	sd.parsed = sd.ticker.Split(' ')[0];
    			sd.iv = new List<Decimal>();
    			sd.percentile = Decimal.MaxValue;
    			
            	// add stockdata to universe
            	universe.Add(sd);
            	
            	Log($"Added {sd.parsed} to universe.");
            }
        }
		
		// default class containing all ticker information
		public class StockData {
			// stock ticker
			public string ticker = "";
			public string parsed = "";
			
			// historical IV values
			public List<Decimal> iv;
			
			// current iv value
			public decimal current;
			// iv percentile variables
			public decimal percentile;
		}
    }
}
namespace QuantConnect {

	public class Connection {
		
		private readonly QCAlgorithm algorithm;
		
		// sample API call for MSFT on 2021-01-05 as a reference:
		//"https://data.nasdaq.com/api/v3/datasets/VOL/MSFT/data?start_date=2021-01-05&end_date=2021-01-05&api_key=9xMzJxNYmHaXix2xTpKt"
		
		// base url for API calls
		private readonly string url = "https://data.nasdaq.com/api/v3/datasets/VOL/";
		// API to use in call
		private readonly string key;
		// data point entry to look for in call
		private readonly string entry;
		// index of the entry within the call (so we don't scan for it every time)
		private int entryIndex = -1;
		
		// hash of the current dates stored so they don't have to be refreshed
		private string hash = "null";
		// list of the dates stored under the current hash
		private List<DateTime> dates = new List<DateTime>();
		
		// Connection constructor to import the QCAlgorithm, API key, and entry name
		public Connection(QCAlgorithm algorithm, string key, string entry) {
			this.algorithm = algorithm;
			this.key = key;
			this.entry = entry;
		}
		
		// used to retrieve a list of all historical IV values for the prior year
		// calls the object cache should the value already exist for that month
		public List<Decimal> Load(string symbol, DateTime time) {
			// cross reference hash with current time
			// if different then load new dates
			if(hash != $"{time.Year}{time.Month}{time.Day}") {
				hash = $"{time.Year}{time.Month}{time.Day}";
				
				// get all historical dates
				dates = new List<DateTime>();
				DateTime start = new DateTime(time.Year - 1, time.Month, time.Day);
				DateTime end = time;
				for(var dt = start; dt <= end; dt = dt.AddMonths(1))
					dates.Add(dt);
			}
			
			// request each date and add it to a list
			List<Decimal> values = new List<Decimal>();
			foreach(DateTime dt in dates) {
				decimal requested = Request(symbol, dt.Year, dt.Month, dt.Day, time);
				// if invalid date
				if(requested == -1.0m)
					algorithm.Log($"Warning: Unable to retrieve IV for {symbol} on {dt.Year}-{dt.Month}-{dt.Day}.");
				else
					values.Add(requested);
			}
			return values;
		}
		
		// requests the historical IV value for the given date
		// takes the symbol, date, and current date as parameters
		private decimal Request(string symbol, int year, int month, int day, DateTime bounds) {
			// check for cache existing
			string key = Hash(symbol, entry, year, month);
			// if it exists then return stored value
			if (algorithm.ObjectStore.ContainsKey(key)) {
				decimal stored = Convert.ToDecimal(algorithm.ObjectStore.Read(key));
				Print($"Read hash for: {key} = {stored}");
				return stored;
			}
			
			// download and parse data from URL request
			Print($"Requesting: {Format(symbol, year, month, day)}");
			string data = algorithm.Download(Format(symbol, year, month, day));
			// parse by line
			string[] split = data.Split('\n');
			Console.WriteLine(String.Join(",", split));
			
			// if only 2 lines then request the next day
			// if past the DateTime bound then return -1
			if(split.Count() <= 2) {
				DateTime dt;
				// make sure testing valid date
				bool valid = DateTime.TryParse($"{month}/{day + 1}/{year}", out dt);
				
				// if invalid date or out of bounds, return -1
				if(!valid || dt > bounds) {
					Print($"Invalid: {year}-{month}-{day+1} :: {dt > bounds}");
					return -1;
				}
				
				Print($"Requesting Next Day: {year}-{month}-{day+1}");
				// request next day
				return Request(symbol, year, month, day + 1, bounds);
			}
			
			// if the entry index has not been parsed yet, find the index of the value
			if(entryIndex == -1) {
				// parse columns
				string[] columns = split[0].Split(',');
				
				// look for column with matching name
				for(int i = 0; i < columns.Count(); i++) {
					if(columns[i] == entry) {
						entryIndex = i;
						break;
					}
				}
			}
			
			// parse value from data
			string[] values = split[1].Split(',');
			if(values.Count() <= entryIndex) {
				algorithm.Error($"Data issue, [entry points={values.Count()}]");
				return -1.0m;
			}
			//Console.WriteLine(String.Join(",", values));
			decimal value = 0.1m;
			//decimal value = Convert.ToDecimal(values[entryIndex]);
			Print($"Found Value: {year}-{month}-{day} {value}");
			
			// cache found value
			//Console.Write($"Created hash: {Hash(symbol, entry, year, month)}");
			algorithm.ObjectStore.Save(Hash(symbol, entry, year, month), $"{value}");
			
			return value;
		}
		
		private string Format(string symbol, int year, int month, int day) {
			string formatted = url;
			formatted += $"{symbol}/data.csv?start_date={year}-{month}-{day}" +
					$"&end_date={year}-{month}-{day}&api_key={key}";
			return formatted;
		}
		
		private string Hash(string symbol, string entry, int year, int month) {
			return $"{symbol}-{entry}:{year}-{month}";
		}
		
		private void Print(Object obj) {
			//Console.Write(obj);
		}
	}
}