顯示具有 交易系統建置 標籤的文章。 顯示所有文章
顯示具有 交易系統建置 標籤的文章。 顯示所有文章

2014年10月16日 星期四

選擇權賣方最佳結算價

 

這篇要紀錄選擇權賣方最佳結算價的小工具設計,這個價格的意義是在參考整個選擇權市場上未平倉合約對於最可能結算位置的預測,簡單的說,就是結在哪裡會賺最多。

 

以L自己的習慣來說,參考最大未平倉合約的位置及最佳結算價位置是每日必看的一項指標,有參考性,但同時也要提醒,期交所資料都是收盤後才公布,當日有大行情變化時是跟不上的,另外,這也僅僅是從選擇權市場來看。所以,心態上將它當作較長期的變化,這樣的角度或許比較適當。

 

稍微介紹後就來實作吧,寫EXCEL VBA,期交所每日公布選擇權每日交易行情查詢,由Excel匯入外部資料的連結是http://www.taifex.com.tw/chinese/3/3_2_tbl.asp

 

然後我們需要的資料有合約別、履約價、買賣權、結算價、未平倉合約量,將這些貼到sheet2再來算,前置作業是把周和近遠月的合約分開,這點也順帶提醒,由不同月份合約的最佳結算價可以看出市場對於近遠月指數的預期,這點也蠻值得參考的。

 

主要公式就是模擬各結算價下,現在未平倉合約的總損益如何,所以各個履約價都算一次完後加總起來,計算原則很簡單,

  1. 若是call & 模擬結算價<履約價,獲利為市價*未平倉口數
  2. 若是call & 模擬結算價>履約價,損益為( (模擬結算價-履約價)+市價)*未平倉口數
  3. 若是put & 模擬結算價>履約價,獲利為市價*未平倉口數
  4. 若是put & 模擬結算價<履約價,損益為( (履約價-模擬結算價)+市價)*未平倉口數

 

最後把各履約價的損益加總起來計在模擬價旁邊,看在哪個模擬價位是獲利最大就得到了。這個東西也能再進階去探索,就是把未平倉的GREEKS全算出來,搭配期貨去看,那有機會再寫。

 

其他的部份都是處理資料和迴圈而已,有需要參考的讀友可以看看下面程式碼區,如果想進一步瞭解的話,或許也可以再詳細作一篇紀錄。

當然有錯的話,L不負責任,有心得或指教的話,歡迎留言討論。

 

Sub cal()

Sheet2.Range("A2:Z700").Clear

i = 6

While Sheet1.Cells(i, 1) <> ""

If Sheet2.Cells(2, 2) = "" Then
Sheet2.Cells(2, 2) = Sheet1.Cells(i, 2)
ElseIf Sheet2.Cells(3, 2) = "" And Sheet1.Cells(i, 2) <> Sheet2.Cells(2, 2) Then
Sheet2.Cells(3, 2) = Sheet1.Cells(i, 2)
ElseIf Sheet2.Cells(4, 2) = "" And Sheet2.Cells(3, 2) <> "" And Sheet1.Cells(i, 2) <> Sheet2.Cells(3, 2) Then
Sheet2.Cells(4, 2) = Sheet1.Cells(i, 2)
ElseIf Sheet2.Cells(5, 2) = "" And Sheet2.Cells(4, 2) <> "" And Sheet2.Cells(3, 2) <> "" And Sheet1.Cells(i, 2) <> Sheet2.Cells(4, 2) Then
Sheet2.Cells(5, 2) = Sheet1.Cells(i, 2)
End If

Sheet2.Cells(i + 2, 2) = Sheet1.Cells(i, 2)
Sheet2.Cells(i + 2, 3) = Sheet1.Cells(i, 3)
Sheet2.Cells(i + 2, 4) = Sheet1.Cells(i, 4)
Sheet2.Cells(i + 2, 5) = Sheet1.Cells(i, 13)
Sheet2.Cells(i + 2, 6) = Sheet1.Cells(i, 9)

