顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

2013年12月13日 星期五

QuoteManager 回補期交所行情資料(C#)

 

下單系統有時候難免會出狀況,行情漏接是偶爾會碰到的問題,這時事後就要處理行情的回補。如果是有訂閱行情資料的可以線上回補即可,但沒有的呢? 可能要請別人匯出文字檔來回補,但就是有點麻煩。其實偶爾漏個一兩天,還是有簡便方法可以自己處理的,就是直接拿期交所行情來回補。

 

期交所的網站有過去三十日的行情資料提供下載,如圖。

 

download

 

我們下載之後可以得到一個ZIP檔,解壓縮後得到RPT檔,其實就是文字檔,裡面包含當日所有期貨商品的TICK資料,不過我們僅需要台指期的資料而已,這時就要作些小小處理,先看RPT檔的格式。

 

rpt

 

格式為日期,商品,月份,時間,價位,成交量,還有跨月價差用的欄位。而我們想要轉資料進的QuoteManager,要求的TICK檔格式為日期,時間,價位,成交量,如下圖。

 

tick format

 

所以可以利用一個小程式,將格式轉換輸出文字檔就OK了,如下圖,一個來源的RPT和一個輸出的TXT,兩個檔案的路徑和一個執行按鈕。

 

form

 

程式很短,我們只需要兩個輸出入串流,

FileInfo source = new FileInfo(textBox1.Text);
StreamReader sr = source.OpenText();

FileInfo destination = new FileInfo(textBox2.Text);
StreamWriter sw = destination.CreateText();

 

設定來源及目的的檔案路徑,然後處理來源的期交所資料,

其中要處理的部分有:

1. 只要台指期近月

2.成交量要除以2

3.處理成QM的TICK格式(有符號/、: 這個)

 

關於期交所資料每日下載的排程設定可以參考前文,取得期交所每日行情資料 。

程式碼如下,歡迎參考指教。

 
namespace RPTtoQM
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

FileInfo source = new FileInfo(textBox1.Text);
StreamReader sr = source.OpenText();

FileInfo destination = new FileInfo(textBox2.Text);
StreamWriter sw = destination.CreateText();

String tmps = "";
String month = "";
int qty=0;

while( sr.Peek()>=0 )
{
tmps = sr.ReadLine();

if ( tmps.Length>=10 && tmps.Substring(9, 2) == "TX")
{
if (month == "") { month = tmps.Substring(17, 6); }

if (tmps.Substring(17, 6) == month && tmps.Substring(23, 4)==" ")
{
if (tmps.Length == 50) { qty = Int32.Parse(tmps.Substring(41, 3)) /2 ; }
else if (tmps.Length == 49) { qty = Int32.Parse(tmps.Substring(41, 2)) / 2; }
else if (tmps.Length == 48) { qty = Int32.Parse(tmps.Substring(41, 1)) / 2; }

sw.Write(tmps.Substring(0, 4) + @"/" + tmps.Substring(4, 2) + @"/" + tmps.Substring(6, 2) +","
+ tmps.Substring(29, 2) + @":" + tmps.Substring(31, 2) + @":" + tmps.Substring(33, 2) + ","
+ tmps.Substring(36, 4) + "," + qty.ToString() +
"\r\n");
}
}

}

sw.Flush();
sw.Close();
sr.Close();

}
}
}

 

 

 

2013年12月11日 星期三

