I knocked my head against this a bit more. It isn't as elegant as I would like it to be, but it is workable.
I am left with a question for the MultiCharts staff and the experienced coders on this forum:
When the signal or indicator class' constructor is called, as you insert the study - is there any way to collect the symbol info and timeframe resolution BEFORE you hit "OK" on the inputs box or "close" the signal study dialog? I would love to be able to use the symbol name and timeframe res as part of the folder/settings path. I have not been able to find a way to do this.
But, short of that, for those interested: below, find an example of the code needed to collect Inputs of various sorts and save their data to disk. And the next time you add the study, it will read the settings from the file on the path.
- You will need to add two global assemblies: Newtonsoft.Json and the System.IO.
- An additional class needs to be added to the namespace that can contain values used for the Serialization and file save process. I don't like having to use double the number of variables, it can lead to coding frustration when you forget to add a new variable to that section ... but it works. You just have to be careful
- There are some functions at the bottom of the signal class that need to be copied, some code in the main constructor, and in the StartCalc() function.
I will probably look to improve it as I get more comfortable with MultiCharts. Try out your own improvements and please share any you find.
Code: Select all
using System;
using System.IO; // Add this global assembly
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using ATCenterProxy.interop;
using PowerLanguage.TradeManager;
using Newtonsoft.Json; // Add this global assembly
namespace PowerLanguage.Strategy {
/// Inputs fields must match the [Input] fields you wish to use
/// These are the fields saved to the directory
public class Inputs {
public int my_enum { get; set; }
public double my_double { get; set; }
public string my_string { get; set; }
public int my_int32 { get; set; }
public string my_trade_type { get; set; }
} // end of Inputs class
public class _TEST_save_file : SignalObject {
Inputs inputs = new Inputs();
public enum parts_enum
{
Part1,
Part2,
};
[Input]
public parts_enum my_enum { get; set; }
[Input]
public double my_double { get; set; }
[Input]
public string my_string { get; set; }
[Input]
public int my_int32 { get; set; }
internal const string s1 = "long";
internal const string s2 = "short";
[Input(s1, s2)]
public string my_trade_type { get; set; }
// Set a variable to the Documents path.
string docPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
string prefsPath = "prefs_path";
string prefsFile = "settings_file.txt";
string fullPath = "";
public _TEST_save_file(object _ctx) : base(_ctx)
{
Output.Clear();
//prefsPath = this.GetType().Name+"_InputSettings";
//string tf = Bars.Info.Resolution.ToString();
//string symbol = Bars.Info.Name;
//prefsFile = symbol + "_" + tf + "_" + this.GetType().Name;
docPath = Path.Combine(docPath, prefsPath);
System.IO.Directory.CreateDirectory(docPath);
fullPath = Path.Combine(docPath, prefsFile);
if (File.Exists(fullPath)) // try to read from preferences file
{
Output.WriteLine("Reading prefs from {0}", fullPath);
Inputs saved_inputs = ReadFromJsonFile<Inputs>(fullPath);
// this output is just for troubleshooting
Output.WriteLine(">>> File Read, inputs are: " +
"saved_inputs.my_enum={0}, saved_inputs.my_double={1}, " +
"saved_inputs.my_string={2}, saved_inputs.my_int32={3}, saved_inputs.my_trade_type={4}",
saved_inputs.my_enum, saved_inputs.my_double,
saved_inputs.my_string, saved_inputs.my_int32, saved_inputs.my_trade_type);
my_enum = (parts_enum)saved_inputs.my_enum;
my_double = saved_inputs.my_double;
my_string = saved_inputs.my_string;
my_int32 = saved_inputs.my_int32;
my_trade_type = saved_inputs.my_trade_type;
}
else // set defaults
{
my_enum = parts_enum.Part1;
my_double = 5.010;
my_string = "Account info";
my_int32 = 7;
my_trade_type = s1;
}
}
protected override void Create() {
}
protected override void StartCalc() {
// load initial variables
inputs.my_enum = (int)my_enum;
inputs.my_double = my_double;
inputs.my_string = my_string;
inputs.my_int32 = my_int32;
inputs.my_trade_type = my_trade_type;
// this output is just for troubleshooting
Output.WriteLine("inputs.my_enum={0}, inputs.my_double={1}, inputs.my_string={2}, inputs.my_int32={3}, inputs.my_trade_type={4}",
inputs.my_enum, inputs.my_double, inputs.my_string, inputs.my_int32, inputs.my_trade_type);
// after collecting changes, save to file
Output.WriteLine("Saving prefs to {0}", fullPath);
WriteToJsonFile<Inputs>(fullPath, inputs);
}
protected override void CalcBar(){
}
/// <summary>
/// Originally implemented as a class, changed it to class functions.
/// See the blog (thanks Dan!)
/// https://blog.danskingdom.com/saving-and-loading-a-c-objects-data-to-an-xml-json-or-binary-file/
/// <para>Requires the Newtonsoft.Json assembly (Json.Net package in NuGet Gallery) to be referenced in your project.</para>
/// <typeparam name="T">The type of object being written to the file.</typeparam>
/// <param name="filePath">The file path to write the object instance to.</param>
/// <param name="objectToWrite">The object instance to write to the file.</param>
/// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
///
public void WriteToJsonFile<T>(string filePath, T objectToWrite, bool append = false) //where T : new()
{
TextWriter writer = null;
try
{
var contentsToWriteToFile = Newtonsoft.Json.JsonConvert.SerializeObject(objectToWrite);
writer = new StreamWriter(filePath, append);
writer.Write(contentsToWriteToFile);
}
finally
{
if (writer != null)
writer.Close();
}
}
/// <summary>
/// Reads an object instance from an Json file.
/// <para>Object type must have a parameterless constructor.</para>
/// </summary>
/// <typeparam name="T">The type of object to read from the file.</typeparam>
/// <param name="filePath">The file path to read the object instance from.</param>
/// <returns>Returns a new instance of the object read from the Json file.</returns>
public static T ReadFromJsonFile<T>(string filePath) where T : new()
{
TextReader reader = null;
try
{
reader = new StreamReader(filePath);
var fileContents = reader.ReadToEnd();
//Output.WriteLine(fileContents);
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(fileContents);
}
finally
{
if (reader != null)
reader.Close();
}
}
} // end of study class
} // end of namespace