i = i + 1
Wend



i = 8

While Sheet2.Cells(i, 2) <> ""

If Sheet2.Cells(i, 2) = Sheet2.Cells(2, 2) Then
If Sheet2.Cells(i, 4) = "Call" And Sheet2.Cells(i, 5) > Sheet2.Cells(2, 4) Then
Sheet2.Cells(2, 4) = Sheet2.Cells(i, 5)
Sheet2.Cells(2, 3) = Sheet2.Cells(i, 3)
End If
If Sheet2.Cells(i, 4) = "Put" And Sheet2.Cells(i, 5) > Sheet2.Cells(2, 6) Then
Sheet2.Cells(2, 6) = Sheet2.Cells(i, 5)
Sheet2.Cells(2, 5) = Sheet2.Cells(i, 3)
End If
End If

If Sheet2.Cells(i, 2) = Sheet2.Cells(3, 2) Then
If Sheet2.Cells(i, 4) = "Call" And Sheet2.Cells(i, 5) > Sheet2.Cells(3, 4) Then
Sheet2.Cells(3, 4) = Sheet2.Cells(i, 5)
Sheet2.Cells(3, 3) = Sheet2.Cells(i, 3)
End If
If Sheet2.Cells(i, 4) = "Put" And Sheet2.Cells(i, 5) > Sheet2.Cells(3, 6) Then
Sheet2.Cells(3, 6) = Sheet2.Cells(i, 5)
Sheet2.Cells(3, 5) = Sheet2.Cells(i, 3)
End If
End If

If Sheet2.Cells(i, 2) = Sheet2.Cells(4, 2) Then
If Sheet2.Cells(i, 4) = "Call" And Sheet2.Cells(i, 5) > Sheet2.Cells(4, 4) Then
Sheet2.Cells(4, 4) = Sheet2.Cells(i, 5)
Sheet2.Cells(4, 3) = Sheet2.Cells(i, 3)
End If
If Sheet2.Cells(i, 4) = "Put" And Sheet2.Cells(i, 5) > Sheet2.Cells(4, 6) Then
Sheet2.Cells(4, 6) = Sheet2.Cells(i, 5)
Sheet2.Cells(4, 5) = Sheet2.Cells(i, 3)
End If
End If

If Sheet2.Cells(i, 2) = Sheet2.Cells(5, 2) Then
If Sheet2.Cells(i, 4) = "Call" And Sheet2.Cells(i, 5) > Sheet2.Cells(5, 4) Then
Sheet2.Cells(5, 4) = Sheet2.Cells(i, 5)
Sheet2.Cells(5, 3) = Sheet2.Cells(i, 3)
End If
If Sheet2.Cells(i, 4) = "Put" And Sheet2.Cells(i, 5) > Sheet2.Cells(5, 6) Then
Sheet2.Cells(5, 6) = Sheet2.Cells(i, 5)
Sheet2.Cells(5, 5) = Sheet2.Cells(i, 3)
End If
End If


i = i + 1
Wend


Sheet2.Cells(7, 9) = Sheet2.Cells(2, 2)
Sheet2.Cells(7, 10) = Sheet2.Cells(3, 2)
Sheet2.Cells(7, 11) = Sheet2.Cells(4, 2)
Sheet2.Cells(7, 12) = Sheet2.Cells(5, 2)

i = Sheet2.Cells(1, 10)
j = 8

While i < Sheet2.Cells(1, 11)

Sheet2.Cells(j, 8) = i


j = j + 1
i = i + 5
Wend








j = 8

While Sheet2.Cells(j, 8) <> ""

settle = Sheet2.Cells(j, 8)


i = 8

While Sheet2.Cells(i, 2) <> ""

