第二次调用 TWS API 中的 reqContractDetails 未发送任何通知并挂起

Second call to reqContractDetails in TWS API doesn't send any notification and hangs

正在尝试创建一种市场扫描仪。下面的代码应该是 return 期权合约链。调用 TWS API 是一种异步方法,仅当我从 TWS 获得 ContractEnd 或错误响应时,才会 return 发送一些数据。在第一次调用 reqContractDetails() 时,它按预期工作,我得到合同列表,收到消息 "ContractEnd",然后退出该方法。

障碍物

出于某种原因,在第二次调用 reqContractDetails() 时,我没有收到来自 TWS 的任何通知。我必须停止并重新启动我的应用程序,启动与服务器的新连接以使其再次工作。

更新

重构我的代码后,我在第二次调用时遇到错误 "Unable to read beyond the end of the stream"。调用堆栈看起来是这样的。

IBLibrary.dll!IBLibrary.OptionService.GetOptionsChain.AnonymousMethod__3(IBLibrary.Messages.ErrorMessage data) Line 64
IBLibrary.dll!IBLibrary.Classes.Client.error(string str) Line 42
CSharpAPI.dll!IBApi.EReader.putMessageToQueue() Line 94
CSharpAPI.dll!IBApi.EReader.Start.AnonymousMethod__9_0() Line 48

我在 C# 中实现的包装器

public class BaseService : IDisposable
{
  protected Client Sender { get; set; }
  protected EReader Receiver { get; set; }

  public BaseService()
  {
    Sender = new Client();
    Sender.Socket.eConnect("127.0.0.1", 7496, 0);
    Receiver = new EReader(Sender.Socket, Sender.Signal);
    Receiver.Start();

    var process = new Thread(() =>
    {
      while (Sender.Socket.IsConnected())
      {
        Sender.Signal.waitForSignal();
        Receiver.processMsgs();
      }
    })
    {
      IsBackground = true
    };

    process.Start();
  }

  public void Dispose()
  {
    Sender.Socket.eDisconnect();
  }
}

public class OptionService : BaseService
{
  public Task<List<OptionModel>> GetOptionsChain(OptionModel query)
  {
    if (query == null)
    {
      query = new OptionModel();
    }

    var process = Task.Run(() =>
    {
      var done = false;
      var id = new Random(DateTime.Now.Millisecond).Next();

      var contract = new Contract
      {
        Symbol = query.Symbol,
        SecType = "OPT",
        Exchange = "SMART",
        Currency = "USD",
        LastTradeDateOrContractMonth = query.Expiration
      };

      var contracts = new List<OptionModel>();

      Action<ErrorMessage> errorMessage = null;
      Action<ContractDetailsMessage> contractMessage = null;
      Action<ContractDetailsEndMessage> contractMessageEnd = null;

      contractMessage = (ContractDetailsMessage data) =>
      {
        contracts.Add(new OptionModel
        {
          Symbol = data.ContractDetails.Contract.Symbol,
          Right = data.ContractDetails.Contract.Right,
          Strike = data.ContractDetails.Contract.Strike,
          Expiration = data.ContractDetails.RealExpirationDate
        });
      };

      // I receive this message at first, but not the second time

      contractMessageEnd = (ContractDetailsEndMessage data) =>
      {
        done = true;
      };

      errorMessage = (ErrorMessage data) =>
      {
        var notifications = new List<int>
        {
          (int) ErrorCode.MarketDataFarmConnectionIsOK,
          (int) ErrorCode.HmdsDataFarmConnectionIsOK
        };

        if (notifications.Contains(data.ErrorCode) == false)
        {
          done = true;
        }
      };

      Sender.ErrorEvent += errorMessage;
      Sender.ContractDetailsEvent += contractMessage;
      Sender.ContractDetailsEndEvent += contractMessageEnd;
      Sender.Socket.reqContractDetails(id, contract);

      // Execute method until we get all contracts
      // The econd call to reqContractDetails doesn't return 
      // any notification, so obviously this line hangs forever

      while (done == false);

      Sender.ErrorEvent -= errorMessage;
      Sender.ContractDetailsEvent -= contractMessage;
      Sender.ContractDetailsEndEvent -= contractMessageEnd;

      return contracts;
    });

    return process;
  }
}

就没有人有答案,甚至 IB 本身,我看到的唯一解决方案是,将我的 API 控制器转换为同步控制器,并在每次请求后关闭与 IB 服务器的套接字连接。

旧版本。

public class ServiceOptionsController : BaseServiceController
{
  OptionService Service = new OptionService();

  [AcceptVerbs("POST")]
  public async Task<List<OptionModel>> Options([FromBody] dynamic data)
  {
    var selectors = data.ToObject<QueryModel>();

    var optionModel = new OptionModel
    {
      Symbol = "MSFT",
      Expiration = "201806"
    };

    var processes = new List<Task<List<OptionModel>>>
    {
      Service.GetOptionsChain(optionModel)
    };

    return (await Task.WhenAll(processes)).SelectMany(o => o).ToList();
  }
}

工作版本。

public class ServiceOptionsController : BaseServiceController
{
  [AcceptVerbs("POST")]
  public List<OptionModel> Options([FromBody] dynamic data)
  {
    var selectors = data.ToObject<QueryModel>();

    var optionModel = new OptionModel
    {
      Symbol = "MSFT",
      Expiration = "201806"
    };

    var optionService = new OptionService();

    var processes = new List<Task<List<OptionModel>>>
    {
      optionService.GetOptionsChain(optionModel)
    };

    var items = Task.WhenAll(processes).Result.SelectMany(o => o).ToList();

    optionService.Dispose(); // Ridiculous fix for ridiculous API

    return items;
  }
}