Overall Statistics |
Total Trades 3 Average Win 7.36% Average Loss 0% Compounding Annual Return -2.441% Drawdown 10.000% Expectancy 0 Net Profit -0.400% Sharpe Ratio 0.078 Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.216 Beta -0.481 Annual Standard Deviation 0.314 Annual Variance 0.099 Information Ratio -1.062 Tracking Error 0.353 Treynor Ratio -0.051 Total Fees $0.00 |
namespace QuantConnect { /// <summary> /// Framework for backtesting algorithms on theoretical options // by: Jean-Paul van Brakel (jeanpaulvbrakel@gmail.com) /// </summary> public class OptionFrameWork : QCAlgorithm { Resolution _res = Resolution.Daily; string option = "SPY"; string[] options = {"CALL", "PUT"}; string _maturityType = "MONTHLY"; // type of option maturities (also: "WEEKLY") int _nof_strikes = 3; // number of option strikes that you want to include int _nof_maturities = 3; // number of option maturities that you want to include public override void Initialize() { SetStartDate(2013, 01, 01); SetEndDate(2013, 02, 28); // initialise option settings //OptionSettings.Initialize(this); // ^this will automatically download the interest rate from QUANDL // if you want to speed up your backtest, use a fixed interest rate instead: OptionSettings.Initialize(this, 0.05M); // we can also pass a dividend-yield parameter to change the theoretical prices: // OptionSettings.Initialize(this, 0.05M, 0.01M); // create 3 in-the-money and 3-out-of-the-money options for 10 different maturities for (int i = -_nof_strikes; i <= _nof_strikes; i++) { for (int m = 1; m <= _nof_maturities; m++) { foreach (string o in options) { AddData<Option>(option+"."+o+"."+i+"."+m, _res); OptionSettings.RegisterOption(option+"."+o+"."+i+"."+m,i,m,_maturityType); Securities[option+"."+o+"."+i+"."+m].Exchange = new EquityExchange(); } } } } public override void OnData(Slice slice) { if (!Portfolio.HoldStock) { // we can order our options like any other asset // option contracts go per 100 (so multiply with 100!) Order("SPY.CALL.1.2", 80*100); // ^this order did the following: // > order 80 call contracts with the first in-the-money strike // and the second upcoming maturity } if (slice.Time == new DateTime(2013, 02, 22)) { // can also access all greeks. For example: if (slice.ContainsKey("SPY.CALL.1.2")) OptionSettings.PrintAllGreeksOf(slice.Get<Option>("SPY.CALL.1.2")); // or, other example: Console.WriteLine("RHO:\t"+slice.Get<Option>("SPY.CALL.-2.1").Rho); } } } }
using System.Linq; namespace QuantConnect { /// <summary> /// Unfinished code to backtest using theoretical options /// this ensures semi-realistic backtesting because theoretical options /// feature the time-value-of-money. However, volatility is estimated from /// the underlying which is not always a good approximation. /// /// Mind you: this code is unfinished (so I'm sorry if it's messy) /// composed by: Jean-Paul van Brakel /// jeanpaulvbrakel@gmail.com /// /// </summary> public class Option : BaseData { // define derived properties public string optionType { get; set; } public decimal Delta; public decimal Gamma; public decimal Vega; public decimal Theta; public decimal Rho; public decimal Volga; public decimal Vanna; public decimal Charm; public decimal Color; public decimal DualDelta; public decimal DualGamma; // override so we can properly set it from the tradebar public override DateTime EndTime { get; set; } /// <summary> /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. /// </summary> /// <param name="config">Subscription data config setup object</param> /// <param name="line">Line of the source document</param> /// <param name="date">Date of the requested data</param> /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param> /// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns> public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { OptionSpecs specs = OptionSettings.specs[config.Symbol]; try { config = HackSubscriptionDataConfig(config); // use the tradebar reader implementation to read QC csv files var tradebar = (TradeBar)new TradeBar().Reader(config, line, date, isLiveMode); // get name of option string OptionName = config.Symbol+"."+specs.OptionType; // update price of option (before calculation of Black-Scholes price) OptionSettings.specs[OptionName].UpdatePrice(tradebar.Close); // get nearest value for the risk-free rate var keys = new List<DateTime>(OptionSettings.RFR.Keys); var index = keys.BinarySearch(tradebar.EndTime); KeyValuePair<DateTime, decimal> rfr = OptionSettings.RFR.ElementAt((int)Math.Abs(index)-1); // update last known price to this price if (specs.LastTwoWeeks.Count == 0) { specs.LastTwoWeeks.Add(tradebar.EndTime.Date, tradebar.Close); } else if (tradebar.Time.Date > specs.LastTwoWeeks.Keys.Last().Date) { specs.LastTwoWeeks.Add(tradebar.EndTime.Date, tradebar.Close); while (specs.LastTwoWeeks.Count > 20) specs.LastTwoWeeks.Remove(specs.LastTwoWeeks.Keys.First()); } else if (tradebar.Time.Date == specs.LastTwoWeeks.Keys.Last()) { specs.LastTwoWeeks[specs.LastTwoWeeks.Keys.Last()] = tradebar.Close; } OptionSettings.specs[OptionName].LastTwoWeeks = specs.LastTwoWeeks; double[] ltw = Array.ConvertAll(specs.LastTwoWeeks.Values.ToArray(), x => (double)x); // check if current time is larger than maturity if (tradebar.Time.Date > specs.MaturityDate.Date) { // liquidate current option OptionSettings.Algorithm.Liquidate(OptionName); // find new option for this maturity and strike (according to new price) OptionSettings.specs[OptionName].ChooseOption(tradebar.EndTime, tradebar.Close); } else { // update option maturity (re-calculate) OptionSettings.specs[OptionName].UpdateMaturity(tradebar.Time.Date); } // Checking stuff: //Console.WriteLine(config.Symbol+"."+specs.OptionType+"\t\tMaturity:\t"+(double)specs.Maturity+"\t\t"+specs.MaturityDaysAway+"\t"+tradebar.Time+"\t"+specs.MaturityDate); //Console.WriteLine("Volatility:\t"+BlackScholes.HistoricalVolatility(ltw)); //Console.WriteLine("Close:\t\t"+(double)tradebar.Close); //Console.WriteLine("Strike:\t\t"+(double)specs.Strike); //Console.WriteLine("RFR:\t\t"+(double)rfr.Value); //Console.WriteLine("Maturity:\t"+(double)specs.Maturity); //Console.WriteLine("Dividend:\t"+(double)specs.DividendYield+"\n\n"); // reload specs specs = OptionSettings.specs[OptionName]; // now that we have all needed variables, can can calculate the theoretical option price // we do this using the Black-Scholes formula by Fischer Black and Myron Scholes (1973) // The Pricing of Options and Corporate Liabilities // The Journal of Political Economy, Vol. 81, No. 3, pp. 637-654 decimal BlackScholesPrice = 0M; double Volatility = BlackScholes.HistoricalVolatility(ltw); if (specs.OptionType.Contains("CALL")) { // calculate Black-Scholes price for CALL option: BlackScholesPrice = (decimal) BlackScholes.blsCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Delta = (decimal) BlackScholes.blsdeltaCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Gamma = (decimal) BlackScholes.blsgamma((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Vega = (decimal) BlackScholes.blsvega((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Theta = (decimal) BlackScholes.blsthetaCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Rho = (decimal) BlackScholes.blsrhoCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Volga = (decimal) BlackScholes.blsvolga((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Vanna = (decimal) BlackScholes.blsvanna((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Charm = (decimal) BlackScholes.blscharmCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Color = (decimal) BlackScholes.blscolor((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); DualDelta = (decimal) BlackScholes.blsdualdeltaCall((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); DualGamma = (decimal) BlackScholes.blsdualgamma((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); } else if (specs.OptionType.Contains("PUT")) { // calculate Black-Scholes price for PUT option: BlackScholesPrice = (decimal) BlackScholes.blsPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Delta = (decimal) BlackScholes.blsdeltaPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Gamma = (decimal) BlackScholes.blsgamma((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Vega = (decimal) BlackScholes.blsvega((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Theta = (decimal) BlackScholes.blsthetaPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Rho = (decimal) BlackScholes.blsrhoPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Volga = (decimal) BlackScholes.blsvolga((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Vanna = (decimal) BlackScholes.blsvanna((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Charm = (decimal) BlackScholes.blscharmPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); Color = (decimal) BlackScholes.blscolor((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); DualDelta = (decimal) BlackScholes.blsdualdeltaPut((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); DualGamma = (decimal) BlackScholes.blsdualgamma((double)tradebar.Close,Volatility,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,(double)specs.DividendYield); } BlackScholesPrice = Math.Max(Math.Round(BlackScholesPrice,2), (decimal) 0.01); Symbol = OptionName; Time = tradebar.Time; EndTime = tradebar.EndTime; // save BlackScholesPrice as the value of the option Value = BlackScholesPrice; // we can now return the option return this; } catch { return null; } } /// <summary> /// Return the URL string source of the file. This will be converted to a stream /// </summary> /// <param name="config">Configuration object</param> /// <param name="date">Date of this source file</param> /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param> /// <returns>String URL of source file.</returns> public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { // the source of our data is the same as normal trade bar data config = HackSubscriptionDataConfig(config); return new TradeBar().GetSource(config, date, isLiveMode); } /// <summary> /// Reroutes the configuration to an equity trade bar /// </summary> private static SubscriptionDataConfig HackSubscriptionDataConfig(SubscriptionDataConfig config) { // removing the option to find the symbol of the underlying string symbol = config.Symbol.Value; string ticker = symbol.Substring(0, symbol.IndexOf(".")); // we need to override the configuration produced by the AddData method so it will get the // data from the same place we get equity tradebar equity data config = new SubscriptionDataConfig(typeof(TradeBar), SecurityType.Equity, ticker, // reuse all the other options so we get the right data config.Resolution, config.Market, config.TimeZone, config.FillDataForward, config.ExtendedMarketHours, config.IsInternalFeed, config.IsCustomData ); return config; } } /// <summary> /// I'm sorry Jared, I didn't write any documentation/ comments for this code /// I hope you can figure it out. If not, you can always contact me. /// </summary> public static class BlackScholes { private static double[] nsia = { 2.50662823884, -18.61500062529, 41.39119773534, -25.44106049637 }; private static double[] nsib = { -8.4735109309, 23.08336743743, -21.06224101826, 3.13082909833 }; private static double[] nsic = { 0.3374754822726147, 0.9761690190917186, 0.1607979714918209, 0.0276438810333863, 0.0038405729373609, 0.0003951896511919, 0.0000321767881768, 0.0000002888167364, 0.0000003960315187 }; //cumulative normal distribution function private static double CND(double X) { double L = 0.0; double K = 0.0; double dCND = 0.0; const double a1 = 0.31938153; const double a2 = -0.356563782; const double a3 = 1.781477937; const double a4 = -1.821255978; const double a5 = 1.330274429; L = Math.Abs(X); K = 1.0 / (1.0 + 0.2316419 * L); dCND = 1.0 - 1.0 / Math.Sqrt(2 * Convert.ToDouble(Math.PI.ToString())) * Math.Exp(-L * L / 2.0) * (a1 * K + a2 * K * K + a3 * Math.Pow(K, 3.0) + a4 * Math.Pow(K, 4.0) + a5 * Math.Pow(K, 5.0)); if (X < 0) { return 1.0 - dCND; } else { return dCND; } } //function phi private static double phi(double x) { double phi = 0.0; phi = Math.Exp(-x * x / 2) / Math.Sqrt(2 * Math.PI); return phi; } public static double NORMSINV(double probability) { double r = 0; double x = 0; x = probability - 0.5; if (Math.Abs(x) < 0.42) { r = x * x; r = x * (((nsia[3] * r + nsia[2]) * r + nsia[1]) * r + nsia[0]) / ((((nsib[3] * r + nsib[2]) * r + nsib[1]) * r + nsib[0]) * r + 1); return r; } r = probability; if (x > 0) r = 1 - probability; r = Math.Log(-Math.Log(r)); r = nsic[0] + r * (nsic[1] + r * (nsic[2] + r * (nsic[3] + r * (nsic[4] + r * (nsic[5] + r * (nsic[6] + r * (nsic[7] + r * nsic[7]))))))); return Math.Abs(r); } public static double NORMINV(double probability, double mean, double standard_deviation) { return (NORMSINV(probability) * standard_deviation + mean); } public static double NORMINV(double probability, double[] values) { return NORMINV(probability, Mean(values), StandardDeviation(values)); } public static double Mean(double[] values) { double tot = 0; foreach (double val in values) tot += val; return (tot / values.Length); } public static double StandardDeviation(double[] values) { return Math.Sqrt(Variance(values)); } public static double Variance(double[] values) { double m = Mean(values); double result = 0; foreach (double d in values) result += Math.Pow((d - m), 2); return (result / values.Length); } // calculate centered historical volatility over the last two weeks // derived from the annualized log difference of current price and last price public static double HistoricalVolatility(double[] historicalPrices) { double[] logDifferences = new double[historicalPrices.Length-1]; for (int i = 1; i < historicalPrices.Length; i++) { logDifferences[i-1] = Math.Log(historicalPrices[i]/historicalPrices[i-1]); } double meanLogDifferences = Mean(logDifferences); double sumSquaredVariances = 0; for (int i = 0; i < logDifferences.Length; i++) { logDifferences[i] = Math.Pow(logDifferences[i] - meanLogDifferences,2); sumSquaredVariances += Math.Pow(logDifferences[i] - meanLogDifferences,2); } double sqrtAverageVariance = Math.Sqrt(sumSquaredVariances/logDifferences.Length); double annualizedVariance = sqrtAverageVariance*Math.Sqrt(252); return annualizedVariance; } //Call pricer public static double blsCall(double Price, double Volatility, double Strike, double Rate, double Time, double Yield) { double d1 = 0.0; double d2 = 0.0; double Call = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); Call = Price * Math.Exp(-Yield * Time) * CND(d1) - Strike * Math.Exp(-Rate * Time) * CND(d2); return Call; } //Put pricer public static double blsPut(double Price, double Volatility, double Strike, double Rate, double Time, double Yield) { double d1 = 0.0; double d2 = 0.0; double Put = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); Put = Strike * Math.Exp(-Rate * Time) * CND(-d2) - Price * Math.Exp(-Yield * Time) * CND(-d1); return Put; } //delta for Call public static double blsdeltaCall(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); return Math.Exp(-Yield * Time) * CND(d1); } //delta for Put public static double blsdeltaPut(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); return Math.Exp(-Yield * Time) * CND(d1) - 1; } //gamma is the same for Put and Call public static double blsgamma(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); return Math.Exp(-Yield * Time) * phi(d1) / (Price * Volatility * Math.Sqrt(Time)); } //vega is the same for Put and Call public static double blsvega(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); return Price * Math.Exp(-Yield * Time) * phi(d1) * Math.Sqrt(Time); } //theta for Call public static double blsthetaCall(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); return -Math.Exp(-Yield * Time) *( Price * phi(d1) * Volatility / (2 * Math.Sqrt(Time))) - Rate * Strike * Math.Exp(-Rate * Time) * CND(d2) + Yield * Price * Math.Exp(-Yield * Time) * CND(d1); } //theta for Put public static double blsthetaPut(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); return -Math.Exp(-Yield * Time) *( Price * phi(d1) * Volatility / (2 * Math.Sqrt(Time))) + Rate * Strike * Math.Exp(-Rate * Time) * CND(-d2) - Yield * Price * Math.Exp(-Yield * Time) * CND(-d1); } //rho for Call public static double blsrhoCall(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); return Strike * Time * Math.Exp(-Rate * Time) * CND(d2); } //rho for Put public static double blsrhoPut(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); return -Strike * Time * Math.Exp(-Rate * Time) * CND(-d2); } //volga is the same for Call and Put public static double blsvolga(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); return Price * Math.Exp(-Yield * Time) * phi(d1) * Math.Sqrt(Time) * d1 * d2 / Volatility; } //vanna is the same for Call and Put public static double blsvanna(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double vanna = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); vanna = -Math.Exp(-Yield * Time) * phi(d1) * d2 / Volatility; return vanna; } //charm for Call public static double blscharmCall(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double charmC = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); charmC = -Yield * Math.Exp(-Yield * Time) * CND(d1) + Math.Exp(-Yield * Time) * phi(d1) * (2 * (Rate - Yield) * Time - d2 * Volatility * Math.Sqrt(Time)) / (2 * Time * Volatility * Math.Sqrt(Time)); return charmC; } //charm for Put public static double blscharmPut(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double charmP = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); charmP = Yield * Math.Exp(-Yield * Time) * CND(-d1) - Math.Exp(-Yield * Time) * phi(d1) * (2 * (Rate - Yield) * Time - d2 * Volatility * Math.Sqrt(Time)) / (2 * Time * Volatility * Math.Sqrt(Time)); return charmP; } //color is the same for Call and Put public static double blscolor(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double color = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); color = -Math.Exp(-Yield * Time) * (phi(d1) / (2 * Price * Time * Volatility * Math.Sqrt(Time))) * (2 * Yield * Time + 1 + (2 * (Rate - Yield) * Time - d2 * Volatility * Math.Sqrt(Time)) * d1 / (2 * Time * Volatility * Math.Sqrt(Time))); return color; } //dual delta for Call public static double blsdualdeltaCall(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double ddelta = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); ddelta = -Math.Exp(-Rate * Time) * CND(d2); return ddelta; } //dual delta for Put public static double blsdualdeltaPut(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double ddelta = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); ddelta = Math.Exp(-Rate * Time) * CND(-d2); return ddelta; } //dual gamma is the same for Call and Put public static double blsdualgamma(double Price, double Strike, double Rate, double Time, double Volatility, double Yield) { double d1 = 0.0; double d2 = 0.0; double dgamma = 0.0; d1 = (Math.Log(Price / Strike) + (Rate - Yield + Volatility * Volatility / 2.0) * Time) / (Volatility * Math.Sqrt(Time)); d2 = d1 - Volatility * Math.Sqrt(Time); dgamma = Math.Exp(-Rate * Time) * phi(d2) / (Strike * Volatility * Math.Sqrt(Time)); return dgamma; } } }
using System.Net; namespace QuantConnect { public static class OptionSettings { public static Dictionary<string, OptionSpecs> specs = new Dictionary<string, OptionSpecs>(); public static SortedDictionary<DateTime, decimal> RFR = new SortedDictionary<DateTime, decimal>(); public static QCAlgorithm Algorithm; /// <summary> /// Initialises the option framework /// </summary> public static void Initialize(QCAlgorithm algorithm, decimal r = 0M) { Algorithm = algorithm; if (r != 0) // if user wants to use a fixed interest rate { RFR.Add(new DateTime(1500,1,1), r); RFR.Add(new DateTime(3000,1,1), r); return; } // download 3-Month Treasury Bill: Secondary Market Rate from QUANDL string rfr = ""; using (WebClient wc = new WebClient()) { rfr = wc.DownloadString("https://www.quandl.com/api/v3/datasets/FRED/DTB3.csv"); } if (string.IsNullOrEmpty(rfr)) { throw new System.NullReferenceException("Cannot download data: QUANDL cannot be reached"); } else { string[] lines = rfr.Split(new string[] { "\n" }, StringSplitOptions.None); foreach (string l in lines) { if (l.Contains("DATE") || string.IsNullOrEmpty(l)) { continue; } string[] values = l.Split(','); DateTime d = DateTime.ParseExact(values[0], "yyyy-MM-dd", CultureInfo.InvariantCulture); decimal v = Decimal.Parse(values[1]); RFR.Add(d, v); } } // use LINQ to reverse the order of the dictionary RFR.Keys.Reverse(); } /// <summary> /// Registers option by creating a new OptionSpecs for this option /// </summary> public static void RegisterOption(string name, int i, int m, string maturityType = "MONTHLY", decimal dividendYield = 0M) { string type = name.Substring(name.IndexOf('.')+1).ToUpper(); if (name.ToUpper().Contains("CALL")) { specs.Add(name, new OptionSpecs(type, Algorithm.StartDate, i, m, maturityType, dividendYield)); } else if (name.ToUpper().Contains("PUT")) { specs.Add(name, new OptionSpecs(type, Algorithm.StartDate, i, m, maturityType, dividendYield)); } else { throw new System.ArgumentException("This is not a valid option type", type); } } /// <summary> /// Prints the greeks (makes no sense if you don't have the ACTUAL price of the option) /// </summary> public static void PrintAllGreeksOf(Option o) { Console.WriteLine("Option:\t"+o.Symbol); Console.WriteLine("---------------------"); Console.WriteLine("Value:\t\t"+o.Value); // save all option greeks Console.WriteLine("Delta:\t\t"+o.Delta); Console.WriteLine("Gamma:\t\t"+o.Gamma); Console.WriteLine("Vega:\t\t"+o.Vega); Console.WriteLine("Theta:\t\t"+o.Theta); Console.WriteLine("Rho:\t\t"+o.Rho); Console.WriteLine("Volga:\t\t"+o.Volga); Console.WriteLine("Vanna:\t\t"+o.Vanna); Console.WriteLine("Charm:\t\t"+o.Charm); Console.WriteLine("Color:\t\t"+o.Color); Console.WriteLine("Dual Delta:\t"+o.DualDelta); Console.WriteLine("Dual Gamma:\t"+o.DualGamma); Console.WriteLine("---------------------"); } } public class OptionSpecs { public SortedDictionary<DateTime, decimal> LastTwoWeeks { get; set; } public bool NoStrike { get; set; } public decimal Price { get; set; } public string OptionType { get; set; } public string MaturityType { get; set; } public decimal Maturity { get; set; } public int MaturityDaysAway { get; set; } public DateTime MaturityDate { get; set; } public decimal Strike { get; set; } public int StrikesAway { get; set; } public decimal DividendYield { get; set; } /// <summary> /// Initialises the option specs /// </summary> public OptionSpecs(string type, DateTime Now, int strikesAway, int maturityDaysAway, string maturityType, decimal dividendYield) { this.Price = -1; this.OptionType = type; this.LastTwoWeeks = new SortedDictionary<DateTime, decimal>(); this.MaturityDaysAway = maturityDaysAway; this.MaturityType = maturityType.ToUpper(); this.DividendYield = dividendYield; this.StrikesAway = strikesAway; // find corresponding strike FindAndSetStrike(strikesAway, Price); // find corresponding maturity FindAndSetMaturity(Now); } /// <summary> /// Calculates a new option based on the properties of the class (which strike away? which maturity away?) /// </summary> public void ChooseOption(DateTime Now, decimal Price) { // find corresponding strike FindAndSetStrike(StrikesAway, Price); // find corresponding maturity FindAndSetMaturity(Now); } public void UpdatePrice(decimal price) { this.Price = price; } /// <summary> /// Update maturity given the current time /// </summary> public void UpdateMaturity(DateTime Now) { this.Maturity = (decimal)CountWeekDays(Now, this.MaturityDate)/365M; this.MaturityDaysAway = CountWeekDays(Now, this.MaturityDate); } /// <summary> /// Finds a given maturity, given the number of maturities away. /// This builds a grid of the 'third fridays of the months' and chooses the MaturityDaysAway'th one. /// </summary> public void FindAndSetMaturity(DateTime Now) { // make sure that the maturity is at least 'maturityDaysAway' away DateTime MinimalMaturity = AddBusinessDays(Now, MaturityDaysAway); DateTime NearestMaturity = new DateTime(1500, 1, 1); DateTime TempDate = new DateTime(MinimalMaturity.Year, MinimalMaturity.Month, 1); if (MaturityType.Equals("MONTHLY")) { // if monthly option, search for next third friday of the month TempDate = TempDate.AddMonths(-1); // prepare for loop while (MinimalMaturity.Date >= NearestMaturity.Date) { // add month TempDate = TempDate.AddMonths(1); // find first friday of this month while (TempDate.DayOfWeek != DayOfWeek.Friday) { TempDate = TempDate.AddDays(1); } // add two weeks TempDate = TempDate.AddDays(14); NearestMaturity = TempDate; TempDate = new DateTime(TempDate.Year, TempDate.Month, 1); } } else if (MaturityType.Equals("WEEKLY")) { // if weekly option: search for next friday int daysUntilFriday = ((int) DayOfWeek.Friday - (int) MinimalMaturity.DayOfWeek + 7) % 7; NearestMaturity = MinimalMaturity.AddDays(daysUntilFriday); } else { throw new System.ArgumentException("This is not a valid option maturity type", MaturityType); } this.Maturity = ((decimal)CountWeekDays(Now, NearestMaturity)) / 365M; this.MaturityDate = NearestMaturity; this.MaturityDaysAway = CountWeekDays(Now, NearestMaturity); } /// <summary> /// find nearest strike price (grid of 1 dollars below a stock price of 250. Otherwise, grid is per 5 dollars). /// </summary> public void FindAndSetStrike(int strikesAway, decimal price) { decimal strike = 0.0M; if (price <= 250) { if (strikesAway >= 0) { strike = Math.Ceiling((price+((decimal)strikesAway*1.0M))/1.0M)*1.0M; } else { strike = Math.Floor((price+((decimal)strikesAway*1.0M))/1.0M)*1.0M; } } else { if (strikesAway >= 0) { strike = Math.Ceiling((price+((decimal)strikesAway*5.0M))/5.0M)*5.0M; } else { strike = Math.Floor((price+((decimal)strikesAway*5.0M))/5.0M)*5.0M; } } this.Strike = strike; } /// <summary> /// Counts the number of week days (business days). Not so robust. Better to use real calendar. /// </summary> public int CountWeekDays(DateTime d0, DateTime d1) { int ndays = 1 + Convert.ToInt32((d1 - d0).TotalDays); int nsaturdays = (ndays + Convert.ToInt32(d0.DayOfWeek)) / 7; return ndays - 2 * nsaturdays - (d0.DayOfWeek == DayOfWeek.Sunday ? 1 : 0) + (d1.DayOfWeek == DayOfWeek.Saturday ? 1 : 0); } /// <summary> /// Adds business days to any given input date. /// </summary> public DateTime AddBusinessDays(DateTime date, int addDays) { while (addDays != 0) { date = date.AddDays(Math.Sign(addDays)); if ((date.DayOfWeek != DayOfWeek.Saturday)&&(date.DayOfWeek != DayOfWeek.Sunday)) { addDays = addDays - Math.Sign(addDays); } } return date; } } }