If Sheet2.Cells(i, 4) = "Call" And Sheet2.Cells(i, 6) <> "-" Then
If settle > Sheet2.Cells(i, 3) Then Sheet2.Cells(i, 7) = (Sheet2.Cells(i, 6) - (settle - Sheet2.Cells(i, 3))) * Sheet2.Cells(i, 5)
If settle <= Sheet2.Cells(i, 3) Then Sheet2.Cells(i, 7) = Sheet2.Cells(i, 6) * Sheet2.Cells(i, 5)
End If

If Sheet2.Cells(i, 4) = "Put" And Sheet2.Cells(i, 6) <> "-" Then
If settle >= Sheet2.Cells(i, 3) Then Sheet2.Cells(i, 7) = Sheet2.Cells(i, 6) * Sheet2.Cells(i, 5)
If settle < Sheet2.Cells(i, 3) Then Sheet2.Cells(i, 7) = (Sheet2.Cells(i, 6) - (Sheet2.Cells(i, 3) - settle)) * Sheet2.Cells(i, 5)
End If

If Sheet2.Cells(i, 2) = Sheet2.Cells(7, 9) Then
Sheet2.Cells(j, 9) = Sheet2.Cells(j, 9) + Sheet2.Cells(i, 7)
ElseIf Sheet2.Cells(i, 2) = Sheet2.Cells(7, 10) Then
Sheet2.Cells(j, 10) = Sheet2.Cells(j, 10) + Sheet2.Cells(i, 7)
ElseIf Sheet2.Cells(i, 2) = Sheet2.Cells(7, 11) Then
Sheet2.Cells(j, 11) = Sheet2.Cells(j, 11) + Sheet2.Cells(i, 7)
ElseIf Sheet2.Cells(i, 2) = Sheet2.Cells(7, 12) Then
Sheet2.Cells(j, 12) = Sheet2.Cells(j, 12) + Sheet2.Cells(i, 7)
End If

i = i + 1
Wend

j = j + 1
Wend


Sheet2.Cells(6, 9) = Application.WorksheetFunction.Max(Sheet2.Range("I8:I360"))
Sheet2.Cells(6, 10) = Application.WorksheetFunction.Max(Sheet2.Range("J8:J360"))
Sheet2.Cells(6, 11) = Application.WorksheetFunction.Max(Sheet2.Range("K8:K360"))
Sheet2.Cells(6, 12) = Application.WorksheetFunction.Max(Sheet2.Range("L8:L360"))




i = 8

While Sheet2.Cells(i, 8) <> ""

If Sheet2.Cells(i, 9) = Sheet2.Cells(6, 9) Then Sheet2.Cells(2, 7) = Sheet2.Cells(i, 8)
If Sheet2.Cells(i, 10) = Sheet2.Cells(6, 10) Then Sheet2.Cells(3, 7) = Sheet2.Cells(i, 8)
If Sheet2.Cells(i, 11) = Sheet2.Cells(6, 11) Then Sheet2.Cells(4, 7) = Sheet2.Cells(i, 8)
If Sheet2.Cells(i, 12) = Sheet2.Cells(6, 12) Then Sheet2.Cells(5, 7) = Sheet2.Cells(i, 8)

i = i + 1
Wend



i = 1
While Sheet3.Cells(i, 1) <> ""

i = i + 1
Wend

Sheet3.Cells(i, 1) = Sheet1.Cells(2, 1)
Sheet3.Cells(i, 3) = Sheet2.Cells(2, 7)



End Sub

 

 

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年10月30日 星期三

Multicharts報價串接DDE

 

本篇先不論有些軟體可以直接串接DDE字串的方法,因為通常可能會用多來源做備援系統或判斷報價速度,透過交易量者較大的在EXCEL上進行判斷,之後取出該格報價作為來源,因此學會EXCEL的串接DDE是特別重要

 

首先準備好報價來源,放置在EXCEL上, J 故意建置兩個分頁,如下圖左下角所示,以便於後續示範如何建立有多分頁的報價字串,注意此處要先存成英文檔名且格式為2007以前使用的.xls(本次示範mc軟體較舊未能試驗是否新版已經支援.xlsx)

 

