天天看點

【C#】29. VIX指數的實作(使用上證50ETF為原始資料)

這是到現在為止,第一個用C#完整做出來的金融應用,雖然還有許多需要完善的地方,但是架構已經搭建好了。

</pre><pre name="code" class="csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;

namespace VIX
{
    public enum OptionType
    { 
        Call, Put
    }

    static class Read
    {
        //從CSV讀取所有資料到DataTable,認為CSV檔案中第一行為标題
        public static DataTable CSV(string path, List<Type> typeList, bool hasHead = true)
        {
            string line = "";
            DataTable dt = new DataTable();
            FileStream fs = new FileStream(path, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
            //寫入列名稱
            if (hasHead)
            {
                line = sr.ReadLine();
                string[] columnNames = line.Split(',');
                for (int i = 0; i < columnNames.Length; i++)
                {
                    dt.Columns.Add(columnNames[i],typeList[i]); //添加列明以及類型
                }
            }
            
            //開始讀取資料
            line = sr.ReadLine();
            while (line != null)
            {
                dt.Rows.Add(line.Split(','));   //将内容分割成數組,并且寫入行中
                line = sr.ReadLine();
            }
            sr.Close();
            fs.Close();
            return dt;
        }
    }

    static class TypeList
    {
        //格式化Info Table
        public static List<Type> GetInfoTypeList()
        {
            return new List<Type>(new Type[] { typeof(int), typeof(string), typeof(double), typeof(string), typeof(int) });
        }
        //格式化Trading Data的typeList
        public static List<Type> GetTrdTypeList(int numOptions)
        {
            List<Type> trdTypeList = new List<Type>(100);
            for (int i = 0; i < 2 + 2 * numOptions; i++)
            {
                if (i == 0)
                    trdTypeList.Add(typeof(DateTime));  //第一列為時間
                else
                    trdTypeList.Add(typeof(Double));    //第二列是ETF;後面的列都是Bid和Ask
            }
            return trdTypeList;
        }
    }

    static class Filter
    {
        //根據給定的 near / next (int) 類型,将 InfoDT 中的對應的符合條件的行篩選出來,組合成資料表(結果按照行權價格排列)
        static public DataTable NearOrNextInfoDT(DataTable infoDT, int intNearNext)
        {
            return infoDT.AsEnumerable().Select(dr => dr).Where(dr => dr.Field<int>("行權日") == intNearNext).OrderBy(dr => dr.Field<double>("行權價")).CopyToDataTable();
        }

        //給定 Forward Price,找出以它為标準的 out-of-money call 或者 put codes -> Strike
        //Key = code, Value = double [2] { strike, midquote}
        static public void GetDictCodesStrikeMidquote(ref Dictionary<string, double[]> ResultDict, 
            DataTable NearOrNextInfoTB, DataRow TrdDR,double K0, OptionType OpType)
        {
            #region 篩選出執行價格高于或者低于K0的call或者put
            if (OpType==OptionType.Call)
            {
                for (int i = 0; i < ResultDict.Count; i++)
                {
                    if (ResultDict.ElementAt(i).Value[0] <= K0)
                    {
                        ResultDict.Remove(ResultDict.ElementAt(i).Key);
                        i--;
                        ResultDict.OrderByDescending(kvp => kvp.Value[0]);  //按照行權價格降序排序
                    }
                }
                //dictCodeStrike = NearOrNextInfoTB.AsEnumerable().Where(dr => dr.Field<double>("行權價") > K0 && dr.Field<string>("期權類型") == "認購")
                //    .OrderByDescending(dr => dr.Field<double>("行權價")).Select(dr => dr)
                //    .ToDictionary(dr => dr.Field<string>("期權代碼"), dr =>new double[] {dr.Field<double>("行權價"),0});   // Key, Strike, MidQuote = 0
            }
            else if (OpType == OptionType.Put)
            {
                for (int i = 0; i < ResultDict.Count; i++)
                {
                    if (ResultDict.ElementAt(i).Value[0] >= K0)
                    {
                        ResultDict.Remove(ResultDict.ElementAt(i).Key);
                        i--;
                        ResultDict.OrderBy(kvp => kvp.Value[0]);    //按照行權價格升序排列
                    }
                }
                //dictCodeStrike = NearOrNextInfoTB.AsEnumerable().Where(dr => dr.Field<double>("行權價") < K0 && dr.Field<string>("期權類型") == "認沽")
                //    .OrderBy(dr => dr.Field<double>("行權價")).Select(dr => dr)
                //    .ToDictionary(dr => dr.Field<string>("期權代碼"), dr => new double[] { dr.Field<double>("行權價"), 0 });
            }
            #endregion

            Dictionary<string, double[]> tempDict = new Dictionary<string, double[]>();
            bool isLastBidZero = false; //上一個Bid是不是0?
            for (int i = ResultDict.Count - 1; i >= 0; i--)
            {
                if ((double)TrdDR[ResultDict.ElementAt(i).Key + "買一價"] == 0 && isLastBidZero == true)
                {
                    //相鄰連續的兩個put的bid都是0
                    break;
                    
                }
                else if ((double)TrdDR[ResultDict.ElementAt(i).Key + "買一價"] == 0 && isLastBidZero == false)
                {
                    //相鄰第一次出現 0
                    isLastBidZero = true;
                    continue;
                }
                else
                {
                    //符合條件的加入到tempDict
                    tempDict.Add(ResultDict.ElementAt(i).Key,ResultDict.ElementAt(i).Value);
                    isLastBidZero = false;
                }   
            }
            ResultDict = tempDict;
        }
        
