#include #include #include #include #include #include #include #include #include #include "DataFromSpryWare.h" #include "DataFormats.h" #include #include #include #include #include #include "../for_spryware/SpryWareSimple.h" #include "../../shared/MiscSupport.h" #include "../data_framework/MarketHours.h" time_t dateFromSpryWare(Database::Date *date) { struct tm t; t.tm_year = date->GetYear() - 1900; t.tm_mon = date->GetMonth() - 1; t.tm_mday = date->GetDay(); t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0; t.tm_isdst = -1; return mktime(&t); } bool symbolIsIndex(const std::string &symbol) { if (symbol.size() > 0) { if (symbol[0] == '$') return true; } return false; } void DataFromSpryWare::connect() { std::string str; // Create the directory object pDirectory = CreateMisDirectory(); if (0==pDirectory) { fprintf(stderr,"Error: Unable to Create MisDirectory\n"); return; } // Open and parse the config file if (0 == pDirectory->GetConfigFile(str) && true==File::IsExist(str.c_str())) { fprintf(stdout,"Loaded config file %s\n", str.c_str()); } else if (0 == pDirectory->GetConfigFile(str) && false==File::IsExist(str.c_str())) fprintf(stderr,"Config file %s does not exist\n", str.c_str()); connectTable(pTableHistory, keyHistory, recordHistory, TABLE_EQUITY_HISTORY); connectTable(pTable1Min, key1Min, record1Min, TABLE_EQUITY_BAR); connectTable(pTableCorporateActions, keyCorporateActions, recordCorporateActions, TABLE_CORPORATE_ACTIONS, 1); connectTable(pTableEquityFundamental, keyEquityFundamental, recordEquityFundamental, TABLE_EQUITY_FUNDAMENTAL); connectTable(pTableIndexFundamental, keyIndexFundamental, recordIndexFundamental, TABLE_INDEX_FUNDAMENTAL); connectTable(pTableEquityPrice, keyEquityPrice, recordEquityPrice, TABLE_EQUITY_PRICE); connectTable(pTableIndexPrice, keyIndexPrice, recordIndexPrice, TABLE_INDEX_PRICE); } void DataFromSpryWare::disconnect() { pDirectory->Disconnect(pTable1Min); pDirectory->Disconnect(pTableHistory); pDirectory->Disconnect(pTableCorporateActions); pDirectory->Disconnect(pTableEquityFundamental); pDirectory->Disconnect(pTableIndexFundamental); pDirectory->Disconnect(pTableEquityPrice); pDirectory->Disconnect(pTableIndexPrice); delete pDirectory; } void DataFromSpryWare::connectTable(Database::ITable * &pTable, Database::Key * &key, Database::Record * &record, const std::string &tableName, const int &indexNumber /* = 0 */) { std::string str; std::string userName; std::string host; std::string passWord; std::string domain; unsigned rc = 0; // Create an instance of the table, and attempt to open it rc = pDirectory->Connect(tableName.c_str(), pTable, domain.c_str(), userName.c_str(), passWord.c_str(), host.c_str()); if((!pTable) || (rc != RETURN_CODE_SUCCESS)) { pTable->GetHostName(str); std::cerr << "Unable to Connect to " << tableName << ": " << rc << " - " << MIS::ERR::ToString(rc) << std::endl; exit(1); } else { pTable->GetHostName(str); std::cout << "Connected to " << tableName << " on " << str << std::endl; record = new Database::Record; record->Initialize(pTable); key = new Database::Key; key->Initialize(pTable, indexNumber); } } std::string getExchangeFromSpryWare(std::string spryWareExchange) { if(spryWareExchange == "Q") return "NASD"; else if(spryWareExchange == "N" || spryWareExchange == "P") // P is NYSE ARCA which may belong under its own list_exch at some point. return "NYSE"; else if(spryWareExchange == "A") return "AMEX"; else if(spryWareExchange == "T") return "CAT"; else if(spryWareExchange == "V") return "CAV"; else if(spryWareExchange == "U") return "OTC"; else if(spryWareExchange == "u") return "PINK"; else return ""; } void DataFromSpryWare::getFundamentalData(const std::string &symbol, FundamentalData &fundamentalData) { unsigned int rc = 0; std::string spryWareSymbol = getAlternateCanadianSymbol(symbolToSpryWare(symbol)); Database::IndexPosition position = {0}; Database::IField *field; Database::Price *price; Database::Value *value; Database::NativeType *native; unsigned long temp = 0; unsigned int temp2 = 0; double last = 0.0; fundamentalData.companyName = ""; fundamentalData.listedExchange = ""; fundamentalData.epsNetIncome = 0; fundamentalData.high52Week = 0; fundamentalData.low52Week = 0; fundamentalData.putVolume = 0; fundamentalData.callVolume = 0; fundamentalData.peRatio = 0; fundamentalData.beta = 0; fundamentalData.marketCap = 0; fundamentalData.sharesOutstanding = 0; fundamentalData.shortInterest = 0; // Calculated in getDailyBars. Always run getFundamentals before getDailyBars fundamentalData.dividend = 0; // Not available from SpryWare fundamentalData.income = 0; fundamentalData.revenues = 0; fundamentalData.assets = 0; fundamentalData.debt = 0; // Unknown purpose with realtick seemed to be EPS, and EPS was something different entirely. fundamentalData.earnings = 0; //Select the correct databases if (symbolIsIndex(symbol)) { pTableFundamental = pTableIndexFundamental; keyFundamental = keyIndexFundamental; recordFundamental = recordIndexFundamental; pTablePrice = pTableIndexPrice; keyPrice = keyIndexPrice; recordPrice = recordIndexPrice; } else { pTableFundamental = pTableEquityFundamental; keyFundamental = keyEquityFundamental; recordFundamental = recordEquityFundamental; pTablePrice = pTableEquityPrice; keyPrice = keyEquityPrice; recordPrice = recordEquityPrice; } rc = keyFundamental->FromString(spryWareSymbol); assert(rc == RETURN_CODE_SUCCESS); rc = keyPrice->FromString(spryWareSymbol); assert(rc == RETURN_CODE_SUCCESS); rc = pTableFundamental->GetEqual(*keyFundamental, *recordFundamental, false, &position); if (rc != RETURN_CODE_SUCCESS) { std::cout << " Failed to get fundamentals for \'" << spryWareSymbol << "\': " << rc << " - "<< MIS::ERR::ToString(rc) << std::endl; return; } rc = pTablePrice->GetEqual(*keyPrice, *recordPrice, false, &position); if (rc != RETURN_CODE_SUCCESS) { std::cout << " Failed to get price info for \'" << spryWareSymbol << "\': " << rc << " - "<< MIS::ERR::ToString(rc) << std::endl; return; } rc = recordFundamental->GetField(MIS::FID::ISSUER_NAME, field); assert(rc == RETURN_CODE_SUCCESS); fundamentalData.companyName = field->ToString(); rc = recordFundamental->GetField(MIS::FID::PRIMARY_EXCHANGE, field); assert(rc == RETURN_CODE_SUCCESS); fundamentalData.listedExchange = getExchangeFromSpryWare(field->ToString()); // Workaround for dually listed Canadian stocks if (fundamentalData.listedExchange.size() < 1 && symbol.size() > 4) { if(symbol.substr(symbol.size() - 4, 4) == ".CAT") fundamentalData.listedExchange = "CAT"; else if(symbol.substr(symbol.size() - 4, 4) == ".CAV") fundamentalData.listedExchange = "CAV"; } rc = recordFundamental->GetField(MIS::FID::HIGH_52, field); assert(rc == RETURN_CODE_SUCCESS); price = dynamic_cast< Database::Price * >(field); assert(price); rc = price->GetPrice(fundamentalData.high52Week); assert(rc == RETURN_CODE_SUCCESS); rc = recordFundamental->GetField(MIS::FID::LOW_52, field); assert(rc == RETURN_CODE_SUCCESS); price = dynamic_cast< Database::Price * >(field); assert(price); rc = price->GetPrice(fundamentalData.low52Week); assert(rc == RETURN_CODE_SUCCESS); rc = recordFundamental->GetField(MIS::FID::BETA, field); assert(rc == RETURN_CODE_SUCCESS); value = dynamic_cast< Database::Value * >(field); assert(value); rc = value->Get(fundamentalData.beta); assert(rc == RETURN_CODE_SUCCESS); rc = recordFundamental->GetField(MIS::FID::SHARES_OUTSTANDING, field); assert(rc == RETURN_CODE_SUCCESS); native = dynamic_cast< Database::NativeType * >(field); assert(native); rc = native->Get(temp); assert(rc == RETURN_CODE_SUCCESS); if (temp / 1000 < (unsigned int) std::numeric_limits< int >::max()) fundamentalData.sharesOutstanding = temp / 1000; else std::cerr << " Shares Outstanding Too Large: " << temp << std::endl; rc = recordFundamental->GetField(MIS::FID::EARNINGS_PER_SHARE , field); assert(rc == RETURN_CODE_SUCCESS); price = dynamic_cast< Database::Price * >(field); assert(price); rc = price->GetPrice(fundamentalData.epsNetIncome); assert(rc == RETURN_CODE_SUCCESS); rc = recordPrice->GetField(MIS::FID::PUT_VOLUME, field); assert(rc == RETURN_CODE_SUCCESS); native = dynamic_cast< Database::NativeType * >(field); assert(native); rc = native->Get(temp2); assert(rc == RETURN_CODE_SUCCESS); if (temp2 < (unsigned int) std::numeric_limits< int >::max()) fundamentalData.putVolume = temp2; else std::cerr << " Put Volume Too Large: " << temp2 << std::endl; rc = recordPrice->GetField(MIS::FID::CALL_VOLUME, field); assert(rc == RETURN_CODE_SUCCESS); native = dynamic_cast< Database::NativeType * >(field); assert(native); rc = native->Get(temp2); assert(rc == RETURN_CODE_SUCCESS); if (temp2 < (unsigned int) std::numeric_limits< int >::max()) fundamentalData.callVolume = temp2; else std::cerr << " Call Volume Too Large: " << temp2 << std::endl; rc = recordPrice->GetField(MIS::FID::LAST, field); assert(rc == RETURN_CODE_SUCCESS); price = dynamic_cast< Database::Price * >(field); assert(price); rc = price->GetPrice(last); assert(rc == RETURN_CODE_SUCCESS); if (fundamentalData.epsNetIncome > 0.0) fundamentalData.peRatio = last / fundamentalData.epsNetIncome; rc = recordFundamental->GetField(MIS::FID::SHORT_INTEREST, field); assert(rc == RETURN_CODE_SUCCESS); native = dynamic_cast< Database::NativeType * >(field); assert(native); rc = native->Get(temp2); assert(rc == RETURN_CODE_SUCCESS); if (temp2 < (unsigned int) std::numeric_limits< int >::max()) fundamentalData.shortInterest = temp2; else std::cout << " Short Interest too large, discarding: " << temp2 << std::endl; fundamentalData.marketCap = double(fundamentalData.sharesOutstanding) / 1000.0 * last; } void DataFromSpryWare::getDailyBars(const std::string &symbol, BarList &bars, FundamentalData *fundamentalData /* = NULL */) { std::string str; std::string spryWareSymbol = getAlternateCanadianSymbol(symbolToSpryWare(symbol)); std::string barSymbol; BarData bar; unsigned int rc = 0; Database::IField *field; Database::Value *value; Database::Date *date; Database::Sku *sku; Database::NativeType *volume; Database::IndexPosition position = {0}; Database::Date yearAgo, lastDate; unsigned year, month, day; yearAgo.Now(); lastDate.Now(); yearAgo.Get(year, month, day); lastDate.Set(year-1000, month, day); yearAgo.Set(year - 1, month, day); bars.clear(); rc = keyHistory->FromString(spryWareSymbol); assert(rc == RETURN_CODE_SUCCESS); rc = pTableHistory->GetEqual(*keyHistory, *recordHistory, false, &position); if (rc != RETURN_CODE_SUCCESS) { std::cout << " Failed to get Daily Bars for \'" << spryWareSymbol << "\': " << rc << " - "<< MIS::ERR::ToString(rc) << std::endl; return; } while (rc == RETURN_CODE_SUCCESS) { rc = recordHistory->GetField(MIS::FID::CLOSE_DATE , field); assert(rc == RETURN_CODE_SUCCESS); date = dynamic_cast< Database::Date * >(field); assert(date); if (*date <= lastDate) { std::cout << " Out of Order Daily Bar" << std::endl << " Current: " << date->ToString() << std::endl << " Last: " << lastDate.ToString() << std::endl; rc = pTableHistory->GetNext(*keyHistory, *recordHistory, false, &position); continue; } bar.startTime = dateFromSpryWare(date); rc = recordHistory->GetField(MIS::FID::SKU, field); assert(rc == RETURN_CODE_SUCCESS); sku = dynamic_cast< Database::Sku * >(field); assert(sku); barSymbol = sku->GetSymbol(); lastDate = *date; assert(rc == RETURN_CODE_SUCCESS); rc = recordHistory->GetField(MIS::FID::OPEN , field); assert(rc == RETURN_CODE_SUCCESS); value = dynamic_cast< Database::Value * >(field); assert(value); rc = value->Get(bar.open); assert(rc == RETURN_CODE_SUCCESS); rc = recordHistory->GetField(MIS::FID::HIGH , field); assert(rc == RETURN_CODE_SUCCESS); rc = value->Get(bar.high); assert(rc == RETURN_CODE_SUCCESS); rc = recordHistory->GetField(MIS::FID::LOW , field); assert(rc == RETURN_CODE_SUCCESS); rc = value->Get(bar.low); assert(rc == RETURN_CODE_SUCCESS); rc = recordHistory->GetField(MIS::FID::CLOSE , field); assert(rc == RETURN_CODE_SUCCESS); rc = value->Get(bar.close); assert(rc == RETURN_CODE_SUCCESS); rc = recordHistory->GetField(MIS::FID::VOLUME, field); assert(rc == RETURN_CODE_SUCCESS); volume = dynamic_cast< Database::NativeType * >(field); assert(volume); rc = volume->Get(bar.volume); assert(rc == RETURN_CODE_SUCCESS); // DELL had a dividend of 2, 10 years ago, seems like we shouldn't go back that far. // Right now it's set to a year. Maybe it should adjust for splits as well. if (fundamentalData != NULL && *date > yearAgo) { double temp; rc = recordHistory->GetField(MIS::FID::DIVIDEND , field); assert(rc == RETURN_CODE_SUCCESS); value = dynamic_cast< Database::Value * >(field); assert(value); rc = value->Get(temp); assert(rc == RETURN_CODE_SUCCESS); if (temp > 0) { fundamentalData->dividend = temp; } } bar.printCount = 0; bars.push_back(bar); rc = pTableHistory->GetNext(*keyHistory, *recordHistory, false, &position); } assert(rc == RETURN_CODE_END_OF_DB); } void DataFromSpryWare::get1MinBars(const std::string &symbol, BarList &bars) { std::string str; std::string spryWareSymbol = getAlternateCanadianSymbol(symbolToSpryWare(symbol)); BarData bar1Min; unsigned int rc = 0; Database::IField *field; Database::Date date; Database::IntradayBar *bar; Database::Price price; Database::IndexPosition position = {0}; Database::Date lastDate; unsigned lastTime = 0, currentTime; unsigned hour, minute, second; struct tm t; bars.clear(); lastDate.Now(); lastDate.Set(lastDate.GetYear() - 1000, 1, 1); rc = key1Min->FromString(spryWareSymbol); assert(rc == RETURN_CODE_SUCCESS); rc = pTable1Min->GetEqual(*key1Min, *record1Min, false, &position); if (rc != RETURN_CODE_SUCCESS) { std::cout << " Failed to get 1 Minute Bars for \'" << spryWareSymbol << "\': " << rc << " - "<< MIS::ERR::ToString(rc) << std::endl; return; } while (rc == RETURN_CODE_SUCCESS) { rc = record1Min->GetField(MIS::FID::INTRADAY_BAR , field); assert(rc == RETURN_CODE_SUCCESS); bar = dynamic_cast< Database::IntradayBar * >(field); assert(bar); bar->GetDate(date); t.tm_year = date.GetYear() - 1900; t.tm_mon = date.GetMonth() - 1; t.tm_mday = date.GetDay(); bar->GetTime(hour, minute, second); currentTime = 60 * 60 * hour + 60*minute + second; t.tm_hour = hour; t.tm_min = minute; t.tm_sec = second; t.tm_isdst = -1; bar1Min.startTime = nyToLocal(mktime(&t)); if ((date < lastDate) || ((date == lastDate) && (lastTime > currentTime))) { std::cout << " Out of Order 1 Minute Bar" << std::endl << " Current: " << date.ToString() << ':' << currentTime << std::endl << " Last: " << lastDate.ToString() << ':' << lastTime << std::endl; } bar->GetOpen(price); price.GetPrice(bar1Min.open); bar->GetHigh(price); price.GetPrice(bar1Min.high); bar->GetLow(price); price.GetPrice(bar1Min.low); bar->GetClose(price); price.GetPrice(bar1Min.close); bar1Min.volume = (unsigned int) bar->GetVolume(); bar1Min.printCount = (unsigned int) bar->GetTickCount(); // We sometimes get a final bar of the day right at the close that appears // to be within market hours so we combine it with the 12:59:00 bar if(currentTime == MARKET_HOURS_CLOSE && lastTime == MARKET_HOURS_CLOSE - MARKET_HOURS_MINUTE && lastDate == date) { BarData &previousBar = bars.back(); previousBar.high = std::max(previousBar.high, bar1Min.high); previousBar.low = std::min(previousBar.low, bar1Min.low); previousBar.close = bar1Min.close; previousBar.volume += bar1Min.volume; previousBar.printCount += bar1Min.printCount; } else { // In case we get the 1:00:00 bar but don't have a 12:59:00 bar // I don't know if this case will ever actually happen as the 1:00:00 // bar always seems smaller if(currentTime == MARKET_HOURS_CLOSE) { bar1Min.startTime -= MARKET_HOURS_MINUTE; } bars.push_back(bar1Min); } lastDate = date; lastTime = currentTime; rc = pTable1Min->GetNext(*key1Min, *record1Min, false, &position); } assert(rc == RETURN_CODE_END_OF_DB); } void DataFromSpryWare::getCorporateActions(const std::string &symbol, CorporateActions &corporateActions) { std::string spryWareSymbol = getAlternateCanadianSymbol(symbolToSpryWare(symbol)); Database::IndexPosition position = {0}; Database::CorporateAction *corporateAction; Database::Value dividendValue; Database::IField *field; double splitFactorSpryWare; Database::Date *date; Split split, previousSplit; previousSplit.date = 0; previousSplit.splitFactor = 1.0; corporateActions.splits.clear(); int rc = keyCorporateActions->FromString(spryWareSymbol); assert(rc == RETURN_CODE_SUCCESS); rc = pTableCorporateActions->GetFirstPartial(*keyCorporateActions, 32, *recordCorporateActions, false, &position); if (rc != RETURN_CODE_SUCCESS) { assert(rc == RETURN_CODE_NOT_FOUND || rc == RETURN_CODE_END_OF_DB); return; } while (rc == RETURN_CODE_SUCCESS) { rc = recordCorporateActions->GetField(MIS::FID::CORPORATE_ACTION, field); assert(rc == RETURN_CODE_SUCCESS); corporateAction = dynamic_cast< Database::CorporateAction * >(field); if (corporateAction->IsDividend()) { uint32_t dividendType; rc = corporateAction->GetDividendType(dividendType); assert(rc == RETURN_CODE_SUCCESS); if(dividendType == MIS::DVT::SPLIT) { rc = corporateAction->GetDividend(dividendValue); assert(rc == RETURN_CODE_SUCCESS); dividendValue.Get(splitFactorSpryWare); rc = recordCorporateActions->GetField(MIS::FID::ACTIVITY_DATE, field); assert(rc == RETURN_CODE_SUCCESS); date = dynamic_cast< Database::Date * >(field); // Sanity check, 0.0 splits occasionally show up. if(splitFactorSpryWare > 0.0) { split.splitFactor = splitFactorSpryWare; split.date = dateFromSpryWare(date); assert(split.date > previousSplit.date); corporateActions.splits.push_back(split); std::cout << " Split factor: " << splitFactorSpryWare << " (" << date->ToString() << ')' << std::endl; } else { std::cout << " Bad split factor: " << splitFactorSpryWare << " (" << date->ToString() << ')' << std::endl; } } } rc = pTableCorporateActions->GetNextPartial(*keyCorporateActions, 32, *recordCorporateActions, false, &position); } assert(rc == RETURN_CODE_NOT_FOUND); } void adjustForSplits(BarList &bars, const CorporateActions &corporateActions) { if(corporateActions.splits.size() > 0) { for (BarList::iterator barsIt = bars.begin(); barsIt != bars.end(); barsIt++) { for (std::vector< Split >::const_iterator splitsIt = corporateActions.splits.begin(); splitsIt != corporateActions.splits.end(); splitsIt++) { if (barsIt->startTime < splitsIt->date) { barsIt->open /= splitsIt->splitFactor; barsIt->high /= splitsIt->splitFactor; barsIt->low /= splitsIt->splitFactor; barsIt->close /= splitsIt->splitFactor; // Volume is adjusted for splits as well // (from SpryWare at least) barsIt->volume = (unsigned int)(barsIt->volume * splitsIt->splitFactor); } } } } }