using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using TradeIdeas.MiscSupport;
using TradeIdeas.ServerConnection;
using TradeIdeas.TIProData.Interfaces;
using TradeIdeas.XML;
namespace TradeIdeas.TIProData
{
///
/// This class requests a grid from the server. This takes care of a lot of details, like automatic
/// retries. The public interface to this class should only be called from the GUI thread, and the
/// callback will return in the GUI thread.
///
/// This required data from the TIQ server. This will only work if you are connected to the proxy.
///
/// This could be used in other places. I almost put this into the TIProData library. But this
/// doesn't follow the conventions of that library. In particular, this class uses the Control
/// class, and that library doesn't reference any GUI stuff, so it wouldn't compile.
///
public class SingleGridRequest
{
[XmlRoot("API")]
public class SingleGridResponse
{
[XmlAttribute("symbol")]
public string Symbol;
[XmlAttribute("type")]
public string Type;
[XmlText]
public string CsvData;
};
public class Response
{
public string Symbol;
public string CsvData;
public bool IsPrimarySymbol { get; set; }
public bool IsComparisonSymbol => !IsPrimarySymbol;
public bool IsValid => !string.IsNullOrEmpty(Symbol) && !string.IsNullOrEmpty(CsvData);
};
public readonly string PrimarySymbol;
public List ComparisonSymbols;
public string AllSymbolsMessageRequest
{
get
{
var allSymbols = new List { PrimarySymbol };
if (ComparisonSymbols != null && ComparisonSymbols.Any())
{
allSymbols.AddRange(ComparisonSymbols);
}
return string.Join(" ", allSymbols);
}
}
public int ExpectedCallbackCount
{
get
{
var primarySymbolCount = 1;
var comparisonSymbolCount = ComparisonSymbols != null ? ComparisonSymbols.Count() : 0;
return primarySymbolCount + comparisonSymbolCount;
}
}
private Dictionary _message;
private Control _owner;
private ISendManager _sendManager;
private Action _callBack;
///
/// Send a request to the server.
///
/// Request data for this symbol.
/// Probably the output of PrototypeEditor.GetTcl().
/// We don't use the exact values of row_count or at_time in
/// the prototype. You can leave these blank.
/// You want this many good rows of data. If you set pack=0
/// in the prototype you might get a lot of empty candles mixed
/// in with the output. Either way, the empty candles don't
/// count toward row_count
/// We should grab this many additional candles before the candles
/// that we plan to show someone. For example, if you are including a 5 period SMA, that field
/// might not work well for the first 5 candles of the result. In this case set row_count to
/// the number of rows you plan to display, and set extra_row_count to 5. The default
/// value for this is 0. Like row_count this only countss candles with valid data, regardless of the
/// "pack" setting in the "prototype" field.
/// If you already have some data, but you are requesting
/// more history, start_before should be the start time
/// of the oldest candle you already have. The result will
/// start at the candle immediately before this one and go
/// backward as far as it needs to. The default is the
/// current time. If you don't fill this in the server will
/// give you the most recent data it has.
/// Allows using the chart_data function
/// to return all bars since a certain date.
/// Some indicators are anchored at a certain time (anchored
/// VWAP) so using extra_row_count won't work well. Use this
/// parameter to tell chart_data to go back at least to this
/// time.
/// Use this to send the request.
///
/// Use this to move server responses into the correct thread. Also, if this is a
/// form and the form closes, the callback and any automatic retries are
/// automatically canceled. If this is null, everything will still work except for
/// the automatic cancel.
///
///
/// This will be called when the data comes from the server. This will only be called
/// once. This will be called in the GUI thread.
///
public SingleGridRequest(string primarySymbol, string prototype, int rowCount, int extraRowCount,
DateTime? startBefore, DateTime? sinceDate, DateTime? oldestAnchor, ISendManager sendManager, Control owner, Action callBack, List comparisonSymbols = null)
{
PrimarySymbol = primarySymbol;
ComparisonSymbols = comparisonSymbols;
_owner = owner ?? new Control();
_sendManager = sendManager;
_callBack = callBack;
string startDebug = "";
long? startBeforeTimeT = null;
if (startBefore.HasValue)
{
startBeforeTimeT = ServerFormats.ToTimeT(startBefore);
startDebug = startBefore.Value.ToString("yyyy-MM-dd hh:mm");
}
string sinceDebug = "";
long? sinceDateTimeT = null;
if (sinceDate.HasValue)
{
sinceDateTimeT = ServerFormats.ToTimeT(sinceDate);
sinceDebug = sinceDate.Value.ToString("yyyy-MM-dd hh:mm");
}
long? oldestAnchorTimeT = null;
if (oldestAnchor.HasValue)
oldestAnchorTimeT = ServerFormats.ToTimeT(oldestAnchor.Value);
const string COMMAND = "candles_command";
const string SUB_COMMAND = "chart_data";
var message = new object[]
{
"command", COMMAND,
"subcommand", SUB_COMMAND,
"prototype", prototype,
"symbols", this.AllSymbolsMessageRequest,
"start_before", startBeforeTimeT,
"at_least_from", oldestAnchorTimeT,
"since", sinceDateTimeT,
"row_count", rowCount,
"extra_row_count", extraRowCount
};
_cancelTokenForMessageRequests = new TalkWithServer.CancelToken();
_message = TalkWithServer.CreateMessage(message);
_sendManager.SendMessage(_message, GetDataResponse, streaming: true, cancelToken: _cancelTokenForMessageRequests);
}
// Cancel any callbacks. Unlike the TalkWithServer messages, this takes
// effect immediately.
public void Abort()
{
_callBack = null;
_message?.Clear();
_message = null;
_sendManager = null;
}
private TalkWithServer.CancelToken _cancelTokenForMessageRequests;
public void CancelListenerCallback()
{
if (null != _cancelTokenForMessageRequests)
{
_sendManager.CancelMessage(_cancelTokenForMessageRequests);
_cancelTokenForMessageRequests = null;
}
}
private void GetDataResponse(byte[] body, object clientId)
{
BeginInvokeIfRequired(_owner, (MethodInvoker)delegate
{
ResponseInThread(body);
});
}
private int CallbackCount = 0;
private void ResponseInThread(byte[] body)
{
if (null == _callBack)
// Aborted
return;
if (null == body)
{
// Communication problem. Try again.
_sendManager.SendMessage(_message, GetDataResponse);
// Reset our CallbackCount
CallbackCount = 0;
}
else
{
var response = PopulateResponseSymbolData(body);
if (response.IsValid)
{
CallbackCount++;
}
//If we've received the last CallBack response, cancel the message reqeust listener
if (CallbackCount == ExpectedCallbackCount)
{
CancelListenerCallback();
}
_callBack(response);
}
}
private Response PopulateResponseSymbolData(byte[] body)
{
var xmlDocument = XmlHelper.Get(body);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SingleGridResponse));
SingleGridResponse singleGridResponse = null;
using (XmlReader reader = new XmlNodeReader(xmlDocument))
{
singleGridResponse = (SingleGridResponse)serializer.Deserialize(reader);
}
var isPrimarySymbol = singleGridResponse.Symbol == this.PrimarySymbol;
var csvData = singleGridResponse.Type == "result" ? singleGridResponse.CsvData : string.Empty;
var response = new Response
{
Symbol = singleGridResponse.Symbol,
CsvData = csvData,
IsPrimarySymbol = isPrimarySymbol,
};
return response;
}
private static void BeginInvokeIfRequired(Control control, MethodInvoker code)
{
if (control.InvokeRequired)
{
try
{
// Thread.Sleep(5000); // Test for a possible race condition.
control.BeginInvoke(code);
}
catch (InvalidOperationException)
{
// This probably means that the window was disposed of after the call to InvokeRequired
// but before the call to Invoke().
// Note: Most of our code does not check for this race condition. It probably should.
// This probably doesn't happen very often.
}
}
else if (!control.IsDisposed)
code();
}
}
}