        //根據Forward Price傳回K0
        static public double GetK0(ref DataTable InfoTB, double F)
        {
            return InfoTB.AsEnumerable().Select(dr => dr.Field<double>("行權價")).Where(K => K < F).Max();
        }

        //傳回最終需要處理的資料
        static public Dictionary<string, double[]> GetFinalData(DataTable NearOrNextInfoTB, double K0, DataRow TrdDataRow,
            Dictionary<string, double[]> Call_DictCodeStrikeMidquote, Dictionary<string, double[]> Put_DictCodeStrikeMidquote)
        {
            //Add option with strike = K0
            string[] CallandPutAtK0 = NearOrNextInfoTB.AsEnumerable().Where(dr => dr.Field<double>("行權價") == K0).Select(dr => dr.Field<string>("期權代碼")).ToArray();
            double callMid = Calculate.MidQuote(TrdDataRow, CallandPutAtK0[0]);   //trdDT.Rows[trdRow]
            double putMid = Calculate.MidQuote(TrdDataRow, CallandPutAtK0[1]);
            double OpAtK0 = 0.5 * (callMid + putMid);

            //Combine Two Dict!!!
            Dictionary<string, double[]> dict = new Dictionary<string, double[]>();
            for (int i = 0; i < Call_DictCodeStrikeMidquote.Count; i++)
            {
                dict.Add(Call_DictCodeStrikeMidquote.ElementAt(i).Key, Call_DictCodeStrikeMidquote.ElementAt(i).Value);
            }
            dict.Add("OPATM", new double[] { K0, OpAtK0 });
            for (int i = 0; i < Put_DictCodeStrikeMidquote.Count; i++)
            {
                dict.Add(Put_DictCodeStrikeMidquote.ElementAt(i).Key, Put_DictCodeStrikeMidquote.ElementAt(i).Value);
            }
            return dict;
        }
    }

    static class Calculate
    {
        //Get the mid quote for the option wanted in Trade Table row option
        static public double MidQuote(DataRow TrdDataRow, string OptionCode)
        {
            return 0.5 * ((double)TrdDataRow[OptionCode + "賣一價"] + (double)TrdDataRow[OptionCode + "買一價"]);
        }

        //Calculate T1 or T2
        static public double T(DateTime CurrentDate, int intNearNext, double MINSINYEAR)
        {
            DateTime date = DateTime.ParseExact(intNearNext.ToString() + @" 15:00:00", "yyyyMMdd HH:mm:ss", null);
            TimeSpan ts = date - CurrentDate;
            return ts.TotalMinutes / MINSINYEAR;
        }

        //Get temp K, which is used to calculate F, via finding out the smallest difference between call and put
        static public void TempStrike(ref double tempK, ref double minDiff, double callMidQuote, double putMidQuote, int InfoRowNO, DataTable InfoTB)
        {
            double tempDiff = callMidQuote - putMidQuote;
            double absDiff = Math.Abs(tempDiff);

            //Find the lowest difference of | Call - Put |
            if (InfoRowNO == 0)
            {
                minDiff = tempDiff;
                tempK = (double)InfoTB.Rows[InfoRowNO]["行權價"];
            }
            else
            {
                if (Math.Abs(minDiff) > absDiff)
                {
                    minDiff = tempDiff;
                    tempK = (double)InfoTB.Rows[InfoRowNO]["行權價"];
                }
            }
        }

