Skip to content

Commit

Permalink
Allow getting scraped yahoo finace closing price data when not connec…
Browse files Browse the repository at this point in the history
…ted to TWS (message box displayed when TWS connect fails allowing the user the choice), or when connected to TWS but after hours and without correct market data subscription.
  • Loading branch information
PaulSquires committed Aug 19, 2023
1 parent fe710a8 commit b21f566
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 5 deletions.
2 changes: 1 addition & 1 deletion IB-Tracker/src/Config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ SOFTWARE.

#pragma once

constexpr std::wstring version = L"2.0.1";
constexpr std::wstring version = L"2.1.0";

bool SaveConfig();
bool LoadConfig();
Expand Down
133 changes: 130 additions & 3 deletions IB-Tracker/src/MainWindow/tws-client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,106 @@ SOFTWARE.
std::atomic<bool> isThreadPaused = false;
std::atomic<bool> isMonitorThreadActive = false;

bool MarketDataSubscriptionError = false;

TwsClient client;



//
// If not connected to TWS or after hours and no market data then attmept to scrape yahoo finance
// to get the closing price of the stock.
//
double GetScrapedClosingPrice(std::wstring wszTickerSymbol)
{
static std::wstring curlCommand = L"C:/Windows/System32/curl.exe";
static bool curlExists = AfxFileExists(curlCommand);
if (!curlExists) return 0;

std::wstring cmd = L"C:/Windows/System32/curl.exe \"https://query1.finance.yahoo.com/v7/finance/download/" + wszTickerSymbol + L"?interval=1d&events=history\"";
std::wstring wszText = AfxExecCmd(cmd);

std::wstring wszClosingPrice = L"";

// Get the last line and parse for the closing price
if (wszText.size()) {
std::vector<std::wstring> lines = AfxSplit(wszText, L"\n");
std::wstring wszLastLine = lines.at(lines.size() - 1);
std::vector<std::wstring> prices = AfxSplit(wszLastLine, L",");
try { wszClosingPrice = prices.at(4); }
catch (...) {}
}

return AfxValDouble(wszClosingPrice);
}


//
// Could not connect to TWS so we don't have any market data. Allow user the opportunity to try to get scraped data.
//
void UpdateTickersWithScrapedData()
{
std::unordered_map<std::wstring, double> mapPrices;

HWND hListBox = GetDlgItem(HWND_ACTIVETRADES, IDC_TRADES_LISTBOX);

int lbCount = ListBox_GetCount(hListBox);

for (int nIndex = 0; nIndex < lbCount; nIndex++) {
ListBoxData* ld = (ListBoxData*)ListBox_GetItemData(hListBox, nIndex);
if (ld == nullptr) continue;

if ((ld->trade != nullptr) && (ld->lineType == LineType::TickerLine)) {

if (ld->trade->tickerLastPrice == 0 && ld->trade->tickerClosePrice == 0) {

std::wstring wszTickerSymbol = ld->trade->tickerSymbol;
if (wszTickerSymbol == L"SPX") wszTickerSymbol = L"^SPX";
if (wszTickerSymbol == L"NDX") wszTickerSymbol = L"^NDX";
if (wszTickerSymbol == L"RUT") wszTickerSymbol = L"^RUT";
if (wszTickerSymbol == L"VIX") wszTickerSymbol = L"^VIX";
if (IsFuturesTicker(wszTickerSymbol)) {
wszTickerSymbol = wszTickerSymbol.substr(1);
if (wszTickerSymbol == L"AUD") wszTickerSymbol = L"6A";
if (wszTickerSymbol == L"GBP") wszTickerSymbol = L"6B";
if (wszTickerSymbol == L"EUR") wszTickerSymbol = L"6E";
if (wszTickerSymbol == L"CAD") wszTickerSymbol = L"CD";
if (wszTickerSymbol == L"JPY") wszTickerSymbol = L"JY";
if (wszTickerSymbol == L"CHF") wszTickerSymbol = L"SF";
if (wszTickerSymbol == L"INR") wszTickerSymbol = L"IR";
wszTickerSymbol = wszTickerSymbol + L"=F";
}

// Lookup our map to see if we have already retrieved the price from a previous scrap. If
// yes, then use that value rather than doing yet another scrap.
if (mapPrices.count(wszTickerSymbol)) {
ld->trade->tickerClosePrice = mapPrices.at(wszTickerSymbol);
}
else {
ld->trade->tickerClosePrice = GetScrapedClosingPrice(wszTickerSymbol);
mapPrices[wszTickerSymbol] = ld->trade->tickerClosePrice;
}


std::wstring wszText = AfxMoney(ld->trade->tickerClosePrice, false, ld->trade->tickerDecimals);
ld->SetTextData(COLUMN_TICKER_CURRENTPRICE, wszText, COLOR_WHITELIGHT); // current price

// Do calculation to ensure column widths are wide enough to accommodate the new
// price data that has just arrived.
ListBoxData_ResizeColumnWidths(hListBox, TableType::ActiveTrades, nIndex);

RECT rc{};
ListBox_GetItemRect(hListBox, nIndex, &rc);
InvalidateRect(hListBox, &rc, TRUE);
UpdateWindow(hListBox);

}
}
}
}



//
// Thread function
//
Expand Down Expand Up @@ -138,9 +235,14 @@ bool tws_connect()

