Description
Runs as a bot that writes tick data to a external CSV file that can be read by other processes. I needed this because our AI algorithm exists outside of calgo and needs fresh data every tick to make it's predictions. I will eventually build the plumbing to allow external buy / sell recommendations to be read back from another stream using a separate bot. For those who want bar data instead of tick data. Look for DataExportBars.
Enhanced 2014-12-21 - Added the num_bid, num_ask and vol_adj_ask, vol_adj_bid columns based on market depth also removed some columns that have values that never changed.
I think vol_adj_bid and vol_adj_ask show where the market really is with less gaming. I like this because it shows where the bulk of the real orders are concentrated. That way the odd sell at a very low or very high price people use to try and game the system are averaged out because they typically do not use large volumes for this kind of gaming. It may be even better to throw away the statistical outliers and average the rest.
Enhanced:2014-12-21 - Reverse spread for vol_adj_price and added a price adjust volume to provide a basic bull vs bear indicator based on where the bulk the open order book is hanging.
When used against backtest it can download historical data. (See instructions in source) but the Market depth values I use to set num_bid, num_ask, vol_adj_ask, vol_adj_bid are not available during back test which is a real shame because I think these would be really handle for Machine learning algorithms.
The output file name is constructed from the Symbol plus the length of the moving average. An example is: exp-EURUSD-ma30-ticks.csv The moving average is included as an additional field just to show how it can be merged with the tick data.
Sample Output:
date,ask,bid,spread,num_ask,num_bid,vol_adj_ask,vol_adj_bid,vol_adj_spread 2014-12-22 06:51:29.746,1.225520,1.225410,0.000110,11,10,1.225600,1.225347,-0.000252 2014-12-22 06:51:36.527,1.225520,1.225420,0.000100,9,8,1.225590,1.225356,-0.000234 2014-12-22 06:51:44.308,1.225520,1.225430,0.000090,8,10,1.225584,1.225355,-0.000229 2014-12-22 06:51:44.621,1.225520,1.225440,0.000080,7,11,1.225588,1.225357,-0.000231 2014-12-22 06:51:47.621,1.225540,1.225440,0.000100,9,10,1.225594,1.225358,-0.000235 2014-12-22 06:51:49.371,1.225540,1.225460,0.000080,12,9,1.225635,1.225345,-0.000290 2014-12-22 06:51:49.621,1.225550,1.225460,0.000090,12,13,1.225662,1.225332,-0.000330 2014-12-22 06:51:50.371,1.225560,1.225460,0.000100,12,11,1.225675,1.225336,-0.000339 2014-12-22 06:51:50.605,1.225570,1.225460,0.000110,11,13,1.225682,1.225334,-0.000348 2014-12-22 06:51:50.918,1.225550,1.225450,0.000100,11,12,1.225684,1.225337,-0.000348 2014-12-22 06:51:51.168,1.225570,1.225490,0.000080,11,15,1.225667,1.225319,-0.000348 2014-12-22 06:51:51.480,1.225600,1.225520,0.000080,13,12,1.225723,1.225392,-0.000331 2014-12-22 06:51:51.621,1.225620,1.225520,0.000100,11,11,1.225703,1.225413,-0.000290 2014-12-22 06:51:52.418,1.225610,1.225520,0.000090,9,12,1.225676,1.225436,-0.000241
Sample in Excel
When ran live can save new inbound ticks to the file as they arrive where a external process can pick them up. I opened the file in shared mode so both processes can access the file simultaneously. It delivers a poor mans pipe for freshly arriving bar data. I tested this in Lua and it will sit there waiting for new bars to show up at the end of the file and seems to catch them every time they arrive. When using in this mode I recommend using a fast SSD or Ramdisk to minimize IO latency to disk.
I apologize to those who want to use it internationally since I know the comma delimiter will mess up your formatting. I tried changing it to a Tab delimited format but then it takes 5 extra clicks to load into excel. There are only two lines where you have to change the , to tab to make it work internationally.
I did not try to fix the back fill data problem for ticks like I did with bars because my application did not find them essential. Let me know if you want this feature we can not duplicate it exactly but should be able to get close except for being able to backfill the gaps between end of back-test and real time.
Note: To avoid saving ticks that had no order book I added a If Statement if ((volAdjAsk == 0.0) && (volAdjBid == 0.0)) it will have to be commented if you want to use it to download historical bars.
Want to collaborate: www.linkedin.com/pub/joe-ellsworth/0/22/682/ or http://bayesanalytic.com
No Promises, No Warranty. Terms of use MIT http://opensource.org/licenses/MIT
// -------------------------------------------------------------------------------------------------
//
// DataExportTicks
//
// This cBot is intended to download tick level history to an external CSV File.
// Target file can not be open in excel or will fail to open export file.
//
// Export Historical
// To use set the Backtest Begin date and End Test Date to reflect desired date range.
// Set the time frame to 1 tick.
// Set the backtest Data to "1 tick Data from Server"
// Then start the backtest.
// Some fields are not populated for backtesting
//
// Note: Calgo load all the backtest data into memory. It used over 2.88
// Gig of memory when I instructed back test to go back to Jan-1 2014.
// didn't crash but did grind to a halt. You may want to limit
// size of request. It created a size 1.082 Gig.
//
//
// Note: Could not test with live data due to weekend.
// Export Live Ticks
// Can be used to download current ticks by runing as an active strategy.
// If you open the file in shared mode with a readline() pending it will
// deliver new ticks as they become available.
//
// When using to read data in other processes suggest using fast memory
// RamDrive or fast SSD. Otherwise may need to use NamedPipe. Our other
// process did not support windows named pipes so just using a fast SSD.
//
// Want to collaborate: www.linkedin.com/pub/joe-ellsworth/0/22/682/ or
// http://bayesanalytic.com
//
// No Promises, No Warranty. Terms of use MIT http://opensource.org/licenses/MIT
// -------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using System.IO;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
namespace cAlgo
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class DataExportTicks : Robot
{
// QUESTION: How do I set default Source Timeframe to T1 (1 tick)
//[Parameter("TimeFrame ")]
//public TimeFrame TFrame { get; set; }
// QUESTION: How do I get Backteset to default to 1 tick from server
// so don't have to manually reset?
[Parameter("MA Type")]
public MovingAverageType MAType { get; set; }
[Parameter("Data Dir", DefaultValue = "c:\\download\\calgo")]
public string DataDir { get; set; }
private string fiName;
private System.IO.FileStream fstream;
private System.IO.StreamWriter fwriter;
private string csvhead = "date,ask,bid,spread,num_ask,num_bid,vol_adj_ask,vol_adj_bid,vol_adj_spread,vac_ask,vac_bid,vac_bear_vs_bull\n";
protected override void OnStart()
{
var ticktype = MarketSeries.TimeFrame.ToString();
fiName = DataDir + "\\" + "exp-" + Symbol.Code + "-ticks.csv";
Print("fiName=" + fiName);
if (System.IO.File.Exists(fiName) == false)
{
// generate new file with CSV header only if
// one does not already exist.
System.IO.File.WriteAllText(fiName, csvhead);
}
// had to open file this way to prevent .net from locking it and preventing
// access by other processes when using to download live ticks.
fstream = File.Open(fiName, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
// setup to append to end of file
Print("File is Open");
fstream.Seek(0, SeekOrigin.End);
// write stream has to be created after seek due to .net wierdness
// creating with 0 prevents buffering since we want tick data
// to be available to consumers right away.
fwriter = new System.IO.StreamWriter(fstream, System.Text.Encoding.UTF8, 1);
// QUESTION: How to tell when in Backtest mode so we
// can create the stream with a large buffer and turn off
// auto flush to improve IO performance.
Print("Fwriter is created");
fwriter.AutoFlush = true;
// with autoflush true will autocleanup
// since we can not close since we may run forever
Print("done onStart()");
}
protected double vol_weighted_price(cAlgo.API.Collections.IReadonlyList<cAlgo.API.MarketDepthEntry> mkentries)
{
double weightdiv = 0.0;
double tsum = 0.0;
for (int i = 0; i < mkentries.Count; i++)
{
var aent = mkentries[i];
tsum += (double)aent.Price * (double)aent.Volume;
weightdiv += (double)aent.Volume;
}
if (weightdiv == 0)
{
return 0;
}
else
{
return tsum / weightdiv;
}
}
protected double vol_weighted_cnt(cAlgo.API.Collections.IReadonlyList<cAlgo.API.MarketDepthEntry> mkentries)
{
double weightdiv = 0.0;
double tsum = 0.0;
for (int i = 0; i < mkentries.Count; i++)
{
var aent = mkentries[i];
tsum += (double)aent.Volume * (double)aent.Price;
weightdiv += (double)aent.Price;
}
if (weightdiv == 0)
{
return 0;
}
else
{
return tsum / weightdiv;
}
}
protected override void OnTick()
{
var sa = new System.Collections.Generic.List<string>();
var barTime = MarketSeries.OpenTime.LastValue;
var timestr = barTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
MarketDepth mkdepth = MarketData.GetMarketDepth(Symbol.Code);
var volAdjAsk = vol_weighted_price(mkdepth.AskEntries);
var volAdjBid = vol_weighted_price(mkdepth.BidEntries);
var volAdjSpread = volAdjAsk - volAdjBid;
var volCntAsk = vol_weighted_cnt(mkdepth.AskEntries) / 100000.0;
var volCntBid = vol_weighted_cnt(mkdepth.BidEntries) / 100000.0;
var vonCntBuyVsSell = volCntAsk - volCntBid;
if ((volAdjAsk == 0.0) && (volAdjBid == 0.0))
{
return;
}
sa.Add(timestr);
sa.Add(Symbol.Ask.ToString("F6"));
sa.Add(Symbol.Bid.ToString("F6"));
sa.Add(Symbol.Spread.ToString("F6"));
sa.Add(mkdepth.AskEntries.Count.ToString("F2"));
sa.Add(mkdepth.BidEntries.Count.ToString("F2"));
sa.Add(volAdjAsk.ToString("F6"));
sa.Add(volAdjBid.ToString("F6"));
sa.Add(volAdjSpread.ToString("F6"));
sa.Add(volCntAsk.ToString("F2"));
sa.Add(volCntBid.ToString("F2"));
sa.Add(vonCntBuyVsSell.ToString("F2"));
var sout = string.Join(",", sa);
//System.IO.File.AppendAllText(fiName, sout);
fwriter.WriteLine(sout);
fwriter.Flush();
}
protected override void OnStop()
{
Print("OnStop()");
fwriter.Close();
fstream.Close();
// Put your deinitialization logic here
}
}
}
joeatbayes
Joined on 06.12.2014
- Distribution: Free
- Language: C#
- Trading platform: cTrader Automate
- File name: DataExportTicks.algo
- Rating: 5
- Installs: 4438
- Modified: 13/10/2021 09:54
Comments
public static class OperationsUtlity
{
public static DataTable createDataTable()
{
DataTable table = new DataTable();
//columns
table.Columns.Add("ID", typeof(int));
table.Columns.Add("NAME", typeof(string));
table.Columns.Add("CITY", typeof(string));
//data
table.Rows.Add(111, "Devesh", "Ghaziabad");
table.Rows.Add(222, "ROLI", "KANPUR");
table.Rows.Add(102, "ROLI", "MAINPURI");
table.Rows.Add(212, "DEVESH", "KANPUR");
table.Rows.Add(102, "NIKHIL", "GZB");
table.Rows.Add(212, "HIMANSHU", "NOIDa");
table.Rows.Add(102, "AVINASH", "NOIDa");
table.Rows.Add(212, "BHUPPI", "GZB");
return table;
}
}
Create a static class
public static class CSVUtlity { }
Add Extention Method
public static void ToCSV(this DataTable dtDataTable, string strFilePath){
public static void ToCSV(this DataTable dtDataTable, string strFilePath) {
StreamWriter sw = new StreamWriter(strFilePath, false);
//headers
for (int i = 0; i < dtDataTable.Columns.Count; i++) {
sw.Write(dtDataTable.Columns[i]);
if (i < dtDataTable.Columns.Count - 1) {
sw.Write(",");
}
}
Apkbeer.com
Convert To csv
sw.Write(sw.NewLine);
foreach(DataRow dr in dtDataTable.Rows) {
for (int i = 0; i < dtDataTable.Columns.Count; i++) {
if (!Convert.IsDBNull(dr[i])) {
string value = dr[i].ToString();
if (value.Contains(',')) {
value = String.Format("\"{0}\"", value);
sw.Write(value);
} else {
sw.Write(dr[i].ToString());
}
}
if (i < dtDataTable.Columns.Count - 1) {
sw.Write(",");
}
}
sw.Write(sw.NewLine);
}
sw.Close();
}
using System;
using System.Linq;
using System.IO;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
namespace cAlgo
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class DataExportTicks : Robot
{
// QUESTION: How do I set default Source Timeframe to T1 (1 tick)
//[Parameter("TimeFrame ")]
//public TimeFrame TFrame { get; set; }
// QUESTION: How do I get Backteset to default to 1 tick from server
// so don't have to manually reset?
[Parameter("MA Type")]
public MovingAverageType MAType { get; set; }
[Parameter("Data Dir", DefaultValue = "c:\\download\\calgo")]
public string DataDir { get; set; }
private string fiName;
private System.IO.FileStream fstream;
private System.IO.StreamWriter fwriter;
private string csvhead = "date,ask,bid,spread,num_ask,num_bid,vol_adj_ask,vol_adj_bid,vol_adj_spread,vac_ask,vac_bid,vac_bear_vs_bull\n";
protected override void OnStart()
{
var ticktype = MarketSeries.TimeFrame.ToString();
fiName = DataDir + "\\" + "exp-" + Symbol.Code + "-ticks.csv";
Print("fiName=" + fiName); // Best Logo Design Australia
if (System.IO.File.Exists(fiName) == false)
{
// generate new file with CSV header only if
// one does not already exist.
System.IO.File.WriteAllText(fiName, csvhead);
}
// had to open file this way to prevent .net from locking it and preventing
// access by other processes when using to download live ticks.
fstream = File.Open(fiName, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
// setup to append to end of file
Print("File is Open");
fstream.Seek(0, SeekOrigin.End);
// write stream has to be created after seek due to .net wierdness
// creating with 0 prevents buffering since we want tick data
// to be available to consumers right away.
fwriter = new System.IO.StreamWriter(fstream, System.Text.Encoding.UTF8, 1);
// QUESTION: How to tell when in Backtest mode so we
// can create the stream with a large buffer and turn off
// auto flush to improve IO performance.
Print("Fwriter is created");
fwriter.AutoFlush = true;
// with autoflush true will autocleanup
// since we can not close since we may run forever
Print("done onStart()");
}
I'm facing an error in the above code.
- public static class OperationsUtlity
- {
- public static DataTable createDataTable()
- {
- DataTable table = new DataTable();
- //columns
- table.Columns.Add("ID", typeof(int));
- table.Columns.Add("NAME", typeof(string));
- table.Columns.Add("CITY", typeof(string));
- //data
- table.Rows.Add(111, "Devesh", "Ghaziabad");
- table.Rows.Add(222, "ROLI", "KANPUR");
- table.Rows.Add(102, "ROLI", "MAINPURI");
- table.Rows.Add(212, "DEVESH", "KANPUR");
- table.Rows.Add(102, "NIKHIL", "GZB");
- table.Rows.Add(212, "HIMANSHU", "NOIDa");
- table.Rows.Add(102, "AVINASH", "NOIDa");
- table.Rows.Add(212, "BHUPPI", "GZB");
- return table;
- }
- }
Create a static class
- public static class CSVUtlity { }
Add Extention Method
- public static void ToCSV(this DataTable dtDataTable, string strFilePath){
- public static void ToCSV(this DataTable dtDataTable, string strFilePath) {
- StreamWriter sw = new StreamWriter(strFilePath, false);
- //headers
- for (int i = 0; i < dtDataTable.Columns.Count; i++) {
- sw.Write(dtDataTable.Columns[i]);
- if (i < dtDataTable.Columns.Count - 1) {
- sw.Write(",");
- }
- }
Convert To csv
- sw.Write(sw.NewLine);
- foreach(DataRow dr in dtDataTable.Rows) {
- for (int i = 0; i < dtDataTable.Columns.Count; i++) {
- if (!Convert.IsDBNull(dr[i])) {
- string value = dr[i].ToString();
- if (value.Contains(',')) {
- value = String.Format("\"{0}\"", value);
- sw.Write(value);
- } else {
- sw.Write(dr[i].ToString());
- }
- }
- if (i < dtDataTable.Columns.Count - 1) {
- sw.Write(",");
- }
- }
- sw.Write(sw.NewLine);
- }
- sw.Close();
- }
Export to CSV on button click
- private void btnCSV_Click(object sender, EventArgs e) {
- DataTable dt = OperationsUtlity.createDataTable();
- string filename = OpenSavefileDialog();
- dt.ToCSV(filename);
- }
using System;
using System.Linq;
using System.IO;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
namespace cAlgo
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class DataExportTicks : Robot
{
// QUESTION: How do I set default Source Timeframe to T1 (1 tick)
//[Parameter("TimeFrame ")]
//public TimeFrame TFrame { get; set; }
// QUESTION: How do I get Backteset to default to 1 tick from server
// so don't have to manually reset?
[Parameter("MA Type")]
public MovingAverageType MAType { get; set; }
[Parameter("Data Dir", DefaultValue = "c:\\download\\calgo")]
public string DataDir { get; set; }
private string fiName;
private System.IO.FileStream fstream;
private System.IO.StreamWriter fwriter;
private string csvhead = "date,ask,bid,spread,num_ask,num_bid,vol_adj_ask,vol_adj_bid,vol_adj_spread,vac_ask,vac_bid,vac_bear_vs_bull\n";
protected override void OnStart()
{
var ticktype = MarketSeries.TimeFrame.ToString();
fiName = DataDir + "\\" + "exp-" + Symbol.Code + "-ticks.csv";
Print("fiName=" + fiName); // Shareit for pc
if (System.IO.File.Exists(fiName) == false)
{
// generate new file with CSV header only if
// one does not already exist.
System.IO.File.WriteAllText(fiName, csvhead);
}
// had to open file this way to prevent .net from locking it and preventing
// access by other processes when using to download live ticks.
fstream = File.Open(fiName, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
// setup to append to end of file
Print("File is Open");
fstream.Seek(0, SeekOrigin.End);
// write stream has to be created after seek due to .net wierdness
// creating with 0 prevents buffering since we want tick data
// to be available to consumers right away.
fwriter = new System.IO.StreamWriter(fstream, System.Text.Encoding.UTF8, 1);
// QUESTION: How to tell when in Backtest mode so we
// can create the stream with a large buffer and turn off
// auto flush to improve IO performance.
Print("Fwriter is created");
fwriter.AutoFlush = true;
// with autoflush true will autocleanup
// since we can not close since we may run forever
Print("done onStart()");
}
I'm facing an error in the above code.
Excume, I have typed the code above and try to run it on Calgo but I got error messenge like this:" Error CS0120: An object reference is required for the non-static field, method, or property 'cAlgo.API.Internals.Algo.TimeZone.get' "
what can I do to fix this issue.
using System;
using System.Linq;
using System.IO;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
namespace cAlgo
{
[Robot(TimeZone = TimeZone.UTC, AccessRights = AccessRights.FileSystem)]
public class DataExportTicks : Robot
Need help using the bot.
The log message before crash is as follows: "29/07/2015 21:24:41.915 | Crashed in OnTick with IOException: The process cannot access the file 'c:\exp-EURUSD-ticks.csv' because it is being used by another process."
Pops up right after the start of the bot then crashes. The csv file contains only headers (date, open, close etc).
Thanks
I guess, in backtesting we have to overwrite file. Currently it will be appended.
Sorry, Excel formatting was wrong.
To simplify excel formatting I separated Date and Time into two values.
What would be a way to get 24 hour format instead of AM/PM?
Thanks
Hi Joe,
your cBot is what I was looking to write on my own, but why have you made it cBot, instead of indicator?
Thanks
Hey Deansi, I took the time to add the order book values you mentioned. Let me know if it was what you intended.
I found a free data source with older data http://ratedata.gaincapital.com it takes a little work to coerce the file formats to match. I have found that working with tick data it doesn't seem to do much good to feed more than 200 days into our Machine Learning algorithms but the longer time frames are helpful when back-testing to see how the engine accommodates changing market conditions.
Hey King, Good point on the localized numbers. I think it would be better to use \t as the delimiter so excel can still load it as delimited file than ";"
Densi, Thanks for the tip on the MarketDepth. I will change that as it makes more sense to feed into external engines. The isBacktesting flag is much appreciated.
I updated my bar downloader to attempt to backfill correctly but don't have time to do the same with the Tickdata. Perhaps in Jan.
Just to add to my post above, the code below will tell you if youre currently backtesting:
if (IsBacktesting == true)
Also just a note that the MarketDepthEntry.Volume i mention in the previous post only works in live mode I believe since the server doesnt store this data at this point.
Enjoy
Joe,
Great work, I can answer a couple of your questions you had in the code comments.
The reason the volumes are always the same is the VolumeMin and VolumeMax method youre using is actually getting the min/max volume you are able to open a position in dollars etc.
You may want to try something like MarketDepth and MarketDepthEntry.Volume
and AskEntries and BidEntries,these will give you the market pending volume available, and seems to be updated on every tick.
Ive looked into the other question about timeframe in cbots tab defaulting before, it seemed there is no way to set the option in a cbot, but you can use this code below instead so the cbots tab setting has no effect and you will get ticks output regardless:
// var barTime = MarketSeries.OpenTime.LastValue; //this was getting the bar start time
var barTime = Server.Time ; //this gets the current server time, works live and backtesting
Hi Joe,
thank you for sharing your code!
"How to get data further back than Jan?"
- I think that is the maximum that Spotware offers. I guess the way to get older tick data is by downloading it from some other places.
I would suggest to change these lines:
var sout = string.Join(",", sa);
to -> var sout = string.Join(";", sa);
and
private string csvhead = "datetime;ask;bid;spread;movavgX1X;volmin;volmax;pipsize;pipval\n";
-> , to ; and I deleted some spaces in this line
"," is a problem for people in countries where you use , as number seperator. Otherwise both the variables and numbers are separeted with the same symbol.
Kind regards
What is the meaning behind these two functions?
It seems like he is offsetting the volume with the price. But doesn't that result in an information loss, or what exactly is the intended effect?
protected double vol_weighted_price(cAlgo.API.Collections.IReadonlyList<cAlgo.API.MarketDepthEntry> mkentries)
{
double weightdiv = 0.0;
double tsum = 0.0;
for (int i = 0; i < mkentries.Count; i++)
{
var aent = mkentries[i];
tsum += (double)aent.Price * (double)aent.Volume;
weightdiv += (double)aent.Volume;
}
if (weightdiv == 0)
{
return 0;
}
else
{
return tsum / weightdiv;
}
}
protected double vol_weighted_cnt(cAlgo.API.Collections.IReadonlyList<cAlgo.API.MarketDepthEntry> mkentries)
{
double weightdiv = 0.0;
double tsum = 0.0;
for (int i = 0; i < mkentries.Count; i++)
{
var aent = mkentries[i];
tsum += (double)aent.Volume * (double)aent.Price;
weightdiv += (double)aent.Price;
}
if (weightdiv == 0)
{
return 0;
}
else
{
return tsum / weightdiv;
}
}