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

 



 

changeColor();是依照讀取完的狀態去顯示燈號,這個下面再寫它的內容,因為之後還會一直呼叫它。

 

接下來一段是api的登入、行情接收及將行情顯示到介面上,這一段因為各家api方法不同,所以就各自去study呼叫方法囉。

 

登入成功並註冊好行情接收及顯示後,就要處理讀取文字檔了,寫一個新的函數ProcessPS來處理,因為它是一個不斷讀取的迴圈,所以應該建立新的執行緒來處理這個工作。ProcessPS()的設計大致如下:

 
private void ProcessPS()
{
while (true)
{
System.Threading.Thread.Sleep(100);

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

if (tmp1 != Algo1)
{
String Lots = "0";
String BS = "";
Lots = StatusChange(tmp1, Algo1);
if (Lots == "1" || Lots == "2") { BS = "B"; }
if (Lots == "-1" || Lots == "-2") { BS = "S"; }

Algo1 = tmp1;
MatchPrice = labelMatchPrice.Text;
changeColor();

//確認參數都正常且自動下單是開啟狀態,呼叫委託方法
if (Lots != "0" && checkBox1.Checked == true && BS != "")
{
sendOrder(BS, MatchPrice, Lots);
}
}
}
}

 

一個無窮迴圈,每隔100毫秒執行一次,

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

去讀文字檔狀態並存入tmp1,再和Algo1比較,如果不同就表示狀態變了有訊號出現,先決定口數Lots = StatusChange(tmp1, Algo1);

這個StatusChange()如下:

 
private String StatusChange(String tmp, String Algo) 
{
String s = "0";
if (tmp == "1" && Algo == "0") { s = "1"; }
if (tmp == "1" && Algo == "-1") { s = "2"; }
if (tmp == "0" && Algo == "1") { s = "-1"; }
if (tmp == "0" && Algo == "-1") { s = "1"; }
if (tmp == "-1" && Algo == "0") { s = "-1"; }
if (tmp == "-1" && Algo == "1") { s = "-2"; }
return s;
}

 

看起來就白話,如果新的狀態是1,舊的狀態是0,那麼是要買1單位,以此類推。

if (Lots == "1" || Lots == "2") { BS = "B"; }

if (Lots == "-1" || Lots == "-2") { BS = "S"; }

如果是正的,就是要買。負的就是要賣。

 

Algo1 = tmp1;

把舊狀態Algo1設定成新狀態tmp1,然後changeColor(); 就是再改燈號了

 
private void changeColor() 
{
if (Algo1 == "1") { this.SetOv1("1"); }
if (Algo1 == "-1") { this.SetOv1("-1"); }
if (Algo1 == "0") { this.SetOv1("0"); }
}
private void SetOv1(string text)
{
if (this.InvokeRequired)
{
SetOvColor d = new SetOvColor(SetOv1);
this.Invoke(d, new object[] { text });
}
else
{
switch (text)
{
case "1": this.ovalShape1.FillColor = Color.Red; break;
case "0": this.ovalShape1.FillColor = Color.Gray ; break;
case "-1": this.ovalShape1.FillColor = Color.Green; break;
}

}
}

 

這其實本來很短,只要判斷後直接設定顏色紅灰綠如this.ovalShape1.FillColor = Color.Red,但因為我們是在不同執行緒所以有跨執行緒的問題,要多寫個委派來處理,不好解釋,程式大致如上就看看吧。

 

最後確認參數都正常且自動下單是開啟狀態,呼叫委託方法

if (Lots != "0" && checkBox1.Checked == true && BS != "")

{             sendOrder(BS, MatchPrice, Lots);            }

 
private void sendOrder(String BS,String MatchPrice, String Lots) 
{
if (Lots == "-1") { Lots = "1"; }
if (Lots == "-2") { Lots = "2"; }

switch (BS)
{
case "B": MatchPrice = (Int16.Parse(MatchPrice) + Int16.Parse(textBoxSlid.Text)).ToString(); break;
case "S": MatchPrice = (Int16.Parse(MatchPrice) - Int16.Parse(textBoxSlid.Text)).ToString(); break;
}
int multiple = Int16.Parse(textBoxLots.Text);
Lots = (Int16.Parse(Lots) * multiple).ToString();

//呼叫API的委託方法
api.ordersend(Account, Commodity, BS, MatchPrice, Lots, XXX, XXX);

this.SetTextRTB("\r\n" +DateTime.Now +" "+ Commodity + " " + BS + " " + Lots + " lots at " + MatchPrice);
}

 

其中switch (BS)這一段是處理市價和滑價的委託設定。

 

int multiple = Int16.Parse(textBoxLots.Text);

Lots = (Int16.Parse(Lots) * multiple).ToString();

這一段則是處理口數倍數的設定,再來就是呼叫api的委託方法送出委託單了,也一樣是看各家設定的方法囉。

 

最後,this.SetTextRTB()是把訊號紀錄到介面上的RichTextBox,這樣就完成了。

 

以上,提供給想要嘗試自己建立文字檔下單機的朋友參考,有許多細節沒有詳盡說明,一方面設計方法有許多種,每人的需求也不盡相同,所以能用到的就用,其它沒想到的就再來討論,另外L在本篇提供的設計方式也只是一種簡單的寫法,沒用到什麼高深的程式技巧,實際上要處理的更完美就要再考慮與產生文字檔程式的延遲、碰撞處理與更精確的架構調整,不管如何,基本照此篇範例這樣寫出來的東西是可以運作的。

 

下單機的設計方法還有許多可以紀錄,例如在MC中不需要透過寫出文字檔,可以直接呼叫DLL寫入變數,再寫個下單機利用相同DLL中的變數來獲得訊號並下單,是更快速的作法,這之後有整理好再介紹囉。

 

 

3 則留言:

  1. L大
    請問您
    TXTOrder表單c#的源碼
    可以提供嗎?
    在下想依照這篇教學
    試著自己組合看看
    感謝您

    回覆刪除
  2. HI,
    能提供的都寫在這篇文裡了,程式碼可以直接複製,
    其他的部份是個人環境串接API的,
    不能提供您整份原始碼,不好意思

    L

    回覆刪除
  3. 收到了解
    還是非常感謝您
    提供了這個教學
    感激不盡

    回覆刪除