if (res == false) {
SendMessage(HWND_SIDEMENU, MSG_TWS_CONNECT_FAILURE, 0, 0);
std::wstring wszText =
L"Could not connect to TWS.\n\nConfirm in TWS, File->Global Configuration->API->Settings menu that 'Enable ActiveX and Client Sockets' is enabled and connection port is set to 7496.";
MessageBox(HWND_ACTIVETRADES, wszText.c_str(), L"Connection Failed", MB_OK | MB_ICONEXCLAMATION);
std::wstring wszText =
L"Could not connect to TWS.\n\n" \
"Confirm in TWS, File->Global Configuration->API->Settings menu that 'Enable ActiveX and Client Sockets' is enabled and connection port is set to 7496.\n\n" \
"Do you wish to retrieve closing price quotes from scraped Yahoo Finance data?";
if (MessageBox(HWND_ACTIVETRADES, wszText.c_str(), L"Connection Failed", MB_YESNOCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON2) == IDYES) {
UpdateTickersWithScrapedData();
}
return false;
}

return res;
Expand Down Expand Up @@ -425,6 +527,8 @@ void TwsClient::tickGeneric(TickerId tickerId, TickType tickType, double value)
}




void TwsClient::tickPrice(TickerId tickerId, TickType field, double price, const TickAttrib& attribs) {
if (isThreadPaused) return;

Expand Down Expand Up @@ -477,6 +581,8 @@ void TwsClient::tickPrice(TickerId tickerId, TickType field, double price, const
ld->trade->tickerClosePrice = price;
}



// Calculate the price change
double delta = 0;
if (ld->trade->tickerClosePrice != 0) {
Expand Down Expand Up @@ -625,8 +731,19 @@ void TwsClient::error(int id, int errorCode, const std::string& errorString, con
return;
}
printf("Error. Id: %d, Code: %d, Msg: %s\n", id, errorCode, errorString.c_str());


// If error codes 10091 or 10089 then we are connected most likely after hours and we do not have
// access to streaming data. In this case we will attempt scrap for the closing price.
switch (errorCode) {
case 10089:
case 10091:
MarketDataSubscriptionError = true;
return;
}
}


void TwsClient::position(const std::string& account, const Contract& contract, Decimal position, double avgCost)
{
// This callback is initiated by the reqPositions() call via the clicking on Reconcile button.
Expand All @@ -639,6 +756,16 @@ void TwsClient::positionEnd()
// the position callback.
m_pClient->cancelPositions();
Reconcile_positionEnd();

// We have finished requesting positions. It is possible that some position closing prices were
// not retrieved because maybe we are connected but it is after hours and we need additional
// subscriptions to access the data.
// Check global variable for the market subscription error and then attempt scraping from yahoo finance.
if (MarketDataSubscriptionError) {
UpdateTickersWithScrapedData();
MarketDataSubscriptionError = false;
}

}

void TwsClient::tickOptionComputation(TickerId tickerId, TickType tickType, int tickAttrib, double impliedVol, double delta,
Expand Down
75 changes: 75 additions & 0 deletions IB-Tracker/src/Utilities/AfxWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,81 @@ SOFTWARE.
#include "AfxWin.h"


//
// Execute a command and get the results. (Only standard output)
//
std::wstring AfxExecCmd(std::wstring cmd) // [in] command to execute
{
std::wstring strResult;
HANDLE hPipeRead, hPipeWrite;

SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process.
saAttr.lpSecurityDescriptor = NULL;

// Create a pipe to get results from child's stdout.
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
return strResult;
}

STARTUPINFOW si = { sizeof(STARTUPINFOW) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing.
// Requires STARTF_USESHOWWINDOW in dwFlags.

PROCESS_INFORMATION pi = { 0 };

BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)cmd.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!fSuccess)
{
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return strResult;
}

bool bProcessEnded = false;
for (; !bProcessEnded;)
{
// Give some timeslice (50 ms), so we won't waste 100% CPU.
bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;

// Even if process exited - we continue reading, if
// there is some data available over pipe.
for (;;)
{
char buf[1024];
DWORD dwRead = 0;
DWORD dwAvail = 0;

if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
break;

if (!dwAvail) // No data available, return
break;

if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
// Error, the child process might ended
break;

buf[dwRead] = 0;

const size_t WCHARBUF = 1024;
wchar_t wszDest[WCHARBUF];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buf, -1, wszDest, WCHARBUF);
strResult += wszDest;
}
}

CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return strResult;
}


// ========================================================================================
// Redraws the specified window.
// Do not use it from within a WM_PAINT message.
Expand Down
3 changes: 3 additions & 0 deletions IB-Tracker/src/Utilities/AfxWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ SOFTWARE.
#include <string>
#include <winver.h>


std::wstring AfxExecCmd(std::wstring cmd);

void AfxRedrawWindow(HWND hwnd);
std::wstring AfxGetWindowText(HWND hwnd);
bool AfxSetWindowText(HWND hwnd, const std::wstring& wszText);
Expand Down
3 changes: 2 additions & 1 deletion IB-Tracker/src/Utilities/ListBoxData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,9 @@ void ListBoxData_RequestMarketData(HWND hListBox)
}
}

if (tws_isConnected())
if (tws_isConnected()) {
PrevMarketDataLoaded = true;
}


// Request portfolio updates. This also loads the IBKR and local vectors that
Expand Down
3 changes: 3 additions & 0 deletions IB-Tracker/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")





void BindStdHandlesToConsole()
{
// Redirect the CRT standard input, output, and error handles to the console
Expand Down

0 comments on commit b21f566

Please sign in to comment.