Stockfighter in C++: An Overview

Stockfighter is a stock simulation game. Each level (of increasing difficulty) is operable through a browser-based “blotter” UI, though beyond the first level or two the only way to win is to write a client against the simulator’s REST API. (On an aside, the company publishing the game has an interesting reason for doing so.)

The reality is that I can’t program the web very well: I haven’t had much of a need. I can cURL an address just fine, but rarely need to in a well-structured way. Web latency requires asynchronous call handling, which is another skill I have been trying to improve upon given C++11’s new intrinsics. Upcoming feature work at the office will require these skills, so Stockfighter is a good chance to architect and learn at the same time.

The entire application, soup to nuts, is in C++11. I take advantage of some third party tools (read: Excel) for basic charting, but even then the data comes from logs I needed to build.

I leveraged some third-party libraries to make low-level jobs a little easier:

Websocketpp uses Boost’s asio library, which was easy enough to integrate given I was already going to use Boost. The network libraries also use OpenSSL to encrypt communications, though I didn’t interface with it directly.

I won’t go over every gory line of C++. Nor will I go over specifics related to solving a particular Stockfighter level. My hope is that what I’ve built is capable of solving any of them (given some relatively minor changes.) Rather, I am most interested in calling out specific areas where I felt I learned something new, or built something interesting.

Goals

I didn’t have many goals in mind when building the client. Obviously, winning the various levels was important. But I was also interested in making sure the application was stable, maintainable, and extensible. That means:

  • No loops for blocking. I’ve done something wrong if a processor spikes to 100% waiting for something to happen.
  • Performance. If it takes too long to respond to an event from the stock simulation, my window of opportunity will be lost.
  • Thread safety. If the application is stepping on its own toes, it won’t get very far.

So where to start?

How about the beginning:

int main(int argc, char** argv) try {
std::string settings_path(argc > 1 ? argv[1] : "");

if (!config::init(binary_path, settings_path)) {
// usage, error, etc

return 1;
}

log_t log(config::derivative_file(".log"), true, false);
task_queue_t queue{6};
recur::engine_t recur{queue};
game_t game(log, recur, queue);

std::thread([&](){
console(log, recur, queue, game);
}).detach();

recur.insert(std::chrono::minutes(1), [&]() {
keepalive(log, recur);
});

log("MAIN") << "Startup";

queue.push([&](){
game.start();
});

recur.run();

return 0;
} catch (const std::exception& error) {
std::cerr << "Fatal error : " << error.what() << '\n';

return 1;
} catch (...) {
std::cerr << "Fatal error : Unknown" << '\n';

return 1;
}

Loaded, right? Let’s break it down.

The Settings File

The application takes a single parameter: a path to a settings file. The file is a serialized JSON object with a single value, my Stockfighter API key. (I had intended to leverage the settings file a bit more, but a cause never materialized.)

As it turns out, the most important function of the settings file is to provide a home directory for log files.

The Log

For many of the levels I had several logs where I dumped copious data. The config::derivative_file call starts with the settings file location and returns a sibling file as a boost::filesystem::path. I built the logs to behave much like iostreams, but also included thread support, timestamps, etc.

This log in particular is the primary log. Think of it like std::cout.

The Task Queue

As I mentioned before, asynchronous event handling is a must-have in modern applications. This goes double when dealing with the high-latency resources (e.g., web APIs). The task queue lets me set up jobs on multiple threads to be dealt with concurrently. As information flows in and out of the client to Stockfighter, being able to respond to it all in a timely fashion will be critical. The task queue enables me to do this, but I have to take asynchronous issues into account when doing so. Challenge accepted.

The Recurrent Engine

Many things need to happen on an ongoing basis and at specified intervals. Examples of these include polling for websocket events, or asserting the Stockfighter servers are up (heartbeats). The recurring event engine keeps track of these jobs and inserts them into the task queue when necessary. Once the job completes, it is re-scheduled into the “wait queue” to be called again at a later time.

Even when the task queue is empty, the recurrent engine is ever-vigilant, deciding what job (if any) should be run next, and when. Everything the application does when interfacing with Stockfighter has its roots in the recurrent engine.

Likewise, teardown of the application begins with the termination of the recurrent engine, and is the structure used to determine if the application is “done”. When this engine ceases, the entire application collapses. The task queue empties. Websockets fail. Heartbeats die.

The Game Interface

The game structure is the encapsulation of the entire Stockfighter system. You can see it receives the log, recurrent, and task queue as it is being constructed. And for good reason, as just about everything that happens in the application is located in the game unit. (The game subsystems are broken out into a handful of other files, all in the name of encapsulation and clarity.)

The Console

Much of the intent of the application is to have it interface with Stockfighter uninterrupted. Nevertheless, there are times when a little manual intervention is necessary. The console is a separate unit of the application that handles std::cout and std::cin in an asynchronous fashion. Based on user input, various tasks are pumped into the task queue for execution. I ended up writing about a half-dozen commands for the console. Most of them were probing in nature, and did little to directly control the behavior of the client against a level.

The console lives in its own thread, and uses the recurrent engine only to see if it has terminated. Once it has spun up I have no need to communicate with it directly, and the thread can be safely detached.

The Keepalive

keepalive does little. It is installed into the recurrent engine to fire every minute. Stockfighter has a API to verify the servers are operating, and if they are not, the recurrent engine is terminated:

void keepalive(log_t& log, recur::engine_t& recur) {
// If the API dies, so should we.
if (!stock::heartbeat()) {
log(“MAIN”) << “Heartbeat failure”;
        recur.terminate();
}
}

This is one of the first uses of the logging mechanism. I wanted to have it behave as much like an iostream as possible, so I could treat it like std::cout. I didn’t want to have to think about using it, I just wanted it to work. I can go into more detail on the logging mechanism in a later post.

The Start of the Game

With all the setup out of the way, it’s time to make something happen! The game starts with… well… game.start(). I push it onto the task queue because I want it to execute somewhere else- my recurrent engine has to get busy recurring.

The game engine start does a handful of significant things, like starting the game instance, establishing the necessary websockets, setting up another heartbeat, and logging some basic details about the level. What happens after that is a story for another post.

The End of Main

Finally, the recurrent engine is the last structure mentioned in main: the call to the engine’s run routine drops the main thread into a loop of waiting to scheduling the next recurring job, or quitting.