TD Ameritrade .NET

4k Views Asked by At

Has anyone gotten the TD Ameritrade streaming API to work with C#? They have some documentation and JavaScript examples here https://developer.tdameritrade.com/content/streaming-data. I have gotten the JavaScript examples to work on https://js.do/, but can't get anything similar to work in .NET. This is a shortened version of what I'm trying to do. I can't include exactly what I'm sending because I'm trying to send the login message which includes account information, but I can say that I copy and pasted the exact JSON message that is working in my JavaScript tests into the file LoginJSON.txt in this example. In this example the socket will just close as soon as I send the message, no text response at all. If however I send an intentionally malformatted message I'll actually get text response saying the message is malformatted and then get a socket disconnect. Their support has been non-responsive which I understand to the the norm. There are some python examples here https://www.youtube.com/channel/UCBsTB02yO0QGwtlfiv5m25Q, but I've watched them all and haven't learned anything to help me get my code working.

        ClientWebSocket socket = new ClientWebSocket();
        var connectAsync = socket.ConnectAsync(new Uri("wss://streamer-ws.tdameritrade.com/ws"), CancellationToken.None);
        string loginRequest;
        using (StreamReader re = new StreamReader("LoginJSON.txt")) {
            loginRequest = re.ReadToEnd();
        }

        connectAsync.Wait();

        Thread readThread = new Thread(
            delegate(object obj)
            {
                while (true) {
                    if (socket.State == WebSocketState.Open) {
                        Console.Out.WriteLine("Waiting");
                        byte[] recBytes = new byte[1024];
                        var clientBuffer = new ArraySegment<byte>(recBytes);
                        var receiveAsync = socket.ReceiveAsync(clientBuffer, CancellationToken.None);
                        receiveAsync.Wait();
                        switch (receiveAsync.Result.MessageType) {
                            case WebSocketMessageType.Text:
                                var s = Encoding.UTF8.GetString(recBytes);
                                Console.Out.WriteLine(s.Trim());
                                break;
                            case WebSocketMessageType.Close:
                                Console.Out.WriteLine("Close message received");
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                }
            });

        readThread.Start();
        socket.SendAsync(Encoding.UTF8.GetBytes(loginRequest), WebSocketMessageType.Text, true, CancellationToken.None);
        Console.ReadLine();
5

There are 5 best solutions below

4
On BEST ANSWER

I faced the same problem and I managed to solve it. In my case, the timestamp was not prepared correctly, it is necessary to calculate the timestamp to get the TokenTimestamp property, which should be converted to universal time. Sorry for my english from google translate. :) Here is the correct code:

DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime tokenDate = Convert.ToDateTime(userPrincipal.StreamerInfo.TokenTimestamp);
TimeSpan tokenEpoch = tokenDate.ToUniversalTime() - epoch;
long timestamp = (long)Math.Floor(tokenEpoch.TotalMilliseconds);

var credentials = new Credentials
{
    userid = userPrincipal.Accounts[0].AccountId,
    token = userPrincipal.StreamerInfo.Token,
    company = userPrincipal.Accounts[0].Company,
    segment = userPrincipal.Accounts[0].Segment,
    cddomain = userPrincipal.Accounts[0].AccountCdDomainId,
    usergroup = userPrincipal.StreamerInfo.UserGroup,
    accesslevel = userPrincipal.StreamerInfo.AccessLevel,
    authorized = "Y",
    timestamp = timestamp,
    appid = userPrincipal.StreamerInfo.AppId,
    acl = userPrincipal.StreamerInfo.Acl
};
var credentialArr = credentials.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => new KeyValuePair<string, string>(p.Name, p.GetValue(credentials, null).ToString()));
var loginRequest = new Request
{
    service = "ADMIN",
    command = "LOGIN",
    requestid = "0",
    account = userPrincipal.Accounts[0].AccountId,
    source = userPrincipal.StreamerInfo.AppId,
    parameters = new Parameters
    {
        credential = string.Join("&", credentialArr.Where(c => !string.IsNullOrWhiteSpace(c.Value)).Select(c => string.Format("{0}={1}", HttpUtility.UrlEncode(c.Key, Encoding.UTF8), HttpUtility.UrlEncode(c.Value, Encoding.UTF8)))),
        token = userPrincipal.StreamerInfo.Token,
        version = "1.0",
        qoslevel = "0"
    }
};
var req = JsonConvert.SerializeObject(Requests.ToRequests(loginRequest), Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
socketClient = new WebSocket(string.Format("wss://{0}/ws", userPrincipal.StreamerInfo.StreamerSocketUrl));
if(Environment.OSVersion.Version.Major > 5)
{
    socketClient.SslConfiguration.EnabledSslProtocols = (System.Security.Authentication.SslProtocols)3072;
    socketClient.SslConfiguration.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
}
socketClient.Connect();
socketClient.Send(req);
0
On

@Mikhail, would you share your code for getting user principals. this is my code but I get status=401 even though my access token is valid(I have tested it via API page):

    using System;
    using WebSocketSharp;
    using System.Net.Http;
    using System.Threading.Tasks;

namespace TdLogin
{
    class Program
    {
        static async Task  Main(string[] args)
        {
            string accessToken = util.accessToken; // get the access token from util 
            Console.WriteLine("Hello World!");
            var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken);
            var result = await client.GetAsync("https://api.tdamer`enter code here`itrade.com/v1/userprincipals?fields=streamerSubscriptionKeys%2CstreamerConnectionInfo");
            Console.WriteLine("status= {0}", result.StatusCode);
            Console.WriteLine(result.Content);
            Console.ReadKey();
        }
    }
}
0
On

Here is a C# API on GitHub. They also support other languages: C#, Python, JavaScript, C++ and Ruby. https://github.com/td-ameritrade

I do not know if this is officially from TD Ameritrade.

2
On

I tried the WebSocketClient approach and never got it working as well. I got exactly the same errors you are getting. Exactly. I found that WebSocketClient actually complicates what is very simple to implement in javascript. Just have your C# call a javascript function to execute the javascript and send the response back to you. I've got it working that way using C# in a Blazor app, and it works seamlessly.

1
On

Well TDA Quotes are always going to be in (New York) Eastern Time because it's the NYSE standard. Which is why old watches would display more than one time.

I haven't really played with streaming yet, but worked through the epoch time. I am in Eastern time, so I don't have to deal with conversions. Therefore, the following conversions are not battle tested, but the following C# methods may help someone that stumbles on this thread in the future.

    private static readonly TimeZoneInfo TDAServerTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
    public static DateTime ToServerTime(this DateTime dateTime)
    {
        return TimeZoneInfo.ConvertTime(dateTime, TDAServerTimeZone);
    }

    public static DateTime ToLocalTime(this DateTime dateTime)
    {
        TimeZoneInfo LocalTimeZone = TimeZoneInfo.Local;
        return TimeZoneInfo.ConvertTime(dateTime, TDAServerTimeZone, LocalTimeZone);
    }

    public static DateTime ToUTCTime(this DateTime dateTime)
    {
        TimeZoneInfo UTCTimeZone = TimeZoneInfo.Utc;
        return TimeZoneInfo.ConvertTime(dateTime, TDAServerTimeZone, UTCTimeZone);
    }

    public static DateTime FromUnixTime(long unixTime)
    {
        DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0);
        return epoch.AddMilliseconds(unixTime);
    }

    public static long ToUnixTime(DateTime dtConvert)
    {
        TimeSpan t = dtConvert - new DateTime(1970, 1, 1);
        long SinceEpoch = (long)t.TotalMilliseconds;
        return SinceEpoch;
    }