Второй вызов reqContractDetails в TWS API не отправляет никаких уведомлений и зависает
Попытка создать вид рынка сканера. Код ниже должен вернуть цепочку опционных контрактов. Call to TWS API - это асинхронный метод, который возвращает некоторые данные, только если я получаю ContractEnd или Error от TWS. При первом вызове reqContractDetails() он работает как положено, я получаю список контрактов, получаю сообщение "ContractEnd" и выхожу из метода.
препятствие
По какой-то причине при втором вызове reqContractDetails() я не получаю никаких уведомлений от TWS. Я должен остановить и перезапустить свое приложение, инициируя новое подключение к серверу, чтобы оно снова заработало.
Обновить
После рефакторинга моего кода я получаю сообщение об ошибке при втором вызове, который говорит: "Невозможно прочитать за пределами потока". Стек вызовов выглядит следующим образом.
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;
}
}
1 ответ
Поскольку никто не может ответить, даже сам 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;
}
}