建置DDE報價1

 

 

下圖為第二分頁,讀者未必要分兩個分頁,需要使用需求與管理是否方便,準備好後我們進行QM的設定。

 

建置DDE報價2

 

打開QM,如下圖選擇Data Sources

 

建置DDE報價3

 

往下拉可以找到universal DDE的選項,點選後按右側,clone(複製),本處讀者當然可以直接使用設定去變更這個未定義的報價來源,但我們通常要製作不只一個報價來源,因此透過複製的方式,可以不斷地建置多重的報價來源

 

建置DDE報價4

 

隨讀者喜好建立名稱,注意,第二欄位只能key 3個字母。

 

建置DDE報價5

 

按下ok後就出現在可用來源裡了,點選他,再按右邊的settings,進入字串設定

 

建置DDE報價6

 

首先示範,有分頁且指定欄位的格式,主要的邏輯為先key入=EXCEL|’[檔名.xls]分頁名’!格子位置完成如下圖。本範例讀者可從本篇最上方第二張圖看到,R2表示第二行,C6表示第六欄,就可以找到成交價的格子,同理R2C8就是累積成交量(某些報價軟體只提供累積成交量,若收取本格則在程式碼中,需要透過減去前一值的方式算出當根K棒單量)。之後再左下角按TEST,若是盤中接報價,即可發現有報價再跳動,若是盤後建置,則可透過在EXCEL上任意地方KEY數字,再還原的做法,讓EXCEL以為有新資訊在跳動

 

建置DDE報價7

 

接著示範不預設商品代碼的作法,首先重新建立另一個資料源。

 

建置DDE報價8

 

進入設定後,KEY法J在此處順便示範不預設分頁的做法,假定讀者並沒有建置分頁可以採用下圖的KEY法,因此主要邏輯就改為=EXCEL|檔名.xls!*, * 的部分,就是本次最大的差異,透過*的方式不先預設報價所在的格子,即可達到同資料源,接許多商品的優勢。

 

字串key好後,一樣在左下角按test,此時會多跳出一個視窗,要key入你要測的報價代碼(EXCEL的格子),如下圖。

 

建置DDE報價9

 

確認後就可以看到如下圖所示,一樣透過在EXCEL上更改報價的方式,讓EXCEL上的報價感覺有跳動,就可以測試有沒有成功。

 

建置DDE報價10

 

完成後,開始建置自訂的商品代碼,可參考J之前的文章 Multicharts建立自訂商品 ,下列步驟就跳快一點。

 

2013年10月20日 星期日

Multicharts建立自訂商品

 

在MC中建立自訂商品,讓我們可以透過外部資訊來研究對走勢的預測性,常見的外部資訊就是籌碼面資料,因此本篇將以台指期OI(未平倉量)作一個示範,讓讀者學習如何自行建立所需的商品於MC中。

 

一開始要先準備所需的外部商品資料檔,常見的可能是TXT檔或是CSV檔,因TXT部分可以先匯入EXCEL中並另存成CSV檔(詳細資料可於 匯入或匯出文字檔案 這個說明網頁中了解細部作法),而本篇將以CSV檔來作介紹,製作成MC可以讀取的格式。

 

首先不論讀者手上擁有的資料為何,至少須將符合如下圖的格式,依照日期、時間(日資料可省略)、開盤價、最高價、最低價、收盤價、交易量(若無可省略)排列,可發現因為OI一天僅有一個數值,故須複製貼上至符合四個價位皆有數值的狀態,存成CSV後關閉。( L 補充: 一般是常用有開高低收的資料,但TICK檔是可以僅有時間和資料兩欄位而已,那同樣也可以只用兩個欄位匯入)

 

MultiCharts中建立自訂商品1

 