        //Return Sigma^2
        static public double SigmaSquare(Dictionary<string,double[]> DictCodeStrikeMidquote,double T,double R,double F,double K0)
        {
            double contributionSum = 0;
            int numPairs = DictCodeStrikeMidquote.Count;
            double deltaK;

            //将Dict按照執行價格排序
            KeyValuePair<string,double[]>[] dictArr = DictCodeStrikeMidquote.OrderBy(kvp => kvp.Value[0]).ToArray();

            if (numPairs <=1)
            {//如果數量少于或者等于一個,那麼就直接傳回0(可以作為異常)
                return 0;
            }
            else
            {
                for (int i = 0; i < numPairs; i++)
                {
                    //位于頂端和尾端的情況
                    if (i == 0)
                        deltaK = dictArr.ElementAt(i + 1).Value[0] - dictArr.ElementAt(i).Value[0];
                    else if (i == numPairs - 1)
                        deltaK = dictArr.ElementAt(i).Value[0] - dictArr.ElementAt(i - 1).Value[0];
                    else //中間的情況
                        deltaK = 0.5 * (dictArr.ElementAt(i + 1).Value[0] - dictArr.ElementAt(i - 1).Value[0]);
                    //累加各個期權的contribution
                    contributionSum += Contribution(deltaK, dictArr.ElementAt(i).Value[0], R, T, dictArr.ElementAt(i).Value[1]);
                }
                return 2 / T * contributionSum - 1 / T * Math.Pow(F / K0 - 1,2) ;
            }
        }

        //Return contribution of each option in the target range 
        static public double Contribution(double DeltaK, double K, double R, double T, double MidQuote)
        {
            return DeltaK / (K * K) * Math.Exp(R * T) * MidQuote;
        }

        Return the KeyValuePair List ( key is STRIKE, value is MID QUOTE ) for the calculation of sigma square
        //static public List<KeyValuePair<double, double>> DictListOFStrikeAndQuote(double K0, DataTable NearOrNextInfoTB, DataRow TrdDataRow,
        //    Dictionary<double, double> dictPutStrikesAndMidQuote, Dictionary<double, double> dictCallStikesAndMidQuote)
        //{
        //    //Select the strike price immediate below F1 for near
        //    Dictionary<string, double> nearFiltedPuts = Filter.GetDictCodesStrike(NearOrNextInfoTB, TrdDataRow, K0, OptionType.Put); //trdDT.Rows[trdRow]
        //    Dictionary<string, double> nearFiltedCalls = Filter.GetDictCodesStrike(NearOrNextInfoTB, TrdDataRow, K0, OptionType.Call);
        //    //Add option with strike = K0
        //    string[] CallandPutAtK0 = NearOrNextInfoTB.AsEnumerable().Where(dr => dr.Field<double>("行權價") == K0).Select(dr => dr.Field<string>("期權代碼")).ToArray();
        //    double callMid = Calculate.MidQuote(TrdDataRow, CallandPutAtK0[0]); //((double)trdDT.Rows[trdRow][CallandPutAtK0[0] + "賣一價"] + (double)trdDT.Rows[trdRow][CallandPutAtK0[0] + "買一價"]) / 2;
        //    double putMid = Calculate.MidQuote(TrdDataRow, CallandPutAtK0[1]); //((double)trdDT.Rows[trdRow][CallandPutAtK0[1] + "賣一價"] + (double)trdDT.Rows[trdRow][CallandPutAtK0[1] + "買一價"]) / 2;
        //    double OpAtK0 = 0.5 * (callMid + putMid);

        //    var q1 = from r1 in dictPutStrikesAndMidQuote
        //             join r2 in nearFiltedPuts
        //             on r1.Key equals r2.Value
        //             select r1;
        //    var q2 = from r1 in dictCallStikesAndMidQuote
        //             join r2 in nearFiltedCalls
        //             on r1.Key equals r2.Value
        //             select r1;
        //    List<KeyValuePair<double, double>> dict = q1.ToList();
        //    dict.Add(new KeyValuePair<double, double>(K0, OpAtK0));
        //    dict.AddRange(q2.ToList());
        //    dict.OrderBy(kvp => kvp.Key);
        //    return dict;
        //}



    }

