| Overall Statistics |
|
Total Trades 1 Average Win 0% Average Loss 0% Compounding Annual Return 16.263% Drawdown 9.400% Expectancy 0 Net Profit 0% Sharpe Ratio 1.424 Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0.013 Beta 0.982 Annual Standard Deviation 0.109 Annual Variance 0.012 Information Ratio 0.739 Tracking Error 0.014 Treynor Ratio 0.158 Total Fees $1.00 |
namespace QuantConnect.Algorithm.CSharp
{
/*
Illustrates how to access Amazon S3 from QuantConnect cloud.
Because the whole point of this S3 configuration is to prevent unauthorized access,
I can't share a bucket without additional effort. Configuring Amazon S3 buckets is
unfortunately a bit of work (at least if you do it rarely like I do or for the first time).
Examples of what you can do this code with some modification:
1) Warm up algorithm through backtests, with restored state from S3 when launching live (this example) - S3State
2) Update parameters or signals by polling S3 bucket (not efficient and pretty ugly, but it works) - S3ParamSet
3) Some kind of external logging - S3Client generic usage
This comes without any warranty of any kind. Use at your own risk.
*/
public class S3ExampleAlgorithm : QCAlgorithm
{
private const string _algoID = "helloworld";
public override void Initialize()
{
SetStartDate(2016, 1, 1);
SetEndDate(DateTime.Now);
SetCash(25000);
//at a minimum, you need to replace xxx, yyy, and zzz below
var s3Client = new S3Client();
s3Client.BaseURL = new Uri("https://s3-eu-west-1.amazonaws.com");
s3Client.UserID = "xxx";
s3Client.Password = "yyy";
_s3state = new S3State(this, s3Client);
_s3state.Bucket = "zzz";
_s3state.AlgoID = _algoID;
_assets.Add(new Asset(this, "SPY"));
_s3state.DownloadWarmupState(_assets);
}
private S3State _s3state;
private readonly List<Asset> _assets = new List<Asset>();
public override void OnEndOfAlgorithm()
{
_s3state.UploadWarmupState(_assets);
}
private class AssetState // must be serializable by Json.net
{
public decimal MySavedValue;
}
private class Asset : S3State.Serializable
{
private readonly Security _security;
public Asset(QCAlgorithm algo, string symbol)
{
_security = algo.AddEquity(symbol, Resolution.Minute);
}
public string GetKey()
{
return _security.Symbol.ToString();
}
public object SerializeState()
{
var state = new AssetState();
state.MySavedValue = 3.14m; //should come from your class, obviously
return state;
}
public bool DeserializeState(object o)
{
var state = (AssetState)o;
//do something with state.MySavedValue here
return true;
}
}
/*
* New data arrives here.
* The "Slice" data represents a slice of time, it has all the data you need for a moment.
*/
public override void OnData(Slice data)
{
// slice has lots of useful information
TradeBars bars = data.Bars;
Splits splits = data.Splits;
Dividends dividends = data.Dividends;
//Get just this bar.
TradeBar bar;
if (bars.ContainsKey("SPY")) bar = bars["SPY"];
if (!Portfolio.HoldStock)
{
// place an order, positive is long, negative is short.
// Order("SPY", quantity);
// or request a fixed fraction of a specific asset.
// +1 = 100% long. -2 = short all capital with 2x leverage.
SetHoldings("SPY", 1);
// debug message to your console. Time is the algorithm time.
// send longer messages to a file - these are capped to 10kb
Debug("Purchased SPY on " + Time.ToShortDateString());
//Log("This is a longer message send to log.");
}
}
}
}using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
namespace QuantConnect.Algorithm.CSharp
{
sealed class S3Client
{
public string UserID { get; set; }
public string Password { get; set; }
public Uri BaseURL { get; set; }
private void CheckParameters()
{
if (UserID == null || UserID == "")
throw new ArgumentException("UserID not set");
if (Password == null || Password == "")
throw new ArgumentException("Password not set");
if (BaseURL == null)
throw new ArgumentException("BaseURL not set");
}
private static void WriteToRequestWorkaround(HttpWebRequest request, byte[] data)
{
var stream = request.GetRequestStream();
stream.Write(data, 0, data.Length);
}
private static void ReadFromResponseWorkaround(HttpWebResponse response, byte[] data)
{
var stream = response.GetResponseStream();
for (int count = 0; count < data.Length; )
{
int numRead = stream.Read(data, count, data.Length - count);
if (numRead > 0)
count += numRead;
else
break;
}
}
private static void ReadFromResponseUntilLineWorkaround(HttpWebResponse response, out byte[] data)
{
var bytes = new List<byte>();
var stream = response.GetResponseStream();
for (;;)
{
int next = stream.ReadByte();
if (next == '\n')
break;
bytes.Add((byte)next);
}
data = bytes.ToArray();
}
private HttpWebResponse Request(string method, string pathName, string objectName, byte[] content)
{
//ugly code follows, copied it from a buggy online Stack Overflow help request and fixed it
string hash = "";
if (content != null)
{
using (var md5 = new MD5CryptoServiceProvider())
{
hash = Convert.ToBase64String(md5.ComputeHash(content));
}
}
string contentType = "application/octet-stream";
string date = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ");
string file = "/" + pathName + "/" + objectName;
//Creating signature
var sBuilder = new StringBuilder();
sBuilder.Append(method).Append("\n");
if (content != null)
{
sBuilder.Append(hash).Append("\n");
sBuilder.Append(contentType);
}
else
{
sBuilder.Append("\n");
}
sBuilder.Append("\n\n");
sBuilder.Append("x-amz-date:").Append(date).Append("\n");
sBuilder.Append(file);
string stringToSign = sBuilder.ToString();
//Console.WriteLine(stringToSign);
var signature = Convert.ToBase64String(new HMACSHA1(Encoding.UTF8.GetBytes(Password)).ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var uri = new Uri(BaseURL, file);
var request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = method;
if (content != null)
{
request.ContentType = contentType;
request.ContentLength = content.Length;
}
var headers = request.Headers;
headers.Add("x-amz-date", date);
if (content != null)
headers.Add("Content-MD5", hash);
headers.Add("Authorization", string.Format("AWS {0}:{1}", UserID, signature));
if (content != null)
{
try
{
WriteToRequestWorkaround(request, content);
}
catch (WebException e)
{
Console.WriteLine(e.Message);
return null;
}
}
HttpWebResponse response;
try
{
response = request.GetResponse() as HttpWebResponse;
}
catch (WebException e)
{
Console.WriteLine(e.Message);
response = e.Response as HttpWebResponse;
}
return response;
}
private class SerializationBinderWithoutAssembly : SerializationBinder
{
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.FullName;
}
public override Type BindToType(string assemblyName, string typeName)
{
return Type.GetType(typeName);
}
}
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Binder = new SerializationBinderWithoutAssembly()
};
public bool GetObject<T>(string pathName, string objectName, out T data)
{
data = default(T);
byte[] bytes;
if (!GetObject(pathName, objectName, out bytes))
return false;
data = JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(bytes), _jsonSettings);
return true;
}
public bool GetObject(string pathName, string objectName, out byte[] data)
{
data = null;
CheckParameters();
var response = Request("GET", pathName, objectName, null);
if (response == null)
return false;
if (response.StatusCode == HttpStatusCode.OK)
{
data = new byte[(int)response.ContentLength];
ReadFromResponseWorkaround(response, data);
return true;
}
var content = new byte[0];
ReadFromResponseUntilLineWorkaround(response, out content);
string responseContent = Encoding.UTF8.GetString(content);
//var xmlResponse = XDocument.Parse(responseContent);
Console.WriteLine(response.StatusCode);
Console.WriteLine(responseContent);
//TODO: extract error message from response
return false;
}
public bool PutObject(string pathName, string objectName, object data)
{
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data, _jsonSettings));
return PutObject(pathName, objectName, bytes);
}
public bool PutObject(string pathName, string objectName, byte[] data)
{
CheckParameters();
var response = Request("PUT", pathName, objectName, data);
if (response == null)
return false;
if (response.StatusCode == HttpStatusCode.OK
|| response.StatusCode == HttpStatusCode.Accepted)
return true;
var content = new byte[0];
ReadFromResponseUntilLineWorkaround(response, out content);
string responseContent = Encoding.UTF8.GetString(content);
//var xmlResponse = XDocument.Parse(responseContent);
Console.WriteLine(response.StatusCode);
Console.WriteLine(responseContent);
//TODO: extract error message from response
return false;
}
}
}using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.CSharp
{
sealed class S3State
{
public interface Serializable
{
string GetKey();
object SerializeState();
bool DeserializeState(object o);
}
private readonly QCAlgorithm _algo;
private readonly S3Client _s3Client;
public string AlgoID
{
get; set;
}
public string Bucket
{
get; set;
}
public S3State(QCAlgorithm algo, S3Client client)
{
_algo = algo;
_s3Client = client;
AlgoID = "untitled";
Bucket = "examplebucket";
}
public void UploadWarmupState(IEnumerable<Serializable> objects)
{
if (_algo.LiveMode)
return; //shouldn't be true from where it's called but check anyway
//reasoning is that we might have several live versions of algo running and it's better to warm up from controlled backtest
Dictionary<string, object> states;
if (_s3Client.GetObject(Bucket, AlgoID + ".js", out states))
{
object lastDateObject;
if (states.TryGetValue("LastDate", out lastDateObject) && lastDateObject is DateTime)
{
var lastDate = (DateTime)lastDateObject;
if (_algo.Time.Date < lastDate.Date)
{
Print("Not uploading serialized state, there is already a newer state");
return;
}
}
}
states = new Dictionary<string, object>();
states["LastDate"] = _algo.Time.Date;
foreach (var tradable in objects)
{
string ID = tradable.GetKey();
object tradableState = tradable.SerializeState();
if (tradableState == null)
{
Print("Not uploading serialized state, too little warm up time for " + ID);
return;
}
states[ID] = tradableState;
}
if (_s3Client.PutObject(Bucket, AlgoID + ".js", states))
Print("Uploaded serialized state to " + Bucket);
else
Print("Unable to upload serialized state to " + Bucket);
}
public void DownloadWarmupState(IEnumerable<Serializable> objects)
{
Dictionary<string, object> states;
if (!_s3Client.GetObject(Bucket, AlgoID + ".js", out states))
{
string msg = "Unable to download warmup state from " + Bucket;
if (_algo.LiveMode)
throw new Exception(msg);
else
_algo.Debug(msg);
return;
}
object lastDateObject;
if (states.TryGetValue("LastDate", out lastDateObject) && lastDateObject is DateTime)
{
var lastDate = (DateTime)lastDateObject;
double daysDifference = (_algo.Time.Date - lastDate.Date).TotalDays;
if (daysDifference > 4)
{
string msg = "Algo state on " + Bucket + " seems very old (" + daysDifference + " days)";
if (_algo.LiveMode)
throw new Exception(msg);
else
_algo.Debug(msg);
}
else if (daysDifference < -1 && !_algo.LiveMode)
{
string msg = "Algo state on " + Bucket + " is in the future (" + -daysDifference + " days), skipping";
Print(msg);
return;
}
}
states.Remove("LastDate");
var tradablesRemaining = new List<Serializable>();
tradablesRemaining.AddRange(objects);
foreach (var kv in states)
{
var tradable = tradablesRemaining.Find(x => x.GetKey() == kv.Key);
if (tradable != null)
{
if (tradable.DeserializeState(kv.Value))
tradablesRemaining.Remove(tradable);
}
}
if (tradablesRemaining.Count > 0)
{
string msg = "There wasn't correct algo state to deserialize for all tradables, e.g. " + tradablesRemaining[0].GetKey();
if (_algo.LiveMode)
throw new Exception(msg);
else
_algo.Debug(msg);
}
Print("Downloaded warmup state from " + Bucket);
}
private void Print(string msg)
{
if (_algo.LiveMode)
_algo.Log(msg);
else
_algo.Debug(msg);
}
}
}using System;
namespace QuantConnect.Algorithm.CSharp
{
//new(): because references are atomic, and we wish to support calling DownloadParameters() from another thread while using Parameters property
sealed class S3ParamSet<ParamType> where ParamType : new()
{
private readonly QCAlgorithm _algo;
private readonly S3Client _s3Client;
public string ParamSetID
{
get; set;
}
public string Bucket
{
get; set;
}
public bool ThrowOnErrorBeforeOnceSuccessful
{
get; set;
}
public ParamType Parameters
{
get { return _params; }
}
private ParamType _params = new ParamType();
private bool _lastSuccess = true;
private bool _anySuccess = false;
public S3ParamSet(QCAlgorithm algo, S3Client client)
{
_algo = algo;
_s3Client = client;
ThrowOnErrorBeforeOnceSuccessful = true;
ParamSetID = "config";
Bucket = "examplebucket";
}
public bool DownloadParameters()
{
string filename = ParamSetID;
if (_algo.LiveMode)
filename += ".live";
else
filename += ".test";
filename += ".js";
string path = Bucket + "/" + filename;
ParamType newParams;
if (_s3Client.GetObject(Bucket, filename, out newParams))
{
_params = newParams;
_lastSuccess = true;
_anySuccess = true;
}
else
{
if (_lastSuccess)
{
string msg = "Unable to download parameters from " + path;
if (ThrowOnErrorBeforeOnceSuccessful && !_anySuccess)
throw new Exception(msg);
else
_algo.Error(msg);
}
_lastSuccess = false;
}
return _lastSuccess;
}
}
}