接著開啟QutoeManager,在空白處按右鍵,開始建立自訂的商品代碼與相關設定,選法如下圖所示,最後選擇Manually(手動)。

 

MultiCharts中建立自訂商品2

 

跳出如下室窗,key入想取的名稱,並注意Data source如果是盤後資料只做研究用,就選擇ASCII,如果是有接受DDE盤中或盤後自動接受資訊則需要選擇DDE的來源,日後將再詳細介紹DDE建立方法。

 

MultiCharts中建立自訂商品3

 

完成後進入第一個分頁,這部分沒什麼需要動的,只要在描述上面KEY入敘述即可(本例:台指期OI),不KEY也沒差。

 

MultiCharts中建立自訂商品4

 

點選第二個分頁,使用客製化並設定如下圖所示,此處注意如果是籌碼面資料同常可以模仿設定如下,但若是其他商品價格,則須考量其價格跳動的資訊,這部分資訊通常可參考期貨商提供的商品規格網頁。

 

MultiCharts中建立自訂商品5

 

第三個分頁, J 通常習慣在交易區間的選項中選擇使用者自訂,主要是可以更符合交易上的需求,在圖型顯是上也比較好上下對齊,方便研究與觀察,因此即便是日資料, J 還是會將其設定成分線的格式,畢竟日資料也是可以修改成分線資料來匯入,為了保留日後使用彈性,都一律建置成方便DATA1使用的時間

 

MultiCharts中建立自訂商品6

 

完成後就可以在QM中找到這個自訂商品代碼,如下圖所示,在其上按右鍵,選擇匯入資料。

 

MultiCharts中建立自訂商品7

 

此時在跳出視窗中,先選擇要匯入的CSV檔,再調整匯入的周期(本例:DAY)、成交價與當地時間,並檢查QM是否有判讀到正確的欄位(日期、開、高、低、收)若無顯示,則讀者可自行調整成下列格式,一樣可以繼續匯入。

 

MultiCharts中建立自訂商品8

 

按下完成後顯示正在匯入,正常是很快的,如果有過久沒再動,就可能有錯誤,須重新檢查資料是否有誤,另外若不是第一次匯入資料,且已有部分資料與匯入資料重疊時,將跳出室窗詢問是否替代,讀者可自行選擇。

 

MultiCharts中建立自訂商品9

 

完成資料匯入後,打開MC主程式,開啟DATA1的圖(本例:TXF),此時要將想研究的外部資料放在同一張圖上,需要在圖上按下右鍵,選擇新增商品。

 

MultiCharts中建立自訂商品10

 

跳出視窗的第一個分頁中,去選擇商品來源(本例:ASCII),找到需要的商品代碼,如下圖。

 

MultiCharts中建立自訂商品11

 

點選第二個分頁,選擇商品周期(本例僅匯入日層級資料,故若選擇分線或TICK層級將顯示無資料)與顯示時間。

 

MultiCharts中建立自訂商品12

 

最後就大功告成,如下圖所示,可以開始利用DATA2的資料來進行程式撰寫與研究了。

 

MultiCharts中建立自訂商品13

 

 

2013年10月18日 星期五

Multicharts摩台結算日期

 

摩台結算日是每個月倒數第二個交易日,除了如前篇J所分享的 尾盤出場策略1-建立日期表 所述作一個參考表之外,可以利用一些語法來達成。

 

以下就邊照著程式碼作注釋了。

 

var: M(0),DM(0),DW(0),STWSD(0);

if date <> date[1] then STWSD=0;

M=Month(Date); //取得月份
DM=DayOfMonth(Date); //取得幾日
DW=DayOfWeek(Date); //取得星期幾

 

//如果是大月份的話,結算日期對應星期幾來求出摩台結算日

If M=1 or M=3 or M=5 or M=7 or M=8 or M=10 or M=12 then begin