    static class Loop
    {
        //the loop in near or next info table
        //使用了Out關鍵字來輸出Call和Put的由 Code, Strike, MidQuote 組成的字典
        public static void inNearOrNextTB(DataTable NearOrNextInfoTB, DataRow TrdDataRow,out double Ktemp, out double minDiff,
            out Dictionary<string, double[]> Call_DictCodeStrikeMidquote, out Dictionary<string, double[]> Put_DictCodeStrikeMidquote)
        {
            //out 必須要初始化
            Call_DictCodeStrikeMidquote = new Dictionary<string, double[]>();
            Put_DictCodeStrikeMidquote = new Dictionary<string, double[]>();
            Ktemp = 0;
            minDiff = 0;

            for (int nearInfoRow = 0; nearInfoRow < NearOrNextInfoTB.Rows.Count - 1; nearInfoRow += 2)
            {
                //對于Info Table裡面的每個 Option
                //1、得到它的代碼 Code
                string callCode = NearOrNextInfoTB.Rows[nearInfoRow]["期權代碼"].ToString();
                string putCode = NearOrNextInfoTB.Rows[nearInfoRow + 1]["期權代碼"].ToString();
                //2、得到 Code對應的中點Quote
                double callMidQuote = Calculate.MidQuote(TrdDataRow, callCode);   //trdDT.Rows[trdRow]
                double putMidQuote = Calculate.MidQuote(TrdDataRow, putCode); //trdDT.Rows[trdRow]

                //添加到字典中,為最終求值做準備
                Call_DictCodeStrikeMidquote.Add((string)NearOrNextInfoTB.Rows[nearInfoRow]["期權代碼"],
                    new double[] {(double)NearOrNextInfoTB.Rows[nearInfoRow]["行權價"], callMidQuote});
                Put_DictCodeStrikeMidquote.Add((string)NearOrNextInfoTB.Rows[nearInfoRow+1]["期權代碼"], 
                    new double[] {(double)NearOrNextInfoTB.Rows[nearInfoRow+1]["行權價"], putMidQuote});
 
                
                //得到 Temp Strike Price
                Calculate.TempStrike(ref Ktemp, ref minDiff, callMidQuote, putMidQuote, nearInfoRow, NearOrNextInfoTB);
            }
        }

    }<pre name="code" class="csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Diagnostics;

namespace VIX
{   
    class Program
    {
        //如果當月合約到期日小于LIMIT,那麼near合約将由原來的next來替換,
        //而原來的next将由未來最近第二期的合約替換
        public const int LIMIT = 0;
        public const double R = 0.1;   //無風險利率
        public const double N365 = 365 * 24 * 60;
        public const int N30 = 30 * 24 * 60;

        public static int current = 20151029;
        public static int near;
        public static int next;
        public static DateTime currentDate;
        public static DateTime nearDate;
        public static DateTime nextDate;
        public static double T1;
        public static double T2;
        public static double NT1;
        public static double NT2;
        public static double F1;    //Forward Price
        public static double F2;
        public static DataTable infoDT;
        public static DataTable trdDT;
        public static List<Type> typeListInfo;  //infoDT表格的列屬性
        public static List<Type> typeListTrd; //trdDT表格的列屬性
        public static double K01;
        public static double K02;
        public static double Ktemp1;
        public static double Ktemp2;
        public static double minDiff1;
        public static double minDiff2;
        

