| Overall Statistics |
|
Total Trades 3 Average Win 10.24% Average Loss 0% Compounding Annual Return 5.035% Drawdown 8.600% Expectancy 0 Net Profit 0.800% Sharpe Ratio 0.312 Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.293 Beta -0.492 Annual Standard Deviation 0.311 Annual Variance 0.097 Information Ratio -0.862 Tracking Error 0.35 Treynor Ratio -0.197 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,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Delta = (decimal) BlackScholes.blsdeltaCall((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Gamma = (decimal) BlackScholes.blsgamma((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Vega = (decimal) BlackScholes.blsvega((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Theta = (decimal) BlackScholes.blsthetaCall((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Rho = (decimal) BlackScholes.blsrhoCall((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Volga = (decimal) BlackScholes.blsvolga((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Vanna = (decimal) BlackScholes.blsvanna((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Charm = (decimal) BlackScholes.blscharmCall((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Color = (decimal) BlackScholes.blscolor((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
DualDelta = (decimal) BlackScholes.blsdualdeltaCall((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
DualGamma = (decimal) BlackScholes.blsdualgamma((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
} else if (specs.OptionType.Contains("PUT")) {
// calculate Black-Scholes price for PUT option:
BlackScholesPrice = (decimal) BlackScholes.blsPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Delta = (decimal) BlackScholes.blsdeltaPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Gamma = (decimal) BlackScholes.blsgamma((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Vega = (decimal) BlackScholes.blsvega((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Theta = (decimal) BlackScholes.blsthetaPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Rho = (decimal) BlackScholes.blsrhoPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Volga = (decimal) BlackScholes.blsvolga((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Vanna = (decimal) BlackScholes.blsvanna((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Charm = (decimal) BlackScholes.blscharmPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
Color = (decimal) BlackScholes.blscolor((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
DualDelta = (decimal) BlackScholes.blsdualdeltaPut((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(double)specs.DividendYield);
DualGamma = (decimal) BlackScholes.blsdualgamma((double)tradebar.Close,(double)specs.Strike,(double)rfr.Value,(double)specs.Maturity,Volatility,(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 Strike, double Rate, double Time, double Volatility, 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 Strike, double Rate, double Time, double Volatility, 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;
}
}
}