If DM=30 and DW=4 then STWSD =1;
If DM=30 and DW=3 then STWSD =1;
If DM=30 and DW=2 then STWSD =1;
If DM=30 and DW=1 then STWSD =1;
if DM=29 and DW=4 then STWSD =1;
if DM=28 and DW=5 then STWSD =1;
if DM=28 and DW=4 then STWSD =1;


end;

 

//如果是小月份的話,結算日期對應星期幾來求出摩台結算日

If M=4 or M=6 or M=9 or M=11 then begin

If DM=29 and DW=4 then STWSD =1;
If DM=29 and DW=3 then STWSD =1;
If DM=29 and DW=2 then STWSD =1;
If DM=29 and DW=1 then STWSD =1;
if DM=28 and DW=4 then STWSD =1;
if DM=27 and DW=5 then STWSD =1;
if DM=27 and DW=4 then STWSD =1;


end;

 

//如果是2月份的話,結算日期對應星期幾來求出摩台結算日

If M=2 then begin

If DM=27 and DW=4 then STWSD =1;
If DM=27 and DW=3 then STWSD =1;
If DM=27 and DW=2 then STWSD =1;
If DM=27 and DW=1 then STWSD =1;
if DM=26 and DW=4 then STWSD =1;
if DM=25 and DW=5 then STWSD =1;
if DM=25 and DW=4 then STWSD =1;


end;

 

//台股有時過年過節使得結算日期更改,就把這些日期作記號,往後若有變動也是從這裡加進

if date = 1060124 then STWSD = 1;
if date = 1080926 then STWSD = 1;
if date = 1090120 then STWSD = 1;
if date = 1090526 then STWSD = 1;
if date = 1120224 then STWSD = 1;

 

//以這個語法作指標,可以畫個線圖來觀察

plot1(STWSD,"STWSD");

 

如同下圖,就完成囉。

STWSD

 

 

 

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字串變數。

 

2013年8月29日 星期四

選擇權評價-BS MODEL (with Excel)

 

選擇權的評價是策略的最基礎,瞭解各項因素對選擇權價格的變動後,我們才能進一步討論各項因素的實際運用。我們一開始需要先來應用Black–Scholes option pricing model,經過實際的計算瞭解後,再來看Greeks和波動率,再來才是各種價差和組合策略。

 

關於選擇權的評價和解說,網路上的資料已經非常豐富,各項變動因素說明的文章也很容易找到,所以為了有些不同的貢獻紀錄,L將偏重在計算函數和程式介紹上面,實作上以EXCEL VBA和C#為主。

 

另一方面,大部份人看到評價公式後大概就已經懶惰了,但光是看看公式和解釋,其實不如實際寫一次、算一次來的有體會,而且實際上真的很容易,如果對於選擇權有興趣卻沒計算過的朋友們,請跟著一起作看看吧。

 

首先就看看公式的定義,可以到這兩個網址先大略看看假設和定義,有興趣再詳讀內容

Black–Scholes

Black-Scholes期權定價模型

 

推導和證明就算了吧,如果有要念財金所的話倒是會出考題,我們只要會用就好了,所以只要看Call和Put的價格是這樣的:

 

CP


 

其中d1和d2是這樣的:

 D1D2


 

 

參數是這樣的:

PA


 

所以我們知道計算價格需要五個變數,

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

k 履約價格

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

r 年利率

v 年化波動率

 

放到excel上寫寫看了,如下圖,

 

excel


 

s,就是期貨價,假設7750。

k,就是履約價,假設我們要算的是7700的call和put。

t,年化的到期餘日這個地方要注意的是有人一年用日曆日365,也有人一年是用工作日255左右,L自己是慣用工作日的算法。到期餘日就是今日和到期日的差距,可以利用excel的函數 NETWORKDAYS()來計算,以圖中例子就是=NETWORKDAYS(C27,C28)。今日也可以用函數來包,例如=DATE(YEAR(NOW()),MONTH(NOW()),DAY(NOW()))。算出到期餘日為16後,拿來除255,就得到年化的t = 0.062745。

