This script below will automatically download the latest price data every time you change symbols (historical or intraday). In addition, on the intraday chart, it'll automatically run the downloader every 10 seconds or so to keep your chart fresh. The Autodownloader is designed for users who ARE NOT using the built-in stock price providers which are available with the Premium license tier.
The QuantShare team was gracious enough to allow autodownloader scripts to be shared without any special restrictions. (Another company very easily might have said no!) But they have not reviewed this script and they are not responsible for it. If you have any support issues, please post them here. This script is aimed at ADVANCED USERS and may require some troubleshooting to adapt to your situation. *USE AT YOUR OWN RISK*
You MUST read and follow the comments at the start of the script which contains additional instructions and *required* configuration steps. The Autodownloader will not work without the required changes.
This is the first QuantShare script of any significant size that I've written. I learned a lot (about QuantShare and C#), but this code is *very ugly*. Now that I have a better understanding of how this all works, I may end up rewriting the script because I see a lot of things I'd do different. After I worked through some initial kinks, the script has worked well for me for quite some time now. If there's enough interest, I'll pursue a rewrite.
PS: Don't forget to follow the instructions (in the code comments below) before using the script. And as always, thanks to the QuantShare team for their continued support!
// Stock Price Autodownloader v1.0 by Josh McCormick
// Last Updated: Saturday September 5th, 2020
//
// This autodownloader is designed for users who are NOT using the stock price providers that
// automatically update stock price data on the Premier support tier. For each chart you have open,
// this script determines what symbol is active and if you're using the historical/daily/EOD timeline or the
// intraday timelime. In the background, it calls the appropriate downloader to refresh your stock price
// database, and then it tells QuantShare to update the on-screen chart with the new data.
//
// On the daily chart, after the first run, this cycle only repeats when you change symbols.
// On the intraday chart, this cycle repeats itself every ten seconds or so, and again if you change symbols.
// TO DO: You will need to find the section much further down in the code which looks like this:
// string DAILYDL = "SCRIPTED - Historical Stock Market Data";
// string INTRADL = "SCRIPTED - Intraday Quotes for Major Stock Exchanges";
// Again, don't modify these lines. Find the real copy further below. When you locate them,
// you will need to put the EXACT names of your intrday and daily downloaders inside the quotes.
// It is HIGHLY RECOMMENDED that you create a copy of your existing downloader and rename
// them so that it is obvious which version is called by the script. You may later find the need
// to alter your script to do something different when it is called automatically throughout the day.
// I personally downloaded these two QuantShare Downloader scripts, and then made a second copy
// of them with a unique filename. Here are the download links:
//
// DAILY https://www.quantshare.com/item-463-historical-stock-market-data
// INTRADAY https://www.quantshare.com/item-734-intraday-quotes-for-major-stock-exchanges
// You may notice that every OTHER time you run the script, it simply exits. This is
// because it is designed to run with a keyboard shortcut to stop and start itself.
// Once you are satisfied with this script's operation, you may wish to assign it to
// a keyboard shortcut such as SHIFT-CONTROL-D with D meaning "downloader".
// Optionally, this script can also be used with the following add-on:
// https://www.quantshare.com/item-1825-getvariable-multiple-usage
//
// This add-on makes it possible for a formula to report on the status of a script.
// When the script is allowed to run, the following formula will put a blue
// box with the letter D in the lower-right corner of the chart, followed by the
// number of downloaders currently running.
//
// DownloadInfo=StringEqual(GetVariable("Downloader"),"1");
// PrintChart("D".GetVariable("Downloaders"), "", BottomRight, colorWhite, colorWhite, colorBlue, 255*DownloadInfo);
// Finally, this is version 1.0 of the script. Much of this was experimentation
// to figure out what works and what doesn't. There are occasions where the
// script will crash (creating a pop-up box with an OK button). If that happens,
// Stop/Restart the script. The next version of this script should be more stable.
// I am not responsible for any accidents, deaths, injuries, or financial losses
// resulting from your intended or unintended use of this script. Your feedback
// is very welcome.
// Use to enable/disable debug tracing
Globals.debug = 0;
// Default wait time for the Intraday downloader.
int seconds = 10;
string[] oldsymbol = new string[100];
string[] newsymbol = new string[100];
string keyword="Downloader";
string res = (string)Global.GetVariable(keyword);
if (res == "1") Global.SetVariable(keyword, "0");
else Global.SetVariable(keyword, "1");
if (Globals.debug>0) Global.Trace1("MAIN PROGRAM BEGINS."+Environment.NewLine);
if ((Global.GetVariable(keyword)=="0") & (Globals.debug>0)) Global.Trace1("SHUTDOWN DUE TO KEYWORD MATCH."+Environment.NewLine);
while((string)Global.GetVariable(keyword)!="0")
{
// By default, we always sleep 0.25 seconds.
// If there is at least ONE intraday chart, we'll bump that number up further below.
int maxsleep = 0;
int diff=0;
Chart[] charts = Charts.GetAllCharts();
if (Globals.debug>5) Global.Trace("NUMBER OF CHARTS: "+charts.Length+Environment.NewLine);
for(int i=0;i < charts.Length;i++)
{
if (Globals.debug>0) Global.Trace("------------------------------"+Environment.NewLine);
if (Globals.debug>5) Global.Trace("MAIN LOOP, CHART "+(i+1)+" OF "+charts.Length+":"+Environment.NewLine);
if (Globals.debug>5) Global.Trace("CHARTS "+(i+1)+" IS SYMBOL: "+charts[i].SymbolName+Environment.NewLine);
//if (System.String.Copy
//oldsymbol[i]=charts[i].SymbolName;
if (Globals.debug>9) Global.Trace("DEBUG LINE A"+Environment.NewLine);
newsymbol[i]="PLK";
if (Globals.debug>9) Global.Trace("DEBUG LINE A1"+Environment.NewLine);
newsymbol[i]=charts[i].SymbolName;
if (Globals.debug>9) Global.Trace("DEBUG LINE B"+Environment.NewLine);
diff=System.String.Compare(oldsymbol[i],charts[i].SymbolName);
if (Globals.debug>9) Global.Trace("DEBUG LINE C"+Environment.NewLine);
if ((Globals.debug>0) & (diff != 0)) Global.Trace("NEW SYMBOL! "+newsymbol[i]+" WAS: "+oldsymbol[i]+Environment.NewLine);
if ((Globals.debug>0) & (diff == 0)) Global.Trace("SAME SYMBOL! "+newsymbol[i]+" WAS: "+oldsymbol[i]+Environment.NewLine);
oldsymbol[i]=newsymbol[i];
if (Globals.debug>9) Global.Trace("DEBUG LINE D"+Environment.NewLine);
if(!charts[i].IsHistorical)
{
if (Globals.debug>0) Global.Trace("DL'ING INTRA: "+charts[i].SymbolName+Environment.NewLine);
GetNewData(charts[i]);
// We have an Intraday chart, so we're going to need to introduce a delay between
// runs or we'll start building up a queue of simultaneous downloads.
if (diff==0) maxsleep = seconds; else maxsleep = 2;
}
if(charts[i].IsHistorical)
{
if (diff != 0) {
if (Globals.debug>0) Global.Trace("DL'ING EOD: "+newsymbol[i]+Environment.NewLine);
GetNewData(charts[i]);
}
}
}
App.Main.Sleep (250);
// WHY? Because we've changing to sleeping for HALF SECONDS instead of SECONDS
// to make things more responsive.
if (Globals.debug>1) Global.Trace("SLEEPING UP TO "+maxsleep+" SECONDS."+Environment.NewLine);
maxsleep=maxsleep*2;
while (maxsleep > 0) {
// We want to sleep for the full period unless we've got some symbol changes that
// have been queued up by the user. In that case, we'll sleep for 250ms.
//
// If the user changes symbols while we were sleeping (normally up to 10 seconds),
// we'll wake up every half second to see if we have a new symbol to download charts
// for.
//
// BAD ASSUMPTION: Grabbing the new charts and comparing the symbols takes no real time.
if (diff!=0) {
if (Globals.debug>0) Global.Trace("Exiting sleep for "+charts[i].SymbolName+Environment.NewLine);
maxsleep=0;
}
}
App.Main.Sleep(500); // Do not change this - It may crash your PC if you change it
}
if (Globals.debug>0) Global.Trace("BOTTOM OF MAIN LOOP."+Environment.NewLine);
}
if (Globals.debug>0) Global.Trace("PROGRAM ENDS."+Environment.NewLine);
if (Globals.debug>6) Global.Trace("ENTERED GetNewData."+Environment.NewLine);
if(chart == null)
{
return;
}
string[] symbols = new string[1];
symbols[0] = chart.SymbolName;
string[] fields = new string[1];
Globals.counter++;
if ( !chart.IsHistorical )
{
fields[0] = "Number of past days=2";
Downloader.DownloadData("", INTRADL, true, symbols, fields, DownloadComplete, chart);
}
if ( chart.IsHistorical )
{
Downloader.DownloadData("", DAILYDL, true, symbols, null, DownloadComplete, chart);
}
}
void DownloadComplete(Object obj)
{
string dlsymbol="";
int dldiff=0;
Chart chart = (Chart)obj;
if(chart != null)
{
Globals.counter--;
// We don't have an atomic-level counter, so slippage can occur.
// If the number goes negative, zero it out.
if (Globals.counter < 0) Globals.counter=0;
dlsymbol=chart.SymbolName;
Global.SetVariable("Downloaders", (string) Globals.counter.ToString());
if (Globals.debug>1) Global.Trace("DL DONE: "+chart.SymbolName+Environment.NewLine);
if (Globals.debug>1) Global.Trace("COUNTER "+Global.GetVariable("Downloaders")+Environment.NewLine);
// It isn't good to have too many simultaneous downloaders to run at once.
// Keep updating the count if we've got a lot of these running for some reason.
if (Globals.counter>3) {
if (Globals.debug>6) Global.Trace("Setting Downloaders="+Globals.counter.ToString()+Environment.NewLine);
Global.SetVariable("Downloaders", (string) Globals.counter.ToString());
if (Globals.debug>1) Global.Trace("DELAYING FOR COUNTER ("+Globals.counter+") > 3"+Environment.NewLine);
App.Main.Sleep (1000);
}
// If the signal changed on us, we're probably screwed. Don't make it worse with an immediate update.
dldiff=System.String.Compare(dlsymbol,chart.SymbolName);
if (dldiff==0) chart.Update();
Global.SetVariable("Downloaders", (string) Globals.counter.ToString());
}
}
static class Globals
{
// global int
public static int counter;
public static int debug;
// global function
public static string HelloWorld()
{
return "Hello World";
}
// global int using get/set
static int _getsetcounter;
public static int getsetcounter
{
set { _getsetcounter = value; }
get { return _getsetcounter; }
}
static int _getsetdebug;
public static int getsetdebug
{
set { _getsetdebug = value; }
get { return _getsetdebug; }
}
}
There is no automatic on/off at the start and end of market hours. If it is running, it is downloading. If you've followed the optional instructions, you will be able to use SHIFT-CONTROL-D to turn the downloader off and on, and you'll see a blue box with the letter D in the lower-right hand corner of your chart to let you know that the downloader is running (assuming it didn't crash or anything). A week or two ago I submitted GetVariable Multiple Usage which allows your scripts to send variables to your functions. (And with a little work, that includes hotkeys.)
If you're interested in after-hours intraday data, you may need to modify your downloader to provide that data. For the downloader I use, I just had to change "includePrePost=false" to "includePrePost=true" in the URL-Script (under the script's settings). But if you don't want or need after-hours market data, you probably want to keep it disabled. (Between the wild price fluctuations and the low volume, I know I'd be likely to end up with some corrupted indicators.)
If you're having problems with the script, you can turn on debugging by changing the line which says "Globals.debug = 0;" and setting it to a number from 1 (mild) to 20 (complete) debugging into an Output Window. In QuantShare, click on View then Output to view its contents. Be aware that it won't be entirely consistent with what kind of events are logged at what numeric level.
If QuantShare locks within a minute of starting the script, you might find this variable "int seconds = 10;" and increase it to 30 to see if it solves the problem.
I'll add any additional notes here as they become available.
Trading financial instruments, including foreign exchange on margin, carries a high level of risk and is not suitable for all investors. The high degree of leverage can work against you as well as for you. Before deciding to invest in financial instruments or foreign exchange you should carefully consider your investment objectives, level of experience, and risk appetite. The possibility exists that you could sustain a loss of some or all of your initial investment and therefore you should not invest money that you cannot afford to lose. You should be aware of all the risks associated with trading and seek advice from an independent financial advisor if you have any doubts.