/*********************************************************** * Google Finance Stock Tracker * * Program for keeping track of a stock portfolio. * * REQUIRES: ddgr, wget * * program: gfst * author : yetimach * date : June 12, 2022 * ************************************************************/ #include #include #include #include #include #include #include #define MAX_S 100 // max number of stocks that can be tracked #define MAX_CH 200 // max chars, used for various strings #define MAX_C 50 // smaller max chars #define MNS 12 // max number string #define ERROR 1000 #define SP "data-last-price=\"" //search pattern #define D '#' //delimiter for reading strings that contain white space FILE *wtf; //wget temp file FILE *asif; //all stock info file FILE *date_file; struct stock { char sn[MAX_CH]; // stock name char url[MAX_CH]; // www.google.com/finance/quote/####### char pd[10]; // purchase date format: mm/dd/yy int q; // quantity float pp; // purchse price float cp; // current price }; struct stock sta[MAX_S]; //stock array char temp_num_st[MAX_C]; // temporary number string float temp_num_float; int temp_num_int; char g_temp_sn[MAX_CH]; // global temp stock name char g_temp_url[MAX_CH]; // global temp url float g_csp = 0; //global current stock price int num_of_st; int option; char path_to_asif[MAX_CH]; char sds[MAX_C]; // short date string char lds[MAX_C]; // long date string char g_user_name[MAX_C]; int no_stocks = 0; int asif_not_set = 1; // read all stock info file int read_asif(); //takes care of options for individual stock //print info for one stock, get options for one stock, process option for one stock //pifos() gofos() pofos() int pgp(); //print menu and process option int pmapo(); float read_price(); int read_int(); float get_price_after_pat(char *pattern){ float price; int pattern_len = 0; char num_str[MNS]; int ch; while ((ch = pattern[pattern_len++]) != '\0') ; --pattern_len; int matches = 0; int sp = 0; while ((ch = getc(wtf)) != EOF){ if (ch != pattern[matches]) matches = 0; else ++matches; if (matches == pattern_len) break; } if (ch == EOF) return -1; int i = 0; while ((ch = getc(wtf)) != '"'){ i++; if (isdigit(ch) || ch == '.') num_str[sp++] = ch; if (i > MNS) break; } num_str[sp] = '\0'; price = atof(num_str); return (price > 0) ? price : -1; } int is_valid_url(char *the_url){ int i; char temp_the_url[MAX_CH]; char *good_url ="www.google.com/finance/quote/"; // the idea here is to put the first 29 chars into temp_the_url for (i = 0; i < 29; i++){ temp_the_url[i] = the_url[i]; } temp_the_url[i] = '\0'; return ((strcmp(temp_the_url, good_url)) == 0) ? 1 : -1; } // use ddgr to generate a file with the correct name and url for a given stock // nos = name of stock int get_correct_n_and_u(char *nos){ char path[MAX_CH]; strcpy(path, getenv("HOME")); strcat(path, "/.gfst/gfst_temp_file.txt"); char ddgr_cmd[MAX_CH] = "ddgr -n=1 -x --np google finance quote "; strcat(ddgr_cmd, nos); strcat(ddgr_cmd, " > "); strcat(ddgr_cmd, path); system(ddgr_cmd); FILE *gfst_temp_file = fopen(path, "r"); char sn[MAX_CH]; //correct stock name char snt[MAX_CH]; //correct stock name temp int si = 0; //string index // the first line in the file is the stock name (hopefully) while ((snt[si++] = getc(gfst_temp_file)) != '\n' && (si < MAX_CH)) ; if (si == MAX_CH) return -1; si = 0; char url[MAX_CH]; //correct url char urlt[MAX_CH]; //correct url temp // the second line in the file is the google finance url (hopefully) while ((urlt[si++] = getc(gfst_temp_file)) != '\n' && (si < MAX_CH)) ; if (si == MAX_CH) return -1; si = 0; // time to clean up the name, remove "1. " from the beginning and // cut it at the final ")" while (!isalpha(snt[si++])) if (si > MAX_CH) return -1; --si; int si2 = 0; while ((sn[si2++] = snt[si++]) != ')' && (si < MAX_CH)) ; if (si == MAX_CH) return -1; sn[si2] = '\0'; char ch; si = 0; si2 = 0; while ((ch = urlt[si++]) != 'w') if (si > 199) return -1; --si; while ((url[si2++] = urlt[si++]) != ' ') if (si > 199) return -1; --si2; url[si2] = '\0'; int counter = 0; //remove the mystery newline... while (url[counter++] != '\0'){ if (url[counter-1] == '\n') url[counter-1] = '\0'; } int check = is_valid_url(url); if (check == 1){ strcpy(g_temp_sn, sn); strcpy(g_temp_url, url); } char rm_cmd[MAX_CH] = "rm "; strcat(rm_cmd, path); system(rm_cmd); return check; } float get_current_st_price(char *gf_url){ char wtfp[MAX_CH]; //wget temp file path strcpy(wtfp, getenv("HOME")); strcat(wtfp, "/.gfst/wtf.txt"); char touch_cmd[MAX_CH] = "touch "; strcat(touch_cmd, wtfp); system(touch_cmd); char wget_cmd[MAX_CH] = "wget "; strcat(wget_cmd, gf_url); strcat(wget_cmd, " -q -O "); strcat(wget_cmd, wtfp); system(wget_cmd); wtf = fopen(wtfp, "r"); float csp = get_price_after_pat(SP); return csp; } // read string between delimiter d int rsbd(FILE *file, char *s, char d) { char c; while ((c = getc(file) != d)) ; while ((*s++ = getc(file)) != d) ; --s; *s = '\0'; if (s[0] == '\0') return -1; return 0; } int getYn(){ int yes = 0; char ch = getchar(); if (ch == '\n') yes = 1; while (ch != '\n'){ if (ch == 'y' || ch == 'Y'){ yes = 1; while ((getchar()) != '\n') ; } ch = getchar(); } return yes; } void sort_struct(){ // function to alphabetize an array of structs struct stock temp; for(int h = 1; h < num_of_st; h++){ temp = sta[h]; int k = h - 1; while(k >= 0 && (strcmp(temp.sn, sta[k].sn)) < 0){ sta[k + 1] = sta[k]; --k; } sta[k + 1] = temp; } } void delete_stock(){ void ros(int); printf("\n\tDelete data for %s?" " [Y/n] ", sta[option-1].sn); int sure = getYn(); if (sure){ asif = fopen(path_to_asif, "w"); fprintf(asif, "%d\n", num_of_st-1); for (int i = 0; i < num_of_st; i++){ if (i == option - 1){ ; //do nothing }else{ ros(i); } } fclose(asif); --num_of_st; }else{ ; //do nothing } return; } // record one stock void ros(int i){ fprintf(asif, "\n#%s#\t%s\n%lf\t%d\n%s\n", sta[i].sn, sta[i].pd, sta[i].pp, sta[i].q, sta[i].url); return; } void record_info(){ asif = fopen(path_to_asif, "w"); fprintf(asif, "%d\n", num_of_st); for (int i = 0; i < num_of_st; i++) ros(i); fclose(asif); return; } void create_new_st(){ int si = 0; //string index char temp_sn[MAX_C] = ""; //stock name float temp_pp = 0; // purchase price int temp_q = 0; // quantity of shares purchased int check = 1; temp_pp = 0; temp_q = 0; check = 1; for (int i = 0; i < MAX_C; i++) temp_sn[i] = '\0'; printf("\n\tStock name: "); char ch; while (((ch = getchar()) != '\n') && (si < MAX_CH - 1)){ if ((isalpha(ch)) || (ch == ' ') || ch == '\n'){ temp_sn[si++] = ch; }else{ check = -1; while((ch = getchar()) != '\n'){ ; //do nothing } break; } } temp_sn[si] = '\0'; si = 0; if (check == 1){ check = get_correct_n_and_u(temp_sn); } if (check == 1){ check = is_valid_url(g_temp_url); } if (check == 1){ ; //do nothing }else{ printf("\n\n\tThere seems to have been a problem finding that stock.\n\t" "Perhaps try typing the name differently.\n\n"); create_new_st(); return; } g_csp = get_current_st_price(g_temp_url); printf("\n\t%s current value: $%.2f", g_temp_sn, g_csp ); printf("\n\tIs this your stock? [Y/n]: "); check = getYn(); if (check == 1){ ; //do nothing }else{ printf("\n\tOkay, let's try again."); create_new_st(); return; } printf("\n\tWhat was the purchase price? "); temp_pp = read_price(); printf("\n\tAnd how many shares did you purchase? "); temp_q = read_int(); printf("\n\n\tYou have provided the following information:\n\n\t" "Stock name : %s\n\tPurchase price : $%.2lf\n\t" "Number of shares: %d\n", g_temp_sn, temp_pp, temp_q); printf("\n\tIs this correct? [Y/n]: "); check = getYn(); if (check == 1){ strcpy(sta[num_of_st].sn, g_temp_sn); strcpy(sta[num_of_st].pd, sds); sta[num_of_st].pp = temp_pp; sta[num_of_st].q = temp_q; strcpy(sta[num_of_st].url, g_temp_url); ++num_of_st; sort_struct(); record_info(); read_asif(); }else{ create_new_st(); } return; } int read_asif(){ asif = fopen(path_to_asif, "r"); fscanf(asif, "%d\n", &num_of_st); for (int i = 0; i < num_of_st; i++){ rsbd(asif, sta[i].sn, D); fscanf(asif, "%s %f %d %s", sta[i].pd, &sta[i].pp, &sta[i].q, sta[i].url); } fclose(asif); return 0; } void get_all_cps(){ for (int i = 0; i < num_of_st; i++){ sta[i].cp = get_current_st_price(sta[i].url); } return; } void display_all_stocks(){ float dif = 0, oadif = 0; //difference and over all difference for (int i = 0; i < num_of_st; i++){ printf("\n\t%3d. %-40s value: $%.2f\n\t Purchase price: $%-7.2f quantity %-3d" , i + 1, sta[i].sn, sta[i].cp, sta[i].pp, sta[i].q); dif = ((sta[i].cp - sta[i].pp) * sta[i].q); (dif >= 0) ? printf(" gain: $%.2f\n", dif): printf(" loss: $%.2f\n", dif); oadif += dif; } (oadif >= 0) ? printf("\n\n\tGood news! Your overall gain is currently $%.2f.\n", oadif): printf("\n\n\tBad news: Your overall loss is currently $%.2f.\n", oadif); return; } int read_option(){ printf("\n\tEnter option: "); int num, i = 0; char num_str[MNS]; char ch; while ((ch = getchar()) == ' '){ ; //do nothing } if (isdigit(ch) || ch == '-'){ num_str[i++] = ch; } while (i < MNS && ch != '\n'){ ch = getchar(); if (isdigit(ch)){ num_str[i++] = ch; } } if ((i == 1 && num_str[0] == '-') || (i == 0)){ return ERROR; } num_str[i] = '\0'; num = atoi(num_str); return num; } // select or create portfolio int socp(){ char *home = getenv("HOME"); printf("\n\tEnter existing portfolio name or create a new one: "); char user_name[MAX_C]; char ch; int si = 0; while ((ch = getchar()) != '\n'){ if (isalpha(ch) || isdigit(ch)){ user_name[si++] = ch; } if (si > MAX_C - 1){ printf("\n\tUser name excedes maximum limit.\n"); return -1; } } if (si < 2) return -1; user_name[si] = '\0'; strcpy(g_user_name, user_name); strcat(path_to_asif, home); strcat(path_to_asif, "/.gfst/"); strcat(path_to_asif, user_name); strcat(path_to_asif, "_asif.txt"); if( access( path_to_asif, F_OK ) != -1 ) { return 0; }else{ asif = fopen(path_to_asif, "w"); fprintf(asif, "%d\n", 0); fclose(asif); num_of_st = 0; no_stocks = 1; } return 0; } void print_sta_and_menu(){ printf("\n\t****************************************************************" "\n\t|| GOOGLE FINANCE STOCK TRACKER ||" "\n\t****************************************************************\n"); printf("\n\t %s", lds); //select or create portfolio if (asif_not_set){ socp(); asif_not_set = 0; } read_asif(); if (num_of_st > 0){ get_all_cps(); display_all_stocks(); }else{ printf("\n\n\t0. No stocks currently recorded\n\n"); } printf("\n\tOPTIONS\n"); printf("\n\tTo view and/or edit a stock, enter its number." "\n\tTo add a new stock enter the next consecutive number." "\n\tTo delete a stock, enter its number preceded by \"-\"." "\n\tTo exit, enter the number 0.\n"); return; } //get fresh date stings void gfds(){ //create date_file.txt char dfp[MAX_CH]; //date_file.txt path strcpy(dfp, getenv("HOME")); strcat(dfp, "/.gfst/date_file.txt"); char date_cmd[MAX_CH] = "date > "; strcat(date_cmd, dfp); system(date_cmd); char date_cmd2[MAX_CH] = "date +\%D >> "; strcat(date_cmd2, dfp); system(date_cmd2); //and read the dates into strings date_file = fopen(dfp, "r"); int si = 0; // string index while ((lds[si++] = getc(date_file)) != '\n'){ if (si > MAX_C - 1){ break; } } lds[si] = '\0'; si = 0; while ((sds[si++] = getc(date_file)) != '\n'){ if (si > MAX_C - 1){ break; } } sds[si] = '\0'; si = 0; return; } //print info for one stock void pifos(){ int omo = option - 1; // option minus one printf("\n\tSTOCK INFORMATION\n\n\tName: %s\n\tPurchase date: %s\n\tPurchase price: $%.2f" "\n\tQuantitiy: %d\n\tTotal invesment: $%.2f", sta[omo].sn, sta[omo].pd, sta[omo].pp, sta[omo].q, (sta[omo].pp * sta[omo].q)); //get fresh date strings gfds(); // tpp is total value at purchase price (purchase price x quantity) // tcp is total value at current price (current price x quantity) float tpp = sta[omo].pp * sta[omo].q, tcp = sta[omo].cp * sta[omo].q, td = tcp - tpp; printf("\n\n\tToday, %s\tEach share is currently: $%.2f\n\tTotal value: $%.2f" "\n\t$%.2f - $%.2f = $%.2f\n\t$%.2f - $%.2f = $%.2f", lds, sta[omo].cp, tcp, sta[omo].cp, sta[omo].pp, (sta[omo].cp - sta[omo].pp), tcp, tpp, td); (td > 0) ? printf("\n\tTotal increase from this stock: $%.2f\n", td): printf("\n\tTotal loss from this stock: $%.2f\n", td); printf("\n OPTIONS" "\n\tChange purchase date 'd', change purchase price 'p'" "\n\tChange quantity 'q', main menu 'm', exit program 'e'"); return; } // filter bad options from possible options (pop) // (This is probably an inefficient way to do this whole thing // but it seems to work. Maybe this could be pofos (process options for one stock) int bad_op(char pop){ switch (pop){ case 'd': return 0; case 'p': return 0; case 'q': return 0; case 'e': return 0; case 'm': return 0; } return 1; } //get option for one stock char gofos(){ char ch; char op; printf("\n\n\tEnter option: "); //keeps reading from getchar until a valid option is entered //I don't understand why the != '\n' line is necessary //but with out it the printf executes twice. while ((bad_op(op = getchar()))){ if (op != '\n'){ printf("\tInvalid option. Try again: "); } } while ((ch = getchar()) != '\n') ; return op; } //get date and validate format void gdavf(){ printf("\n\tEnter the new date mm/dd/yy : "); int success = 1; char temp_d[9]; scanf("%s", temp_d); getchar(); if (temp_d[2] != '/' || temp_d[5] != '/' || temp_d[8] != '\0') success = 0; if (success){ strcpy(sta[option-1].pd, temp_d); printf("\n\tThe purchase date has been updated.\n"); record_info(); }else{ gdavf(); } return; } //process options for one stock int pofos(char op){ switch (op) { case 'd': gdavf(); return pgp(); break; case 'p': printf("\n\tEnter the new purchase price: "); sta[option - 1].pp = read_price(); record_info(); printf("\n\tThe purchase price has been updated.\n"); return pgp(); break; case 'q': printf("\n\tEnter the new quantity: "); sta[option -1].q = read_int(); record_info(); printf("\n\tThe quantity has been updated.\n"); return pgp(); break; case 'm': return 1; break; case 'e': printf("\n\tHave a nice day!\n\n"); return -1; break; } printf("\nHow is it getting here?\n"); return 0; } // pifos gofos pofos int pgp(){ //pifos prints info for one stock pifos(); //gofos gets option for one stock and returns it to pofos //which then processes option for one stock return pofos(gofos()); } // print menu and process option int pmapo(){ gfds(); print_sta_and_menu(); while (((option = read_option()) == ERROR) || (option < -num_of_st) || (option > num_of_st + 1)) ; if (option == 0){ printf("\n\tHave a nice day!\n\n"); return -1; }else if (option == num_of_st + 1){ //I can't imagine this would ever come up. The program would //take forever with anything more than a few stocks. if (option > MAX_S - 1){ printf("\n\tWow! You have a lot of stocks." "\n\tYou must delete one of your current" "\n\tstocks in order to add a new one.\n"); return pgp(); }else{ create_new_st(); } }else if (option < 0 && option >= -num_of_st){ option = -option; delete_stock(); read_asif(); }else if (option > 0 && option <= num_of_st) return pgp(); return 1; } float read_price(){ char tps[MAX_C]; char ch; int si = 0; //string index while ((ch = getchar()) != '\n'){ if (isdigit(ch) || ch == '.'){ tps[si++] = ch; } } tps[si] = '\0'; si = 0; float temp = atof(tps); if (temp > 0){ return temp; }else{ printf("\n\tPlease enter price again: "); temp = read_price(); } return temp; } // read a positive int int read_int(){ int num = 0, si = 0; char ch; char string[MAX_C]; while ((ch = getchar()) != '\n'){ if (si > MAX_C - 1){ break; } if (isdigit(ch)){ string[si++] = ch; } } string[si] = '\0'; num = atoi(string); if (si > 0){ return num; }else{ printf("\n\tPlease enter again: "); num = read_int(); } return num; } int main() { //check for HOME/.gfst directory. If it doesn't exist, make it. char *home = getenv("HOME"); char gfst_dir[MAX_CH]; strcpy(gfst_dir, home); strcat(gfst_dir, "/.gfst"); DIR* dir = opendir(gfst_dir); if (dir) { // HOME/.gfst exists closedir(dir); } else if (ENOENT == errno) { char mkdir_cmd[MAX_CH] = "mkdir "; strcat(mkdir_cmd, gfst_dir); system(mkdir_cmd); } else { printf("\n\tUnknown error : (\n\n"); return -1; } // Keep printing the menu and processing the option // until the user exits the program while (pmapo() == 1){ ; //do nothing } return 0; }