r,年利率,拿定存利率一年約一點多%,以1%為例。

v,主觀的波動率,波動率是重要的主題,下篇再以歷史波動率和隱含波動率一起進行介紹紀錄,這邊以15%為例子。

 

有了這些參數,就可以算價格了,Call算出來理論價是145.23,Put是90.4。用函數 =calloption(F25,F26,F27,F28,F29),這個calloption就是我們要寫的function囉,打開vba或直接在excel按ALT+F11,貼上這段程式碼

 
Function calloption(s, k, t, r, v)

calloption = s * Application.NormSDist(cal_d1(s, k, t, r, v)) - k * Exp(-(r * t)) * Application.NormSDist(cal_d2(s, k, t, r, v))

End Function

Function putoption(s, k, t, r, v)

putoption = k * Exp(-r * t) * Application.NormSDist(-cal_d2(s, k, t, r, v)) - s * Application.NormSDist(-cal_d1(s, k, t, r, v))

End Function

Function cal_d1(s, k, t, r, v)

cal_d1 = (Log(s / k) + (r * t) + (v ^ 2 * t / 2)) / (v * Sqr(t))

End Function

Function cal_d2(s, k, t, r, v)

cal_d2 = cal_d1(s, k, t, r, v) - v * Sqr(t)

End Function

 

稍微再解釋一下,公式的N(),就是常態分配的機率,可以用NormSDist()來計算,sqr()就是根號,d1和d2也都各自寫了function,也都是照著公式直接拿進來寫,一個一個填就完成了。

 

如果跟著作的話,現在就可以試看看這幾個參數的變動對於選擇權價格的影響囉。

 

接著說明一點,這是不包含股利的評價模式,對於股票選擇權不適用,算指數選擇權剛好。

 

