【问题标题】:TD Ameritrade .NET德美利证券.NET
【发布时间】:2020-04-25 06:25:45
【问题描述】:

有没有人让 TD Ameritrade 流媒体 API 与 C# 一起使用?他们在https://developer.tdameritrade.com/content/streaming-data 有一些文档和 JavaScript 示例。我已经让 JavaScript 示例在 https://js.do/ 上工作,但无法获得与在 .NET 中工作类似的东西。这是我正在尝试做的缩短版本。我无法准确包含我发送的内容,因为我正在尝试发送包含帐户信息的登录消息,但我可以说我将在我的 JavaScript 测试中工作的确切 JSON 消息复制并粘贴到文件 LoginJSON .txt 在此示例中。在此示例中,套接字将在我发送消息后立即关闭,根本没有文本响应。但是,如果我发送故意格式错误的消息,我实际上会收到文本响应,说明该消息格式错误,然后断开套接字连接。他们的支持没有回应,我理解这是常态。 https://www.youtube.com/channel/UCBsTB02yO0QGwtlfiv5m25Q 这里有一些 python 示例,但我都看过了,还没有学到任何东西来帮助我让我的代码正常工作。

        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();

【问题讨论】:

  • 使用像wireshark或fiddler这样的嗅探器。检查响应状态,如 200 OK 以确定错误。最好的调试方法是比较工作和非工作应用程序中第一个请求的标头。 c# 中的默认标头与其他方法不同。要解决问题,使 c# 中的标头看起来像工作应用程序中的标头。
  • @jdweng 我试过这个,但遗憾的是我不熟悉 Wire Shark 和网络捕获。我有两个应用程序的两个捕获。每个只有 20 个左右的相关条目。失败的应用程序中的最后一个条目以红色突出显示,所以我认为这是断开连接。我没有看到任何看起来像 200 OK 的东西。还有什么我可以寻找的吗?
  • 您只得到响应的正文,而不是完整的消息。您可以免费下载 wireshark 或 fiddler,它们在调试网络问题时提供了很多有用的信息。
  • @jdweng 抱歉,不熟悉我的意思是我没有太多使用它,但 Wire Shark 是我用来捕获上述网络流量的工具。我看到了诸如“Client Hello”、“Server Hello,Change Cipher Spec”、“Encrypted Handshake Message”之类的消息,以及一些“Application Data”消息。它们从一个应用程序到另一个应用程序看起来略有不同,但我不确定什么是有意义的。它们看起来从同一应用程序的一个捕获到另一个略有不同。感谢您的建议,但这可能有点超出我的想象。
  • HTTP 消息使用 TCP 作为传输层。 TCP 的最大长度约为 1500 字节。因此,每条 HTTP 消息都由一条或多条 TCP 消息组成。此外,对于安全 https,还有一个在 TCP 中完成的 TLS/SSL 安全协议。您应该首先查看第一个 http 请求标头。客户端和服务器之间存在协商,其中来自客户端的标头和网页上的标头确定操作模式。由于您的第一个请求中的标头错误,您将进入错误的模式。您需要将 c# 中的标头修改为与工作应用程序相同。

标签: c# websocket streaming stock ameritrade


【解决方案1】:

我遇到了同样的问题,我设法解决了。在我的情况下,时间戳没有正确准备,需要计算时间戳以获得 TokenTimestamp 属性,该属性应转换为通用时间。对不起,我的英语来自谷歌翻译。 :) 这是正确的代码:

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);

【讨论】:

  • 你的代码中的socketClient是什么类型的?我正在使用 System.Net.WebSockets.ClientWebSocket 并且永远无法让它工作。我没有看到一个简单的 Send 方法,只有一个 SendAsync。我会尝试完全使用您的方法,但我能够确认在 javascript 中发送完全相同的文本是有效的,包括时间戳格式。
  • 我试过很多客户端,还有在线服务websocket.org/echo.html。但是问题与您的问题相同,连接已关闭,没有错误和原因。有了这种连接行为,你可能会认为headers不正确或者WebSocket客户端不好,实际上服务器看到结构是正确的,数据还没有通过验证。我正在为 Net.Framework 4.0 使用 github.com/sta/websocket-sharp 更新了使用 Web 套接字的示例代码。
  • 我能够使用 System.Net.WebSockets.ClientWebSocket 进行登录调用。我遇到了同样的问题,结果是在 Login.Credential 上设置了时间戳。我认为发生的是在调用用户首选项期间,令牌时间戳被转换为本地时间。然后在生成登录请求时,时间戳毫秒是基于本地时间而不是UTC。我使用此stackoverflow.com/questions/5955883/… 更改了时间戳生成,并能够获得适当的登录响应。
  • 我的代码几乎相同,但使用的是不同的 Socket 客户端。切换到 WebSocket-Sharp 并添加了 conditional-if (Environment.OSVersion...) 语句,现在一切正常。谢谢。
【解决方案2】:

我尝试了 WebSocketClient 方法,但始终无法正常工作。我得到了与您完全相同的错误。确切地。我发现 WebSocketClient 实际上使在 javascript 中非常简单的实现变得复杂。只需让您的 C# 调用 javascript 函数来执行 javascript 并将响应发送回给您。我已经在 Blazor 应用程序中使用 C# 以这种方式工作,并且可以无缝运行。

【讨论】:

  • 很公平。我最终让它在 python 中工作,并且可能会使用它来弥合我的 Windows 应用程序的差距。似乎有点绕,但很高兴知道我不是唯一一个有这个问题的人。我之前使用 websockets 来处理加密货币交易市场数据并取得了一些成功,所以没有让这个工作变得令人失望。
  • 对此有 0 票,但已被标记为已接受答案。也没有代码示例。 onejbsmith 你能发布你的 Blazer 代码吗?
【解决方案3】:

@米哈伊尔, 你会分享你的代码来获取用户主体吗?这是我的代码,但我得到 status=401 即使我的访问令牌有效(我已经通过 API 页面对其进行了测试):

    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();
        }
    }
}

【讨论】:

    【解决方案4】:

    嗯,TDA 报价总是在(纽约)东部时间,因为它是纽约证券交易所的标准。这就是为什么旧手表会显示不止一次的原因。

    我还没有真正玩过流媒体,但在整个时代都工作过。我在东部时间,所以我不必处理转换。因此,以下转换未经过实战测试,但以下 C# 方法可能会帮助将来偶然发现此线程的人。

        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;
        }
    

    【讨论】:

    • 这并不能真正回答问题。如果您有其他问题,可以点击 进行提问。要在此问题有新答案时收到通知,您可以follow this question。一旦你有足够的reputation,你也可以add a bounty 来引起对这个问题的更多关注。 - From Review
    【解决方案5】:

    这是 GitHub 上的 C# API。 它们还支持其他语言:C#、Python、JavaScript、C++ 和 Ruby。 https://github.com/td-ameritrade

    我不知道这是否来自 TD Ameritrade。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多