/* trade.c. This program allows users to interact with the WWWWS system. Set these #defines appropriately before compiling. */ #define DATA_PATH "/CHANGE/THIS/PATH/wwwws" #define PROGRAM_URL "/CHANGE/THIS/URL/cgi-bin/trade" #include #include #include #include #include "parse.h" #include "cgic.h" #include "gd.h" #include "chart.h" #define PATH_SPACE 256 #define STRING_SPACE 256 #define HISTORY_SIZE 30 int getFunds(float *fundsP); int setFunds(float funds); int getStockPrice(char *sym, float *price); int getStockHistory(char *sym, float *history, int *historySizeP); int getStockShares(char *sym, int *sharesP); int setStockShares(char *sym, int shares); int Trade(); int History(char *symbol); int HistoryChart(char *symbol); int Transactions(); int Portfolio(); int Newspaper(); char *accountName; int cgiMain() { time_t now; time(&now); srand((int) now); accountName = cgiRemoteUser; if (!strcmp(accountName, "")) { /* This shouldn't happen! But it will happen if the program is not installed according to the instructions, or if the server has bugs regarding user authentication. */ fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "User Not Authenticated\n"); fprintf(cgiOut, "

User Not Authenticated

\n"); fprintf(cgiOut, "The trade program was not installed in\n"); fprintf(cgiOut, "a password-authenticated directory, or\n"); fprintf(cgiOut, "there is a bug in the web server.\n"); fprintf(cgiOut, "Please report this problem to the\n"); fprintf(cgiOut, "site administrator.\n"); fprintf(cgiOut, "\n"); return 1; } if (!strcmp(cgiPathInfo, "/trade")) { return Trade(); } else if (!strncmp(cgiPathInfo, "/history/", 9)) { return History(cgiPathInfo + 9); } else if (!strncmp(cgiPathInfo, "/historychart/", 14)) { /* Sigh, remove the image.gif thing at the end */ char *s = (char *) malloc(strlen(cgiPathInfo + 14) + 1); char *im; int result; strcpy(s, cgiPathInfo + 14); im = strstr(s, "/image"); if (im) { *im = '\0'; } result = HistoryChart(s); free(s); } else if (!strcmp(cgiPathInfo, "/transactions")) { return Transactions(); } else if (!strcmp(cgiPathInfo, "/newspaper")) { return Newspaper(); } else { return Portfolio(); } } void DatabaseMissing(); char *directions[] = { "buy", "sell" }; void StartTransaction(); void EndTransaction(); void TransactionLog(char *text); void stockBuy(char *symbol, int shares); void stockSell(char *symbol, int shares); int Trade() { FILE *in; char symbol[STRING_SPACE], priceString[STRING_SPACE], attr[STRING_SPACE]; char s[PATH_SPACE]; int shares; float funds; SetFieldSeparator(' '); sprintf(s, "%s/database", DATA_PATH); in = fopen(s, "r"); if (!in) { DatabaseMissing(); return 0; } StartTransaction(); if (cgiFormString("newbuy symbol", symbol, STRING_SPACE) != cgiFormNotFound) { cgiFormInteger("newbuy shares", &shares, 0); if (shares) { stockBuy(symbol, shares); } } do { if (!GetField(in, symbol, STRING_SPACE)) { break; } sprintf(attr, "%s shares", symbol); cgiFormInteger(attr, &shares, 0); if (shares > 0) { int direction; sprintf(attr, "%s direction", symbol); cgiFormRadio(attr, directions, 2, &direction, -1); if (direction == -1) { /* Not a complete submission. Don't guess! There's serious play money at stake here. */ char s[STRING_SPACE]; sprintf(s, "Please specify buy or sell for %s. No transaction performed.", symbol); TransactionLog(s); EndTransaction(); return 0; } if (direction == 0) { stockBuy(symbol, shares); } else { stockSell(symbol, shares); } } else if (shares == 0) { /* OK, do nothing */ } else if (shares < 0) { char s[STRING_SPACE]; sprintf(s, "%s: negative numbers of shares are not accepted for trades. Enter a positive number and select buy or sell.", symbol); TransactionLog(s); } } while (NextRecord(in)); fclose(in); EndTransaction(); return Portfolio(); } void stockBuy(char *symbol, int shares) { /* A purchase. Can we afford it? */ float purchasePrice; float price; float funds; if (!getFunds(&funds)) { char s[STRING_SPACE]; sprintf(s, "Unable to access your account. Please contact the site administrator."); TransactionLog(s); return; } if (!getStockPrice(symbol, &price)) { char s[STRING_SPACE]; sprintf(s, "Stock symbol %s is not in the database.", symbol); TransactionLog(s); return; } purchasePrice = shares * price; if (purchasePrice > funds) { /* No */ char s[STRING_SPACE]; sprintf(s, "Insufficient funds to purchase %d shares of %s!", shares, symbol); TransactionLog(s); } else { int currentShares; char s[STRING_SPACE]; getStockShares(symbol, ¤tShares); /* An ideal universe with no commissions! */ currentShares += shares; funds -= purchasePrice; setStockShares(symbol, currentShares); sprintf(s, "Purchased %d shares of %s at %.3f.", shares, symbol, price); TransactionLog(s); } setFunds(funds); } void stockSell(char *symbol, int shares) { /* A sale. Do we have that many shares? */ float salePrice; int currentShares; float price; float funds; if (!getFunds(&funds)) { char s[STRING_SPACE]; sprintf(s, "Unable to access your account. Please contact the site administrator."); TransactionLog(s); return; } if (!getStockPrice(symbol, &price)) { char s[STRING_SPACE]; sprintf(s, "Stock symbol %s is not in the database.", symbol); TransactionLog(s); return; } salePrice = shares * price; getStockShares(symbol, ¤tShares); if (currentShares < shares) { /* No */ char s[STRING_SPACE]; sprintf(s, "Can't sell %d shares of %s: you only have %d shares!", shares, symbol, currentShares); TransactionLog(s); } else { char s[STRING_SPACE]; /* An ideal universe with no commissions! */ currentShares -= shares; funds += salePrice; setStockShares(symbol, currentShares); sprintf(s, "Sold %d shares of %s at %.3f.", shares, symbol, price); TransactionLog(s); } setFunds(funds); } void DatabaseMissing() { fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "Database Missing\n"); fprintf(cgiOut, "