我們現在是帶入五個變數計算出理論的選擇權價格,實際上市場本來就有市價,那麼五個變數裡只要知道四個,就可以求出剩下來那一個。所以我們有選擇權市價、期貨市價、要求的履約價、到期日、利率之後,就可以算出波動率了,是的,它就是隱含波動率,下篇再繼續寫程式實作它。 ( 隱含波動率 (with EXCEL VBA、C#) )

 

 

2013年7月23日 星期二

Multicharts跨圖表傳遞資料

 

在MultiCharts裡有時候會想要引用不同圖表的資料,這個資料可能是價格、計算的指標值、或是某一個圖表訊號的部位,例如B圖表想要參考A圖表策略的目前部位為何?

 

不同圖表間資料的傳遞可以利用下面內建的函數,

GVSetNamedDouble

GVSetNamedInt

GVGetNamedDouble

GVGetNamedInt

 

Set就是存值,Get就是取值。Int是存整數,Double是存小數。

 

以一個傳遞訊號部位的例子來說明,建立兩個indicator,一個是PositionSource,將部位值存起來。一個是PositionDestination,將部位值取出來。

 

PositionSource範例程式如下,

 

PositionSource

 

行號4,將IntrabarOrderGeneration開啟,原因是部位可能每個TICK就有變動,這要看你的Signal是不是用到,一般來說,如果用到SetStop這類函數,就要把這個IOG打開。

 

行號6,和上面同樣理由,宣告一個IOG的變數PositionSource

 

行號8,i_MarketPosition 和 i_CurrentContracts 是部位方向和部位口數,兩個值就是在Siganl裡寫的MarketPosition和CurrentContracts,因為這裡寫的是指標,所以就用前面+ i_的。兩個值相乘後存到PositionSource這個變數裡,例如作空一口值是-1,作多兩口值是2。

 

行號10,呼叫GVSetNamedInt函數,將PositionSource的值,存到前面引號內的變數名,這邊同樣也取作PositionSource,這個名字就是給其它圖表呼叫用的。另外,這個例子傳遞部位值使用整數Int就可以了。

 

PositionDestination範例程式如下,

 

 

PositionDestination

 

前面一樣設定相關IOG,行號8將PositionSource的值取出來,存到變數Position中,第二個參數的說明並不清楚,函數說明僅寫是ErrorCode,推測是取不到值發生錯誤該傳什麼值,這個填一個數字就行了。

 

應用上,這個部位也可以拿來再和自己的部位相加,或是取其它資料時再拿來運算。只是這個取的值,只有存最後的狀態而已,如果需求是要拿來回測就沒辦法了,要用一般多新增Data的方式才可以回測。

 

 

2013年5月13日 星期一

備份歷史報價-排程設定

 

整合取得期交所每日行情資料備份期交所歷史報價的內容,再來作些設計,讓全部作業自動化吧。

 

建立一個CMD檔,路徑請自行更改
@echo off
D:\期貨DB建置資料\ExtractorProcess\ExtractorProcess\bin\Debug\ExtractorProcess.exe
cd D:\期貨DB建置資料\期貨Tick\zip
D:
unzip -d D:\期貨DB建置資料\期貨Tick Daily.zip
del Daily.zip
cd..
copy *.rpt D:\期貨DB建置資料\期貨Tick\BACK_new
rename *.rpt Daily.rpt

指令簡單說明,一開始執行 ExtractorProcess.exe 將檔案下載回來,利用unzip這個執行檔把檔案解壓縮,把ZIP檔刪除,然後把解壓縮完的rpt檔複製備份起來,再將這個rpt檔重新命名為Daily.rpt。

2013年5月9日 星期四

備份期交所歷史報價

 

接著上一篇取得期交所每日行情資料,將下載來的zip檔解壓縮後可以得到一份rpt檔,這個檔案是當日的報價資料,我們要來處理它了,目標是將報價存入資料庫中,讓我們以後可以隨時整理輸出報價文字檔給前端系統使用。

 

以下用到的資料庫是SQL SERVER 2005,有點久遠的版本,不過已經非常夠用,L 也已經用這個架構收資料很多年了,運作正常。不過話說回來,用資料庫的好處除了報價的儲存整理外,日後的進出紀錄和損益報表也用的上,寫第一次都是麻煩的,不過就是一次工夫。

 

在rpt檔裡的格式有以下欄位,

交易日期,商品代號,交割年月,成交時間,成交價格,成交數量(B+S),近月價格,遠月價格,開盤集合競價,如下圖,

 

期交所RPT

 

2013年5月1日 星期三

取得期交所每日行情資料

 

建置交易系統的第一步就是要處理歷史報價了,有幾個重點需要注意

 

歷史報價資料是策略的基礎,不能馬虎,簡單道理就是garbage in garbage out,拿不正確的資料來作回溯測試,可想而知開發出來的策略也不正確。google 搜尋 “台指期歷史資料”,可以找到蠻多相關資料,但是資料的正確性如何,可能有點問題。

 

所以若是在網路上取得的歷史資料,需要進行確認後才能開始使用。另一種可信的方法,就是完全由期交所提供的行情來製作一份歷史資料。

 

從期交所的資料轉檔使用,在實作上的問題不是資料正確性,只要寫好轉檔,輸出TXT或XPO來用,它就是正確的。問題是即時的報價和期交所盤後公布的行情資料一定是不同的,關於這點,只能儘量使我們自己的報價來源更快速穩定,更去貼近實際行情。不過基本工作仍是一樣,我們需要一份正確的歷史資料作為回溯測試之用。

 

行情歷史資料有兩個部份要處理,一個是從以前到今天,一個是今天的行情資料要怎麼加進去歷史資料裡。

 

期交所每日下午約三點半之後會提供成交資料檔,例如圖中,點一下”下載”,就可以取得一個例如Daily_2013_04_22.zip,這個壓縮檔解開後是一個rpt檔,裡面就是我們要的行情資料。