Example Using Modules from SourcePro Core, SourcePro DB, and SourcePro Net
The following example transports stock quotes from a quote service, and persists them to a local database for analysis. Three SourcePro products work together here: SourcePro Net, SourcePro DB, and SourcePro Core.
In the example, class Stock is written so that it can pull past stock closing values from the local database, or real-time quotes from Yahoo!TM. The Essential Networking Module and the Internet Protocols Module are the enabling technologies behind the connection to Yahoo!, whereas the DB Interface Module enables the local persistence of the historical data. You can understand how the different product groups are leveraged by looking at the two different constructors of Stock. When a Stock instance is constructed with a date, historical data is used from the RDBMS. If the date is omitted, the current value is used; if the market is closed, the closing value of the market is used.
The example performs a simple analysis through the Graph class. This analysis could range from calculating the P/E ratio or the book value/share, to generating recommendations based on the MACD, or any of the other common tasks in the financial industry. Alternatively, instead of stock information, the data could be real-time product pricing from a supplier to a retailer.
Example 4 – Using five modules
// STOCK PLOTTER
// An example application that uses SourcePro Net to fetch
// stock quotes from the Web, and SourcePro DB to persist
// those quotes to a relational database.
// From the Internet Protocols Module
#include <rw/http/RWHttpSocketClient.h>
#include <rw/internet/RWURL.h>
// From the Essential Networking Module
#include <rw/network/RWPortalIStream.h>
#include <rw/network/RWWinSockInfo.h>
// From the Threads Module
#include <rw/functor/functor.h>
#include <rw/sync/sync.h>
#include <rw/thread/thread.h>
// From the DB Interface Module
#include <rw/db/db.h>
// From the Essential Tools Module
#include <rw/cstring.h>
#include <rw/ctoken.h>
#include <rw/decport.h>
#include <rw/rwdate.h>
// From the C++ Standard Library
#include <fstream>
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
using std::ios;
using std::ofstream;
using std::ostream;
// GLOBAL UTILITIES
// ::logFile
// Returns the ostream used for logging errors.
ostream& logFile()
{
static ofstream logFile("errors.log", ios::out | ios::ate);
return logFile;
}
// ::log
// Writes the errors to the log file.
void log(const RWCString& message)
{
logFile() << RWDateTime(RWDateTime::setCurrentTime) << " "
<< message << endl;
}
// ::databaseErrorHandler
// An error handler that is invoked anytime there is a
// database-related error encountered by the DB Interface
// Module.
void databaseErrorHandler(const RWDBStatus& s)
{
RWCString error(RWSize_T(256));
error += "Error code : "
+ RWDBValue(s.errorCode()).asString() + "\n";
error += "Error Message : " + s.message() + "\n";
error += "Vendor Message 1: " + s.vendorMessage1() + "\n";
error += "Vendor Message 2: " + s.vendorMessage2() + "\n";
log(error);
}
// CONSTANTS
// Simple Constants
// TICKER - ticker to display
// REFRESH_DELAY - seconds to wait between stock updates
// ENABLE_TRACER - enable the database tracer (log SQL)
const RWCString TICKER = "IBM";
const size_t REFRESH_DELAY = 30; // Seconds to wait
// between updates
const bool ENABLE_TRACER = true;
// Constants for database access. These are used with the
// DB Interface Module to instantiate database objects.
const RWCString DBSERVER_TYPE = "<servertype>";
const RWCString DBSERVER_NAME = "<servername>";
const RWCString DBUSER_NAME = "<username>";
const RWCString DBPASSWORD = "<password>";
const RWCString DBDATABASE_NAME = "<database>";
const RWCString DBDATABASE_PSTRING = "<property_string>";
const RWCString TABLE_NAME = "StockGraph";
// class Graph
// Simple text graph utility. Displays a bar graph representing
// the data that has been added to it. The constructor takes two
// arguments - the columns, c, and the rows, r, where c is the
// width of the graph in characters and r is the height.
class Graph
{
public:
Graph(size_t c, size_t r) : cols_(c), rows_(r) {
;
}
void display(ostream& s) {
normalize();
double miny = LONG_MAX;
double maxy = 0;
for (size_t i = 0; i < display_.entries(); ++i) {
if (maxy < display_[i]) {
maxy = display_[i];
}
if (miny > display_[i]) {
miny = display_[i];
}
}
double deltay = (maxy - miny) / rows_;
for (size_t y = 0; y < rows_ - 1; ++y) {
for (size_t x = 0; x < cols_; ++x) {
if (display_[x] - miny > deltay * (rows_ - y - 1)) {
s << "*";
}
else {
s << " ";
}
}
s << endl;
}
// Place one last line of "*"'s so that empty columns
// look good.
for (size_t x = 0; x < cols_; ++x) {
s << "*";
}
s << endl;
}
void clear() {
values_.clear();
}
void addBar(double value) {
values_.append(value);
}
protected:
RWTValOrderedVector<double> values_;
RWTValOrderedVector<double> display_;
size_t cols_, rows_;
void normalize() {
display_.clear();
if (values_.entries() == 0) {
values_.append(0);
}
if (values_.entries() < cols_) {
double runlength = cols_ / values_.entries();
double delta = double(cols_ % values_.entries())
/ values_.entries();
double ldelta = 0;
for (size_t i = 0; i < values_.entries(); ++i) {
for (size_t r = 0; r < runlength + long(ldelta); ++r) {
display_.append(values_[i]);
if (display_.entries() == cols_) {
return;
}
}
if (ldelta > 1.0) {
--ldelta;
}
ldelta += delta;
}
while (display_.entries() < cols_) {
display_.append(display_[display_.entries() - 1]);
}
}
else {
for (size_t i = values_.entries() - cols_;
i < values_.entries(); ++i) {
display_.append(values_[i]);
}
}
}
};
// class Stock
// A class that represents a stock on a given date.
class Stock
{
public:
// Stock(const RWCString&)
// Constructs a Stock object that represents the most recent
// data for the stock.
Stock(const RWCString& ticker) {
RWCString rawdata = getRawStockData(ticker);
parse(rawdata);
if (Stock(ticker_, datetime_).isValid()) {
// Update the stock data in the database.
RWDBTable table = dbTable();
RWDBUpdater updater = table.updater();
updater.set(table["PRICE"].assign(price_));
updater.set(table["VOLUME"].assign(volume_));
updater.where(table["TICKER"] == ticker_ &&
table["LAST_UPDATE"] == datetime_);
updater.execute();
}
else {
// Insert the stock data into the database.
RWDBInserter inserter = dbTable().inserter();
inserter << *this;
inserter.execute();
}
}
// Stock(const RWCString&, const RWDate&)
// Constructs a Stock object that represents the date for the
// stock on the date represented by the second parameter.
// When the stock data isn't found, the isValid() method
// returns false.
Stock(const RWCString& ticker, const RWDateTime& date) {
RWDBSelector selector = database().selector();
RWDBTable table = dbTable();
selector << table["TICKER"]
<< table["PRICE"]
<< table["LAST_UPDATE"]
<< table["VOLUME"];
selector.where(table["TICKER"] == ticker &&
table["LAST_UPDATE"] == date);
RWDBReader reader = selector.reader();
// Assume one row and advance to it.
reader();
reader >> *this;
}
RWCString ticker() const {
return ticker_;
}
RWDecimalPortable price() const {
return price_;
}
RWDateTime datetime() const {
return datetime_;
}
RWDecimalPortable volume() const {
return volume_;
}
// If the ticker is NULL, then this object isn't valid.
bool isValid() const {
return !ticker_.isNull();
}
protected:
void parse(const RWCString& rawdata) {
RWCTokenizer next(rawdata);
const char* delimiters = ", \t\n";
// Extracts ticker.
RWCString tickerstring(next(delimiters, 5));
tickerstring = tickerstring.strip(RWCString::both, '"');
ticker_ = tickerstring;
// Extracts price of last exchange.
price_ = RWDecimalPortable(next(delimiters, 5).data());
// Extracts date and time of last exchange.
RWCString str(next(delimiters, 5));
str = str.strip(RWCString::both, '"');
// Extracts time of last exchange.
RWCString timestr(next(delimiters, 5));
timestr = timestr.strip(RWCString::both, '"');
// Create a single string whose format conforms to the
// requirements of RWDateTime's constructor.
str += "; " + timestr;
// Combines the date and time components into
// a "date time" object.
datetime_ = RWDateTime(str, RWDateTime::setBoth);
// Extracts the volume
volume_ = RWDecimalPortable(next(delimiters, 5).data());
}
// getRawStockData(const RWCString& ticker)
// Returns a string in CSV format for the entity traded
// under the ticker argument. Usually, valid tickers are
// those that are traded on the NASDAQ or DOW JONES
// exchanges.
// Example of return value:
// "IBM",76.07,"8/15/2002","2:38pm",7379300
RWCString getRawStockData(const RWCString& ticker) const {
// Use Yahoo.com for the stock data source.
const RWURL url("http://download.finance.yahoo.com");
// Make sure the ticker is lower case.
RWCString lticker = ticker;
lticker.toLower();
// The quote system at Yahoo expects the URL in the
// following format.
RWCString source("/d/quotes.csv?s=" + lticker
+ "&f=sl1d1t1v&e=.csv");
// Retrieve the stock data using the HTTP Client
// from the Internet Protocols Module.
RWHttpClient client = RWHttpSocketClient::make();
client.connect(url.getHost());
// Constructs a GET request and submits it
// to the server.
RWHttpRequest getRequest(RWHttpRequest::Get, source);
client.submit(getRequest);
RWHttpReply reply = client.getReply();
// Parses the body from the reply and prints it.
RWCString body = reply.getBody();
cout << body << endl;
return body;
}
// ::database()
// Returns the database to use for the application.
// The return value will be an RWDBDatabase instance.
// Note: If the database is invalid, check the login
// parameters.
RWDBDatabase database() const {
// For performance purposes, we only create the database
// object when the static instance below is invalid. On the
// first invocation, the database instance is invalid
// because the default constructor is used. Valid database
// instances can only be produced from RWDBManager.
static RWDBDatabase database;
if (!database.isValid()) {
// Database isn't valid, let's refresh the copy and
// return it.
database = RWDBManager::database(DBSERVER_TYPE,
DBSERVER_NAME,
DBUSER_NAME,
DBPASSWORD,
DBDATABASE_NAME,
DBDATABASE_PSTRING);
if (!database.isValid()) {
log("[ERROR] Cannot acquire database connection.");
}
if (ENABLE_TRACER) {
database.tracer().setOn(RWDBTracer::BoundBuffers);
database.tracer().stream(logFile());
}
}
return database;
}
RWDBTable dbTable() const {
RWTLockGuard<RWMutexLock> guard(mutex());
// For performance purposes, we'll create the table object
// only when the static instance below is invalid.
// On the first invocation, the table instance is invalid
// because the default constructor is used.
static RWDBTable table;
if (!table.isValid()) {
RWDBDatabase db = database();
table = db.table(TABLE_NAME);
table.drop();
// Check for the existence of the table. When it
// doesn't exist, we'll create it.
if (!table.exists(true)) {
log("[WARNING] Table doesn't exist. Creating now.");
// Create the table's schema.
RWDBSchema schema;
schema.appendColumn("TICKER", RWDBValue::String,
50);
schema.appendColumn("PRICE", RWDBValue::Decimal,
RWDB_NO_TRAIT, RWDB_NO_TRAIT,
8, 2);
schema.appendColumn("LAST_UPDATE",
RWDBValue::Date);
schema.appendColumn("VOLUME", RWDBValue::Decimal);
// Create the table
db.createTable(TABLE_NAME, schema);
// Refetch the schema that was accepted
// (proof of existence.)
if (!table.exists(true)) {
log("[ERROR] Table creation failed.");
}
}
}
return table;
}
// ::mutex()
// Returns a mutex to protect static members.
static RWMutexLock& mutex() {
static RWMutexLock mutex;
return mutex;
}
// Stock insertion operator, allows easy insertion of stock
// instances into the database.
friend RWDBInserter& operator<<(RWDBInserter& inserter,
const Stock& stock) {
inserter["TICKER"] << stock.ticker();
inserter["PRICE"] << stock.price();
inserter["LAST_UPDATE"] << stock.datetime().toRWDate();
inserter["VOLUME"] << stock.volume();
return inserter;
}
// Stock extraction operator, allows easy extraction of
// stock instances from the database.
friend RWDBReader& operator>>(RWDBReader& reader,
Stock& stock) {
reader["TICKER"] >> stock.ticker_;
reader["PRICE"] >> stock.price_;
reader["LAST_UPDATE"] >> stock.datetime_;
reader["VOLUME"] >> stock.volume_;
return reader;
}
private:
RWDecimalPortable price_;
RWDecimalPortable volume_;
RWDateTime datetime_;
RWCString ticker_;
};
// ::backgroundThreadForStockDataUpdate
// The background thread function that updates the data in the
// database to the current stock data.
void backgroundThreadForStockDataUpdate()
{
while (1) {
log("[INFO] Retrieving latest stock information.");
// The following line forces a lookup and storage into the
// database. It is good to do this every 20 minutes or so
// to retrieve the most recent value. If executed after the
// market closes, the closing price is persisted.
Stock s(TICKER);
// Plot all dates from the last month.
long dateoffset = 7 * 4;
// Calculate the starting date.
RWDate date = RWDate() - dateoffset;
Graph g(70, 24);
while (date <= RWDate()) {
Stock stock(TICKER, date);
if (stock.isValid()) {
g.addBar(toDouble(stock.price()));
}
++date;
}
g.display(cout);
for (size_t i = 0; i < REFRESH_DELAY; ++i) {
// sleep one second
rwSleep(1000);
try {
rwThread().serviceCancellation();
}
catch (...) {
log("[INFO] Exiting gracefully.");
throw;
}
}
}
}
// main
// Our main program.
// It is responsible for setting up the required components and
// spawning the background thread.
//
int main(int argc, char* argv[])
{
// Initialize the winsock socket library when required.
RWWinSockInfo info;
// Set the database error handler.
RWDBManager::setErrorHandler(databaseErrorHandler);
// Create a functor to be invoked by the background thread.
RWTFunctor<void()> functor =
backgroundThreadForStockDataUpdate;
// Fire off the background thread that performs the realtime
// stock updates to the database.
RWThread backgroundThread = RWThreadFunction::make(functor);
backgroundThread.start();
// Enter a number and press enter to kill the program.
int key;
cin >> key;
backgroundThread.requestCancellation();
// Join the thread to make sure it won't outlive
// the application.
backgroundThread.join();
return 0;
}
Program output:
Below is the program output for a stock, here identified simply as ”XYZ”, over a four-day period. Each column in the graph represents the relative closing stock price on a particular day.
"XYZ",77.96,"8/27/2002","4:01pm",7087400
**************
**************
****************************
****************************
****************************
****************************
****************************
****************************
****************************
****************************
****************************
******************************************
******************************************
******************************************
******************************************
********************************************************
********************************************************
********************************************************
********************************************************
********************************************************
********************************************************
********************************************************
********************************************************
**********************************************************************