Database Missing

\n"); fprintf(cgiOut, "Please contact the site administrator.\n"); fprintf(cgiOut, "\n"); } FILE *currentOut; FILE *permanentOut; void StartTransaction() { char s[PATH_SPACE]; sprintf(s, "%s/%s.cur", DATA_PATH, accountName); currentOut = fopen(s, "w"); sprintf(s, "%s/%s.log", DATA_PATH, accountName); permanentOut = fopen(s, "a"); } void EndTransaction() { fprintf(currentOut, "End of transaction.

\n"); fclose(currentOut); fclose(permanentOut); } void TransactionLog(char *text) { time_t now; time(&now); fprintf(currentOut, "%s

\n", text); fprintf(permanentOut, "%s Time: %s

", text, ctime(&now)); } int History(char *symbol) { float history[HISTORY_SIZE]; int size; fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Price History: %s\n", symbol); fprintf(cgiOut, "

Price History: %s

\n", symbol); if (!getStockHistory(symbol, history, &size)) { fprintf(cgiOut, "

There is no stock with that ticker symbol.

\n"); } else { /* Add a random component to the URL in order to prevent web browsers from caching the chart when it is out of date. */ fprintf(cgiOut, "

\n", PROGRAM_URL, symbol, abs((rand() >> 8) % 1000)); } fprintf(cgiOut, "Prices for the past 30 days are shown.\n"); fprintf(cgiOut, "


Portfolio \n", PROGRAM_URL); fprintf(cgiOut, "Newspaper \n", PROGRAM_URL); fprintf(cgiOut, "Past Transactions \n", PROGRAM_URL); fprintf(cgiOut, "\n"); return 0; } int HistoryChart(char *symbol) { float history[HISTORY_SIZE]; char *labels[HISTORY_SIZE]; int size; int i; gdImagePtr im; im = gdImageCreate(400, 200); getStockHistory(symbol, history, &size); for (i = 0; (i < size); i++) { labels[i] = (char *) malloc(20); sprintf(labels[i], "%d", size - i - 1); } chartDraw(im, chartBar | chartCross | chartLine, labels, "Stock Price", history, size); fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("image/gif"); gdImageGif(im, cgiOut); gdImageDestroy(im); } int Transactions() { int ch; FILE *in; char s[PATH_SPACE]; sprintf(s, "%s/%s.log", DATA_PATH, accountName); in = fopen(s, "r"); fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Past Transactions\n"); fprintf(cgiOut, "

Past Transactions

\n"); if (!in) { fprintf(cgiOut, "

No transactions to date.

\n"); } else { while(1) { ch = getc(in); if (ch == EOF) { break; } putc(ch, cgiOut); } fclose(in); } fprintf(cgiOut, "
Portfolio \n", PROGRAM_URL); fprintf(cgiOut, "Newspaper \n", PROGRAM_URL); fprintf(cgiOut, "\n"); return 0; } int Newspaper() { FILE *in; int found = 0; char s[PATH_SPACE]; float history[HISTORY_SIZE]; fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Financial News\n"); fprintf(cgiOut, "

Financial News

\n"); sprintf(s, "%s/database", DATA_PATH); in = fopen(s, "r"); if (!in) { return 0; } SetFieldSeparator(' '); do { char symbol[STRING_SPACE]; char priceString[STRING_SPACE]; int historyPos = 0; if (!GetField(in, symbol, STRING_SPACE)) { break; } while (GetField(in, priceString, STRING_SPACE)) { history[historyPos++] = atof(priceString); } if (historyPos) { fprintf(cgiOut, "%s: trading at %.3f", symbol, history[historyPos-1]); if (historyPos >= 2) { float move = history[historyPos-1] - history[historyPos-2]; if (move == 0.0) { fprintf(cgiOut, " unchanged"); } else if (move > 0.0) { fprintf(cgiOut, " up %.3f", move); } else { fprintf(cgiOut, " down %.3f", move); } } fprintf(cgiOut, "\n", PROGRAM_URL, symbol); fprintf(cgiOut, "See History

\n"); } } while (NextRecord(in)); fclose(in); fprintf(cgiOut, "


Portfolio \n", PROGRAM_URL); fprintf(cgiOut, "Past Transactions \n", PROGRAM_URL); fprintf(cgiOut, "\n"); return 0; } int Portfolio() { float funds; float price; FILE *in; int shares; float total = 0.0; char s[PATH_SPACE]; char fundsString[STRING_SPACE]; fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Portfolio\n"); fprintf(cgiOut, "

Portfolio

\n"); sprintf(s, "%s/%s.cur", DATA_PATH, accountName); in = fopen(s, "r"); if (in) { int ch; fprintf(cgiOut, "

Transaction Report

\n"); while(1) { ch = getc(in); if (ch == EOF) { break; } putc(ch, cgiOut); } fclose(in); unlink(s); } sprintf(s, "%s/%s.dat", DATA_PATH, accountName); in = fopen(s, "r"); if (!in) { /* Bad, bad news */ fprintf(cgiOut, "

No database record available

\n"); fprintf(cgiOut, "Your database entry is not available.\n"); fprintf(cgiOut, "Please contact the site administrator.\n"); fprintf(cgiOut, "\n"); return 0; } SetFieldSeparator(' '); GetField(in, fundsString, STRING_SPACE); funds = atof(fundsString); fprintf(cgiOut, "

Current Holdings

\n"); fprintf(cgiOut, "

Funds Available: %.3f

\n", funds); fprintf(cgiOut, "
\n", PROGRAM_URL); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); if (!NextRecord(in)) { fprintf(cgiOut, "\n"); } else { do { char symbol[STRING_SPACE]; char sharesString[STRING_SPACE]; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (!GetField(in, sharesString, STRING_SPACE)) { break; } shares = atoi(sharesString); getStockPrice(symbol, &price); fprintf(cgiOut, "", symbol, shares, price); fprintf(cgiOut, "\n", shares * price); total += shares * price; fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n", symbol); fprintf(cgiOut, "\n", symbol); fprintf(cgiOut, "\n"); } while (NextRecord(in)); } fclose(in); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n", total); fprintf(cgiOut, "
Stock TickerShares HeldCurrent PriceTotal ValueShares to Trade
No current holdings.
%s%d%.3f%.3f\n"); fprintf(cgiOut, "\n", symbol); fprintf(cgiOut, "buysell
Total Value%.3f
\n"); fprintf(cgiOut, "Acquire new stock: \n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Shares:

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Newspaper \n", PROGRAM_URL); fprintf(cgiOut, "Past Transactions \n", PROGRAM_URL); fprintf(cgiOut, "\n"); return 0; } int getStockPrice(char *sym, float *priceP) { FILE *in; int found = 0; char s[PATH_SPACE]; sprintf(s, "%s/database", DATA_PATH); in = fopen(s, "r"); if (!in) { return 0; } SetFieldSeparator(' '); do { char symbol[STRING_SPACE]; char priceString[STRING_SPACE]; float price; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (strcmp(symbol, sym)) { /* Not the right record */ continue; } while (GetField(in, priceString, STRING_SPACE)) { price = atof(priceString); found = 1; } if (found) { *priceP = price; break; } } while (NextRecord(in)); fclose(in); return found; } int getStockHistory(char *sym, float *history, int *historySizeP) { FILE *in; int found = 0; int historyPos = 0; char s[PATH_SPACE]; sprintf(s, "%s/database", DATA_PATH); in = fopen(s, "r"); if (!in) { return 0; } SetFieldSeparator(' '); do { char symbol[STRING_SPACE]; char priceString[STRING_SPACE]; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (strcmp(symbol, sym)) { /* Not the right record */ continue; } found = 1; while (GetField(in, priceString, STRING_SPACE)) { history[historyPos++] = atof(priceString); } break; } while (NextRecord(in)); fclose(in); *historySizeP = historyPos; return found; } int getFunds(float *fundsP) { FILE *in; char s[PATH_SPACE]; char fundsString[STRING_SPACE]; sprintf(s, "%s/%s.dat", DATA_PATH, accountName); in = fopen(s, "r"); if (!in) { *fundsP = 0; return 0; } SetFieldSeparator(' '); if (!GetField(in, fundsString, STRING_SPACE)) { fclose(in); *fundsP = 0; return 0; } *fundsP = atof(fundsString); fclose(in); return 1; } int setFunds(float newFunds) { FILE *in, *out; int found = 0; float funds; char s[PATH_SPACE], snew[PATH_SPACE]; char fundsString[STRING_SPACE]; sprintf(s, "%s/%s.dat", DATA_PATH, accountName); sprintf(snew, "%s/%s.new", DATA_PATH, accountName); in = fopen(s, "r"); if (!in) { return 0; } out = fopen(snew, "w"); if (!out) { fclose(in); return 0; } SetFieldSeparator(' '); if (!GetField(in, fundsString, STRING_SPACE)) { fclose(in); return 0; } funds = atof(fundsString); fprintf(out, "%.3f\n", newFunds); while (NextRecord(in)) { char symbol[STRING_SPACE]; char sharesString[STRING_SPACE]; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (!GetField(in, sharesString, STRING_SPACE)) { break; } fprintf(out, "%s %s\n", symbol, sharesString); } fclose(in); fclose(out); rename(snew, s); return 1; } int getStockShares(char *sym, int *sharesP) { FILE *in; int found = 0; int shares; char s[PATH_SPACE]; sprintf(s, "%s/%s.dat", DATA_PATH, accountName); in = fopen(s, "r"); if (!in) { *sharesP = 0; return 0; } SetFieldSeparator(' '); /* Skip over the first record, which contains this customer's funds */ *sharesP = 0; while (NextRecord(in)) { char symbol[STRING_SPACE]; char sharesString[STRING_SPACE]; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (strcmp(symbol, sym)) { /* Not the right record */ continue; } found = 1; if (!GetField(in, sharesString, STRING_SPACE)) { break; } shares = atoi(sharesString); break; } fclose(in); if (!found) { *sharesP = 0; } *sharesP = shares; return found; } int setStockShares(char *sym, int shares) { FILE *in, *out; int found = 0; float funds; char s[PATH_SPACE], snew[PATH_SPACE]; char fundsString[STRING_SPACE]; sprintf(s, "%s/%s.dat", DATA_PATH, accountName); sprintf(snew, "%s/%s.new", DATA_PATH, accountName); in = fopen(s, "r"); if (!in) { return 0; } out = fopen(snew, "w"); if (!out) { fclose(in); return 0; } SetFieldSeparator(' '); /* Handle the first record, which contains this customer's funds */ if (!GetField(in, fundsString, STRING_SPACE)) { fclose(in); return 0; } funds = atof(fundsString); fprintf(out, "%.3f\n", funds); while (NextRecord(in)) { char symbol[STRING_SPACE]; char sharesString[STRING_SPACE]; if (!GetField(in, symbol, STRING_SPACE)) { break; } if (!GetField(in, sharesString, STRING_SPACE)) { break; } if (strcmp(symbol, sym)) { /* Not the right record */ fprintf(out, "%s %s\n", symbol, sharesString); continue; } else { fprintf(out, "%s %d\n", sym, shares); found = 1; } } fclose(in); if (!found) { fprintf(out, "%s %d\n", sym, shares); } fclose(out); rename(snew, s); return 1; }