        static void Main(string[] args)
        {
            //傳入期權資訊表
            typeListInfo=TypeList.GetInfoTypeList();
            infoDT = Read.CSV(@"F:\Internship_Projects\VIX\Data\OptionInfo_" + current + @".CSV", typeListInfo);
            //得到期權數量
            int numOption = infoDT.Rows.Count;
            //得到四個經過排序的執行日期
            int[] execDates = infoDT.AsEnumerable().Select(dr => dr.Field<int>("行權日")).Distinct().OrderBy(a => a).ToArray();
            //得到near和next的執行日期
            NearNextExecDate(current, execDates, ref near, ref next);
            //得到near和next對應的info table,并且是按照 執行價格 排序
            DataTable nearInfoTB=Filter.NearOrNextInfoDT(infoDT,near);
            DataTable nextInfoTB=Filter.NearOrNextInfoDT(infoDT,next);
            //根據numOption可以知道trdDT裡面有多少列
            typeListTrd = TypeList.GetTrdTypeList(numOption);
            trdDT = Read.CSV(@"F:\Internship_Projects\VIX\Data\TradingData_" + current + @".CSV", typeListTrd);
           
            /
            //大循環
            for (int trdRow = 0; trdRow < trdDT.Rows.Count -4 ; trdRow++)  //循環至14:56
            {
                //Determine T1 and T2
                currentDate = (DateTime)trdDT.Rows[trdRow][0];
                T1 = Calculate.T(currentDate, near, N365);
                T2 = Calculate.T(currentDate, next, N365);
                NT1 = T1 * N365;
                NT2 = T2 * N365;

                Dictionary<string, double[]> Call_DictCodeStrikeMidquote01;
                Dictionary<string, double[]> Put_DictCodeStrikeMidquote01;
                Dictionary<string, double[]> Call_DictCodeStrikeMidquote02;
                Dictionary<string, double[]> Put_DictCodeStrikeMidquote02;

                //Determine the K0
                //near:得到所有near時間到期的Call和Put
                Loop.inNearOrNextTB(nearInfoTB, trdDT.Rows[trdRow], out Ktemp1, out minDiff1,
                    out Call_DictCodeStrikeMidquote01, out Put_DictCodeStrikeMidquote01);
                Loop.inNearOrNextTB(nextInfoTB, trdDT.Rows[trdRow], out Ktemp2, out minDiff2,
                    out Call_DictCodeStrikeMidquote02, out Put_DictCodeStrikeMidquote02);

                //calculate the near forward price 
                F1 = Ktemp1+ Math.Exp(R * T1) * minDiff1;
                F2 = Ktemp2 + Math.Exp(R * T2) * minDiff2;

                //Get the real K0 according to F
                K01 = Filter.GetK0(ref infoDT, F1);
                K02 = Filter.GetK0(ref infoDT, F2);

                //Select the strike price immediate below F1 for near
                Filter.GetDictCodesStrikeMidquote(ref Put_DictCodeStrikeMidquote01, nearInfoTB, trdDT.Rows[trdRow], K01, OptionType.Put);
                Filter.GetDictCodesStrikeMidquote(ref Call_DictCodeStrikeMidquote01, nearInfoTB, trdDT.Rows[trdRow], K01, OptionType.Call);
                Filter.GetDictCodesStrikeMidquote(ref Put_DictCodeStrikeMidquote02, nextInfoTB, trdDT.Rows[trdRow], K02, OptionType.Put);
                Filter.GetDictCodesStrikeMidquote(ref Call_DictCodeStrikeMidquote02, nextInfoTB, trdDT.Rows[trdRow], K02, OptionType.Call);

                Dictionary<string, double[]> dict1 = Filter.GetFinalData(nearInfoTB, K01, trdDT.Rows[trdRow], Call_DictCodeStrikeMidquote01, Put_DictCodeStrikeMidquote01);
                Dictionary<string, double[]> dict2 = Filter.GetFinalData(nextInfoTB, K02, trdDT.Rows[trdRow], Call_DictCodeStrikeMidquote02, Put_DictCodeStrikeMidquote02);

                double SigmaSquare1 = Calculate.SigmaSquare(dict1,T1,R,F1, K01);
                double SigmaSquare2 = Calculate.SigmaSquare(dict2, T2, R, F2, K02);
                
                double tmp = T1*SigmaSquare1*(NT2-N30)/(NT2-NT1)+T2*SigmaSquare2*(N30-NT1)/(NT2-NT1);
                double Vix = 100 * Math.Sqrt(N365 / N30 * tmp);
                Debug.WriteLine(Vix);

            }//大循環
        }

        //給定currentDate,得到near和next
        static void NearNextExecDate(int current,int[] sortedExecDates, ref int near, ref int next)
        {
            //如果currentDate離第一個執行日期的距離在LIMIT之外
            if (sortedExecDates[0] - current >= LIMIT)
            {
                near = sortedExecDates[0];
                next = sortedExecDates[1];
                return;
            }
            else 
            {
                near = sortedExecDates[1];
                next = sortedExecDates[2];
                return;
            }
        }

        
    }
}
           

}