選擇權GREEKS實作(C#)

 

在能夠計算 選擇權評價-BS MODEL (with Excel) 及 隱含波動率 (with EXCEL VBA、C#) 之後,再來剩GREEKS了,要計算四個影響價格的變數是Delta、Gamma、 Vega、Theta,完成這部份就可以得出目前選擇權部位組合的各項數值。關於這些希臘字母的定義和作用,都需要深入瞭解後才能對選擇權策略有所掌握,進而設計出選擇權策略。

 

此篇要紀錄的僅是關於系統開發,雖說大部份的交易軟體都會提供GREEKS資料,但總要是自己寫的才能確定正確性,因為券商軟體算的不一定對。當然也有許多人不在意些微差距,或根本沒在觀察GREEKS,但那樣操作選擇權是無法進步的,再說若要自己建立策略模組,這些也只是第一步而已。

 

大部份設計選擇權監視程式的軟體是EXCEL,因為最簡便,直接利用公式和一些函數設計就可以完成,若要即時串接部位的話,大部份是寫VBA程式和券商API串接,而行情的部份可以用DDE或API來接收。

 

但是EXCEL會有效能問題,如果接受行情多,再加上運算邏輯複雜,EXCEL很可能會有延遲的狀況,因此比較好的方式還是自己寫程式,可以先由券商API所提供的範例程式來測試,可能是VB、C#、C++或Delphi等等,選一個好寫的來測試收行情、回報、部位。此篇L要紀錄的是以C#來實作選擇權GREEKS。

 

整段範例程式碼貼在最後面,需要先說明一下前因後果,先有一個DataTable是用來紀錄未平倉的選擇權部位,這個副程式所要處理的是計算目前所有未平倉選擇權部位的GREEKS並寫進DataTable,這個DataTable再與DataGridView串接,就可以顯示在FORM上

 

這個DataTable,取名dtOI,至少有以下的欄位:

 

dtOI.Columns.Add(new DataColumn("SettleM", typeof(string))); //月份

dtOI.Columns.Add(new DataColumn("Strike", typeof(double))); //履約價

dtOI.Columns.Add(new DataColumn("CP", typeof(string))); //Call or Put

dtOI.Columns.Add(new DataColumn("BS", typeof(string))); //Buy or Short

dtOI.Columns.Add(new DataColumn("Qty", typeof(string))); //口數

dtOI.Columns.Add(new DataColumn("Match", typeof(double))); //成交價

dtOI.Columns.Add(new DataColumn("Market", typeof(double))); //市價

dtOI.Columns.Add(new DataColumn("VOL(%)", typeof(double))); //隱含波動率

dtOI.Columns.Add(new DataColumn("DELTA", typeof(double))); //DELTA

dtOI.Columns.Add(new DataColumn("GAMMA", typeof(double)));//GAMMA

dtOI.Columns.Add(new DataColumn("THETA", typeof(double))); //THETA

dtOI.Columns.Add(new DataColumn("VEGA", typeof(double))); //VEGA

 

在執行副程式之前,除了最後五欄之外都是準備好的,然後有一些變數要準備

AssetPrice 期貨價格

Strike 履約價

InterestRate 利率

Expiry 到期因子

Target 選擇權的市價

 

還有之前紀錄隱含波動率的類別,一開始要NEW一下。

Option op = new Option();

 

然後就開始了,看片段來說明一下

 

圖片 1

 

圖片 3

 

圖片 4

 

以下PUT的部份就依此類推。程式碼如後所貼,有一些使用到的參數及關聯FORM的變數沒有仔細說明,不過整個架構已經是可以瞭解的了。以前 L 設計時找不到完整的參考資料,一步步的自己設計,現在若有同樣需求的朋友看到這邊可以自由取用,歡迎參考及指正

 
private void Greeks()
{
int i = 0;
AssetPrice = double.Parse(label16.Text);

while (i < dtOI.Rows.Count)
{
Strike = double.Parse(dtOI.Rows[i]["Strike"].ToString());
Target = double.Parse(dtOI.Rows[i]["Market"].ToString());
Qty = double.Parse(dtOI.Rows[i]["Qty"].ToString());

//期貨
if (dtOI.Rows[i]["CP"].ToString() == "")
{
if (dtOI.Rows[i]["BS"].ToString().Trim() == "B")
{
dtOI.Rows[i].SetField("DELTA", 200 * Qty );
}
if (dtOI.Rows[i]["BS"].ToString().Trim() == "S")
{
dtOI.Rows[i].SetField("DELTA", -200 * Qty );
}
}

//CALL civ(double AssetPrice, double Strike, double InterestRate, double Expiry, double Target)
if ((dtOI.Rows[i]["CP"].ToString().Trim()) == "C")
{
if (dtOI.Rows[i]["PId"].ToString().Trim() == "TXO")
{
if (dtOI.Rows[i]["SettleM"].ToString().Trim() == label10.Text.Substring(0, 6)) //近月
{
AssetPrice = double.Parse(label16.Text);
Expiry = Expirynear;
}

if (dtOI.Rows[i]["SettleM"].ToString().Trim() == label11.Text.Substring(0, 6)) //遠月
{
if (label22.Text != "")
{
AssetPrice = double.Parse(label22.Text);
}
else { AssetPrice = double.Parse(label16.Text); }
Expiry = Expiryfar;
}
}
else //週選
{
if (wnFid != "MX")
{
if (dtOI.Rows[i]["PId"].ToString().Trim().Substring(1, 2) == wnFid.Substring(1, 2)) //近週
{
AssetPrice = double.Parse(label23.Text);
Expiry = Expirywn;
}
}

if (wfFid != "MX")
{
if (dtOI.Rows[i]["PId"].ToString().Trim().Substring(1, 2) == wfFid.Substring(1, 2)) //遠週
{
AssetPrice = double.Parse(label22.Text);
Expiry = Expirywf;
}
}
}

civ = op.civ(AssetPrice, Strike, InterestRate, Expiry, Target);
d1 = op.d1(AssetPrice, Strike, InterestRate, Expiry, civ);
nd1 = Math.Exp(-0.5 * d1 * d1) / Math.Sqrt(2 * Math.PI);
nd2 = op.NormsDist(d1 - civ * Math.Sqrt(Expiry));

//DELTA NormsDist(double x) d1(double AssetPrice, double Strike, double InterestRate, double Expiry, double Volatility)
DELTA = op.NormsDist(d1);
GAMMA = nd1 / (AssetPrice * civ * Math.Sqrt(Expiry));
THETA = (-(AssetPrice * nd1 * civ / (2 * Math.Sqrt(Expiry))) - InterestRate * Strike * Math.Exp(-InterestRate * Expiry) * nd2) / 365;
VEGA = (AssetPrice * (Math.Sqrt(Expiry)) * nd1) / 100;

dtOI.Rows[i].SetField("VOL(%)", Math.Round(civ * 100, 4));

if (dtOI.Rows[i]["BS"].ToString().Trim() == "B")
{
dtOI.Rows[i].SetField("DELTA", Math.Round(DELTA * 50 * Qty , 0));
dtOI.Rows[i].SetField("GAMMA", Math.Round(GAMMA * 50 * Qty * AssetPrice / 100, 0));
dtOI.Rows[i].SetField("THETA", Math.Round(THETA * 50 * Qty, 0));
dtOI.Rows[i].SetField("VEGA", Math.Round(VEGA * 50 * Qty, 0));
}
else if (dtOI.Rows[i]["BS"].ToString().Trim() == "S")
{
dtOI.Rows[i].SetField("DELTA", Math.Round(-DELTA * 50 * Qty , 0));
dtOI.Rows[i].SetField("GAMMA", Math.Round(-GAMMA * 50 * Qty * AssetPrice / 100, 0));
dtOI.Rows[i].SetField("THETA", Math.Round(-THETA * 50 * Qty, 0));
dtOI.Rows[i].SetField("VEGA", Math.Round(-VEGA * 50 * Qty, 0));
}
}

//PUT piv(double AssetPrice, double Strike, double InterestRate, double Expiry, double Target)
if ((dtOI.Rows[i]["CP"].ToString().Trim()) == "P")
{
if (dtOI.Rows[i]["PId"].ToString().Trim() == "TXO")
{
if (dtOI.Rows[i]["SettleM"].ToString().Trim() == label10.Text.Substring(0, 6)) //近月
{
AssetPrice = double.Parse(label16.Text);
Expiry = Expirynear;
}

if (dtOI.Rows[i]["SettleM"].ToString().Trim() == label11.Text.Substring(0, 6)) //遠月
{
if (label22.Text != "")
{
AssetPrice = double.Parse(label22.Text);
}
else { AssetPrice = double.Parse(label16.Text); }
Expiry = Expiryfar;
}
}
else //週選
{

if (wnFid != "MX")
{
if (dtOI.Rows[i]["PId"].ToString().Trim().Substring(1, 2) == wnFid.Substring(1, 2)) //近週
{
AssetPrice = double.Parse(label23.Text);
Expiry = Expirywn;
}
}

if (wfFid != "MX")
{
if (dtOI.Rows[i]["PId"].ToString().Trim().Substring(1, 2) == wfFid.Substring(1, 2)) //遠週
{
AssetPrice = double.Parse(label22.Text);
Expiry = Expirywf;
}
}
}

piv = op.piv(AssetPrice, Strike, InterestRate, Expiry, Target);
d1 = op.d1(AssetPrice, Strike, InterestRate, Expiry, piv);
nd1 = Math.Exp(-0.5 * d1 * d1) / Math.Sqrt(2 * Math.PI);
nd2 = op.NormsDist(d1 - piv * Math.Sqrt(Expiry));

//DELTA NormsDist(double x) d1(double AssetPrice, double Strike, double InterestRate, double Expiry, double Volatility)
DELTA = op.NormsDist(d1) - 1;
GAMMA = nd1 / (AssetPrice * piv * Math.Sqrt(Expiry));
THETA = (-(AssetPrice * nd1 * piv / (2 * Math.Sqrt(Expiry))) + InterestRate * Strike * Math.Exp(-InterestRate * Expiry) * nd2) / 365;
VEGA = (AssetPrice * (Math.Sqrt(Expiry)) * nd1) / 100;

dtOI.Rows[i].SetField("VOL(%)", Math.Round(piv * 100, 4));

if (dtOI.Rows[i]["BS"].ToString().Trim() == "B")
{
dtOI.Rows[i].SetField("DELTA", Math.Round(DELTA * 50 * Qty , 0));
dtOI.Rows[i].SetField("GAMMA", Math.Round(GAMMA * 50 * Qty * AssetPrice / 100, 0));
dtOI.Rows[i].SetField("THETA", Math.Round(THETA * 50 * Qty, 0));
dtOI.Rows[i].SetField("VEGA", Math.Round(VEGA * 50 * Qty, 0));
}
else if (dtOI.Rows[i]["BS"].ToString().Trim() == "S")
{
dtOI.Rows[i].SetField("DELTA", Math.Round(-DELTA * 50 * Qty , 0));
dtOI.Rows[i].SetField("GAMMA", Math.Round(-GAMMA * 50 * Qty * AssetPrice / 100, 0));
dtOI.Rows[i].SetField("THETA", Math.Round(-THETA * 50 * Qty, 0));
dtOI.Rows[i].SetField("VEGA", Math.Round(-VEGA * 50 * Qty, 0));
}
}

//dtOI.Rows[i].SetField("VOL(%)", i);
i = i + 1;
}

}

 

 

 

2013年11月8日 星期五

隱含波動率 (with EXCEL VBA、C#)

 

延續前兩篇 (選擇權評價-BS MODEL (with Excel) 、歷史波動率 )的內容,這篇要來紀錄隱含波動率了。

 

隱含波動率(implied volatility, iv)是以選擇權市價所倒推回它處在多少的波動率之上, 可以表示市場對於指數波動的看法,這也是重要的選擇權交易策略。而要計算它的方法也有很簡單易懂的,就是去試。

 

假設要計算一個市價100的選擇權商品的iv,如同前篇紀錄的,我們已經有了選擇權評價的公式,先設定一組高低點(如1和0),取高低點的中位(0.5),把0.5代入選擇權評價的公式,假設得到150,比市價100高,表示0.5太高了,將剛剛的高點1改為0.5,再取高低點的中位(0.25),再算價格再和市價比,就這樣一直比到高低點的差距很小,就得到iv了。

 

剛剛這段計算的例子寫成程式,以VBA為例就如下:
Function civ(S, K, T, R, Target)
high = 1
low = 0

Do While (high - low) > 0.00001
If PriceC(S, K, T, R, (high + low) / 2) > Target Then
high = (high + low) / 2
Else
low = (high + low) / 2
End If
Loop

civ = (high + low) / 2
End Function

Function piv(S, K, T, R, Target)
high = 1
low = 0

Do While (high - low) > 0.00001
If PriceP(S, K, T, R, (high + low) / 2) > Target Then
high = (high + low) / 2
Else
low = (high + low) / 2
End If
Loop

piv = (high + low) / 2
End Function

Function d1(S, K, T, R, V)
d1 = (Log(S / K) + (R * T) + (V ^ 2 * T / 2)) / (V * Sqr(T))
End Function

Function d2(S, K, T, R, V)
d2 = d1(S, K, T, R, V) - V * Sqr(T)
End Function

Function PriceC(S, K, T, R, V)
PriceC = S * Application.NormSDist(d1(S, K, T, R, V)) - K * Exp(-(R * T)) * Application.NormSDist(d2(S, K, T, R, V))
End Function

Function PriceP(S, K, T, R, V)
PriceP = K * Exp(-R * T) * Application.NormSDist(-d2(S, K, T, R, V)) - S * Application.NormSDist(-d1(S, K, T, R, V))
End Function

其中civ計算call的iv,piv計算put的iv,用到的Function之前有介紹過。

變數名稱

 

S 現貨價格,我們拿來算台指選的話,就拿台指期市價當作S

K 履約價格

T 距離到期日的時間,要年化

R 年利率

V 年化波動率

Target 標的選擇權的市價

 

同樣的程式用c#寫,也在這篇紀錄一下。
class Option
{

public double CallPrice;
public double PutPrice;

public void Price(double S, double K, double R, double T, double V)
{
double a = Math.Log(S / K);
double b_call = (R + 0.5 * Math.Pow(V, 2)) * T;
double b_put = (R - 0.5 * Math.Pow(V, 2)) * T;
double c = V * Math.Sqrt(T);
double d1 = (a + b_call) / c;
double d2 = (a + b_put) / c;
CallPrice = S * ND(d1) - K * Math.Exp(-R * T) * ND(d2);
PutPrice = K * Math.Exp(-R * T) * ND(-d2) - S * ND(-d1);
}

public double PriceC(double S, double K, double R, double T, double V)
{
double a = Math.Log(S / K);
double b_call = (R + 0.5 * Math.Pow(V, 2)) * T;
double b_put = (R - 0.5 * Math.Pow(V, 2)) * T;
double c = V * Math.Sqrt(T);
double d1 = (a + b_call) / c;
double d2 = (a + b_put) / c;
CallPrice = S * ND(d1) - K * Math.Exp(-R * T) * ND(d2);
return CallPrice;
}

public double PriceP(double S, double K, double R, double T, double V)
{
double a = Math.Log(S / K);
double b_call = (R + 0.5 * Math.Pow(V, 2)) * T;
double b_put = (R - 0.5 * Math.Pow(V, 2)) * T;
double c = V * Math.Sqrt(T);
double d1 = (a + b_call) / c;
double d2 = (a + b_put) / c;
PutPrice = K * Math.Exp(-R * T) * ND(-d2) - S * ND(-d1);
return PutPrice;
}

public double ND(double d)
{
double L = 0.0;
double K = 0.0;
double dD = 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(d);
K = 1.0 / (1.0 + 0.2316419 * L);

dD = 1.0 - 1.0 / Math.Sqrt(2 * Convert.ToDouble(Math.PI)) * 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 (d < 0) {return 1.0 - dD;}else{return dD;}
}

public double d1(double S, double K, double R, double T, double V)
{
double a = Math.Log(S / K);
double b_call = (R + 0.5 * Math.Pow(V, 2)) * T;
double c = V * Math.Sqrt(T);
double d1 = (a + b_call) / c;
return d1;
}

public double civ(double S, double K, double R, double T, double Target)
{
double high = 1;
double low = 0;

while ((high - low) > 0.00001)
{
if (PriceC(S, K, R, T, (high + low) / 2) > Target) { high = (high + low) / 2; }
else {low = (high + low) / 2;}
}

double civ = (high + low) / 2;
return civ;
}

public double piv(double S, double K, double R, double T, double Target)
{
double high = 1;
double low = 0;

while ((high - low) > 0.00001)
{
if (PriceP(S, K, R, T, (high + low) / 2) > Target)
{high = (high + low) / 2;} else {low = (high + low) / 2;}
}

double piv = (high + low) / 2;
return piv;
}
}

上面是一個class,直接加入類別就可以使用了,呼叫 Price(...) 後,可以拿屬性 CallPrice 和 PutPrice來用,或是分別呼叫PriceC(...) 或 PriceP(...) ,計算civ和piv一樣,可以直接得到回傳值iv。跟VBA不同的是,C#要多寫一個常態分配的函數ND。

 

有了這些,就可以計算出隱含波動率,運用上就看各人解讀了,之後再繼續紀錄。

(註: 範例程式是 L 參考網路上程式後自己撰寫的,讀者可自由複製使用,但程式碼轉載請註明出處 。)

 

 

2013年9月7日 星期六

建立文字檔下單機 (with C#)

 

文字檔下單機是常用的自動下單接法,它一定不是最有效率的作法,但非常通用,不論是策略產生是由TS、MC、EXCEL或客製的程式,都可以在訊號產生時寫入文字檔,再由文字檔下單機處理委託送單。

 

網路上也有多種免費的文字檔下單機可以利用,或許之後可以請J再介紹一下,但套裝的總是有點用不順手,寫的太完整的感覺又很複雜,所以L就來紀錄文字檔下單機的設計架構,若有朋友嘗試自己設計一個文字檔下單機,也是個起步的參考。

 

設計程式一開始當然就是確認需求,還有定義好文字檔的格式。這格式設計有很多種,例如

  1. 方向,口數,價位 (“Buy”, ”1”, “8000”) (買一口8000)

  2. 方向結合口數,價位 (”2”, “8000”) (買兩口8000)、(“-1”, ”7950 ”) (賣一口7950)

  3. 方向結合口數 ,(“1”) (買一口)、(“-3”) (賣三口)

  4. 給部位OI ,(“1”) (調到買進一單位)、(“-1”)(調到賣出一單位)、(“0”)(調到無部位)


 

如果文字檔沒寫入價位,那麼下單機就要能收行情,不然就只能丟市價單很奇怪,另外依照不同的文字檔定義,下單機也要設計不同的判斷邏輯。資料給的越少,下單機要處理的事情就越多,那我們就來看給最少的第四種為範例,文字檔只寫策略現在是0、1、-1,這三種狀態。

 

繼續確認需求,這個下單機對於使用上的功能希望如下:

  1. 設定連結下單的帳號

  2. 設定文字檔所在路徑

  3. 設定讀取的文字檔

  4. 設定策略的口數倍數

  5. 設定下單的滑價點數

  6. 設定下單機開啟/關閉

  7. 目前策略狀態以燈號顯示

  8. 策略進出紀錄


 

內部要作到的功能如下:

  1. 登入帳號

  2. 收取行情並顯示

  3. 判斷文字檔後產生委託資料及送出

  4. 寫出訊號紀錄


 

以上,就先來刻介面,如下圖展示的範例:

 

TXTOrder


 

介面各功能如需求所述就開始寫程式吧,唯一的按鈕[登入並啟動],要寫的程式大致是

 
private void buttonLogin_Click(object sender, EventArgs e)
{
FN1 = textBox1.Text;
Algo1 = System.IO.File.ReadAllText(@"L:\"+FN1+".txt");
changeColor();
Commodity = textBoxCommod.Text;

//登入並訂閱行情
api.login("XXXXX", "XXXXXX", "XXX.XX.X.XX");

if (api.loginStatusFlag == true)
{
labelLogin.Text = "登入成功";
api.regItem(Commodity);
}
else
{
labelLogin.Text = "登入失敗";
}
/*
這邊寫把行情顯示到介面的Label上
....
*/

//啟動新執行緒讀取文字檔動作
Thread ProcessPSTD = new Thread(ProcessPS);
ProcessPSTD.Start();
}

 

其中一開始的

FN1 = textBox1.Text;

Algo1 = System.IO.File.ReadAllText(@"L:\"+FN1+".txt");

就是先到設定路徑去讀取文字檔,所用到的方法是System.IO.File.ReadAllText,然後將文字檔的內容設定到Algo1這個字串(別忘了它只有可能是三種狀態0、1、-1),這是第一次讀取文字檔的狀態,之後文字檔的變動會要依據這個Alog1字串變數。