aboutsummarylogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.SRCINFO2
-rw-r--r--Makefile2
-rw-r--r--PKGBUILD2
-rw-r--r--README.md10
-rw-r--r--main.c9
-rw-r--r--portfolio.c340
-rw-r--r--portfolio.h64
-rw-r--r--tick.114
8 files changed, 240 insertions, 203 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 1e1b6e66376a..c4b045a63769 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = tick
pkgdesc = Command line stock and cryptocurrency portfolio tracker.
- pkgver = 1.6.4
+ pkgver = 1.7.0
pkgrel = 1
url = https://github.com/aokellermann/tick
arch = x86_64
diff --git a/Makefile b/Makefile
index 71fc12efe82c..bcacc35532c0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
CC = gcc
CFLAGS = -g -Wall --std=c99
OBJECTS = main.o api.o portfolio.o
-LIBS = -lcurl -ljson-c
+LIBS = -lcurl -ljson-c -lm
BIN = tick
DESTDIR = /usr
diff --git a/PKGBUILD b/PKGBUILD
index 59468fb0d390..b4c906ba8ae1 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,7 +1,7 @@
# Maintainer: Antony Kellermann <aokellermann@gmail.com>
pkgname=tick
-pkgver=1.6.4
+pkgver=1.7.0
pkgrel=1
pkgdesc="Command line stock and cryptocurrency portfolio tracker."
arch=('x86_64')
diff --git a/README.md b/README.md
index d57faa476dad..31d00e80647c 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,11 @@ $ tick news [symbol]
If you wish to use spaces in your input, you can either surround the phrase
with quotes or replace spaces with underscores.
+As of version 1.7, the portfolio file has been reconstructed and formatted
+in JSON. To convert your existing portfolio, run
+```bash
+$ tick convert
+```
Once installed, you may read the man page for more information.
#### License
@@ -58,7 +63,8 @@ license for more information.
#### Future Ideas
* Command to get info about a security
* Historical support -- 7d/28d profits
-* Change portfolio structure to JSON format
* Different ways to sort "check all"
* Look for API to replace Morningstar for MUTF/OTCMKTS data, preferably with
-intraday data \ No newline at end of file
+intraday data
+* Encrypt data
+* Debian/RPM package \ No newline at end of file
diff --git a/main.c b/main.c
index 1bc770970f9a..4b5b0a38356f 100644
--- a/main.c
+++ b/main.c
@@ -17,9 +17,16 @@ int main(int argc, char* argv[]) {
FILE* fp = fopen(portfolio_file, "a+");
if (fp == NULL) {
printf("Could not open portfolio file\n");
+ fclose(fp);
free((void*) portfolio_file);
return 0;
}
+ if (argc == 2 && strcmp(argv[1], "convert") == 0){
+ portfolio_legacy_convert();
+ free((void*) portfolio_file);
+ fclose(fp);
+ return 0;
+ }
for (int i = 0; argv[2][i] != '\0'; i++)
argv[2][i] = (char) toupper(argv[2][i]);
if (argc == 5) {
@@ -39,7 +46,7 @@ int main(int argc, char* argv[]) {
if (argc == 3 && strcmp(argv[1], "check") == 0) {
if (strcmp(argv[2], "ALL") == 0)
portfolio_print_all(fp);
- else free(portfolio_print_stock(argv[2], fp));
+ else free(portfolio_print_stock(argv[2], fp, NULL));
} else printf("Invalid arguments.\n");
}
free((void*) portfolio_file);
diff --git a/portfolio.c b/portfolio.c
index 7c9b4b12a04c..89386ab5184a 100644
--- a/portfolio.c
+++ b/portfolio.c
@@ -2,208 +2,232 @@
void portfolio_file_init() {
char* home = getenv("HOME");
- char* path = (char*) malloc(strlen(home) + 21);
+ char* path = (char*) malloc(strlen(home) + 30);
memcpy(path, home, strlen(home) + 1);
- memcpy(&path[strlen(home)], "/.tick_portfolio", 17);
+ memcpy(&path[strlen(home)], "/.tick_portfolio.json", 25);
portfolio_file = path;
}
+char* portfolio_file_get_string(FILE* fp) {
+ char* portfolio_string = calloc(65536, 1);
+ fseek(fp, 0, SEEK_END);
+ size_t portfolio_size = (size_t) ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(portfolio_string, portfolio_size, sizeof(char), fp);
+ return portfolio_string;
+}
+
void portfolio_modify(char* ticker_name_string, double quantity_shares, double usd_spent, FILE* fp, int option) {
- if (portfolio_contains(ticker_name_string, fp)) {
- long position = ftell(fp);
- fseek(fp, 0, SEEK_END);
- size_t length = (size_t) ftell(fp);
- char* beginning = (char*) calloc(length, sizeof(char));
- char* end = (char*) calloc(length, sizeof(char));
- fseek(fp, 0, SEEK_SET);
- for (int i = 0; i < (int) position; i++)
- beginning[i] = (char) fgetc(fp);
- fseek(fp, position, SEEK_SET);
- double new_quantity_shares;
- double new_usd_spent;
- if (option == ADD) {
- new_quantity_shares = get_next_val(fp) + quantity_shares;
- new_usd_spent = get_next_val(fp) + usd_spent;
- } else if (option == REMOVE) {
- new_quantity_shares = get_next_val(fp) - quantity_shares;
- new_usd_spent = get_next_val(fp) - usd_spent;
- } else {
- new_quantity_shares = quantity_shares;
- new_usd_spent = usd_spent;
- get_next_val(fp);
- get_next_val(fp);
- }
- char c;
- for (int i = 0;; i++) {
- c = (char) fgetc(fp);
- if (feof(fp))
- break;
- end[i] = c;
+ if (quantity_shares < 0 || usd_spent < 0){
+ printf("You must use positive values.\n");
+ return;
+ }
+ if (option != SET && quantity_shares == 0 && usd_spent == 0){
+ printf("You cannot add or remove values of 0.\n");
+ return;
+ }
+ char* portfolio_string = portfolio_file_get_string(fp);
+ Json* jobj = NULL;
+ if (strcmp(portfolio_string, "") == 0) { //new file
+ jobj = json_object_new_array();
+ } else jobj = json_tokener_parse(portfolio_string); //existing file
+ if (jobj == NULL)
+ printf("NULL JOBJ\n");
+ int index = portfolio_symbol_index(ticker_name_string, jobj);
+ if (index == -1) { //if not already in portfolio
+ if (option == REMOVE) {
+ printf("You don't have any %s to remove.\n", ticker_name_string);
+ free(portfolio_string);
+ json_object_put(jobj);
+ return;
}
- if (strlen(end) > 0 && end[strlen(end) - 1] == '\n')
- end[strlen(end) - 1] = '\0';
- remove(portfolio_file);
- fp = fopen(portfolio_file, "w");
- if (new_quantity_shares >= 0 && new_usd_spent >= 0) {
- fprintf(fp, "%s%lf %lf", beginning, new_quantity_shares, new_usd_spent);
- if (strlen(end) != 0)
- fprintf(fp, "\n%s", end);
- if (option == ADD)
- printf("Added ");
- else if (option == REMOVE)
- printf("Removed ");
- else {
- printf("Set %s to %lf\n", ticker_name_string, quantity_shares);
- return;
- }
- printf("%lf %s.\n", quantity_shares, ticker_name_string);
- } else printf("You cannot remove more %s than you have!\n", ticker_name_string);
- free(beginning);
- free(end);
- fclose(fp);
- } else {
- if (strlen(ticker_name_string) > 16 ||
- (api_get_current_price(ticker_name_string) == NULL && strcmp("USD$", ticker_name_string) != 0)) {
+ if (api_get_current_price(ticker_name_string) == NULL){
printf("Invalid symbol.\n");
+ json_object_put(jobj);
+ free(portfolio_string);
return;
}
- if (option == REMOVE) {
- printf("You don't have any %s to remove!\n", ticker_name_string);
- return;
+ Json* new_object = json_object_new_object(); //creates new array index and adds values to it
+ json_object_array_add(jobj, new_object);
+ json_object_object_add(new_object, "Symbol", json_object_new_string(ticker_name_string));
+ json_object_object_add(new_object, "Shares", json_object_new_double(quantity_shares));
+ json_object_object_add(new_object, "USD_Spent", json_object_new_double(usd_spent));
+ printf("Added %lf %s bought for %lf to portfolio.\n", quantity_shares, ticker_name_string, usd_spent);
+ } else {
+ Json* current_index = json_object_array_get_idx(jobj, (size_t) index);
+ double current_shares = json_object_get_double(json_object_object_get(current_index, "Shares"));
+ double current_spent = json_object_get_double(json_object_object_get(current_index, "USD_Spent"));
+ json_object_object_del(current_index, "Shares");
+ json_object_object_del(current_index, "USD_Spent");
+ if (option == SET) {
+ current_shares = quantity_shares;
+ current_spent = usd_spent;
+ printf("Set amount of %s in portfolio to %lf bought for %lf.\n", ticker_name_string, quantity_shares, usd_spent);
+ } else if (option == ADD) {
+ current_shares += quantity_shares;
+ current_spent += usd_spent;
+ printf("Added %lf %s bought for %lf to portfolio.\n", quantity_shares, ticker_name_string, usd_spent);
+ } else {
+ current_shares -= quantity_shares;
+ current_spent -= usd_spent;
+ if (current_shares < 0 || current_spent < 0) {
+ printf("You do not have that many %s to remove.\n", ticker_name_string);
+ json_object_put(jobj);
+ free(portfolio_string);
+ return;
+ }
+ printf("Removed %lf %s bought for %lf to portfolio.\n", quantity_shares, ticker_name_string, usd_spent);
}
- fseek(fp, 0, SEEK_END);
- if (ftell(fp) != 0)
- fprintf(fp, "\n");
- fprintf(fp, "%s %lf %lf", ticker_name_string, quantity_shares, usd_spent);
- if (option == ADD)
- printf("Added ");
+ if (current_shares == 0 && usd_spent == 0) //deletes index from portfolio if values are 0
+ json_object_array_del_idx(jobj, (size_t) index, 1);
else {
- printf("Set %s to %lf\n", ticker_name_string, quantity_shares);
- return;
+ json_object_object_add(current_index, "Shares", json_object_new_double(round(current_shares * 100) / 100)); //adds computed values to index
+ json_object_object_add(current_index, "USD_Spent", json_object_new_double(round(current_spent * 100) / 100));
}
- printf("%lf %s.\n", quantity_shares, ticker_name_string);
- }
-}
-
-double portfolio_get_quantity_shares(char* ticker_name_string, FILE* fp) {
- if (portfolio_contains(ticker_name_string, fp))
- return get_next_val(fp);
- return 0;
-}
-
-double portfolio_get_usd_spent(char* ticker_name_string, FILE* fp) {
- if (portfolio_contains(ticker_name_string, fp)) {
- get_next_val(fp);
- return get_next_val(fp);
}
- return 0;
+ fp = fopen(portfolio_file, "w");
+ fprintf(fp, "%s", json_object_to_json_string(jobj));
+ fclose(fp);
+ json_object_put(jobj);
+ free(portfolio_string);
}
void portfolio_print_all(FILE* fp) {
printf(" AMOUNT SYMBOL VALUE SPENT PROFIT (%%) 24H (%%)\n");
- char* str = (char*) calloc(64, sizeof(char));
- char* ticker_name_string = (char*) calloc(32, sizeof(char));
- double* data;
- double total_owned = 0, total_spent = 0, total_gain_1d = 0;
- int i;
- char c;
- while (fgets(str, 63, fp) != NULL) {
- i = 0;
- while ((c = str[i]) != ' ') {
- ticker_name_string[i] = c;
- i++;
- }
- data = portfolio_print_stock(ticker_name_string, fp);
+ char* portfolio_string = portfolio_file_get_string(fp);
+ double* data, total_owned = 0, total_spent = 0, total_gain_1d = 0;
+ if (strcmp(portfolio_string, "") == 0)
+ return;
+ Json* jobj = json_tokener_parse(portfolio_string);
+ int num_symbols = (int) json_object_array_length(jobj);
+ for (int i = 0; i < num_symbols; i++) {
+ data = portfolio_print_stock(NULL, NULL, json_object_array_get_idx(jobj, (size_t) i));
if (data != NULL) {
total_owned += data[0];
total_spent += data[1];
total_gain_1d += data[2];
}
- memset(str, '\0', 64);
- memset(ticker_name_string, '\0', 32);
free(data);
}
printf("\n TOTALS %8.2lf %8.2lf %8.2lf (%6.2lf%%) %8.2lf (%6.2lf%%)\n",
total_owned, total_spent, total_owned - total_spent, (100 * (total_owned - total_spent)) / total_spent,
total_gain_1d, 100 * total_gain_1d / total_spent);
- free(str);
- free(ticker_name_string);
+ json_object_put(jobj);
+ free(portfolio_string);
}
-double* portfolio_print_stock(char* ticker_name_string, FILE* fp) {
+double* portfolio_print_stock(char* ticker_name_string, FILE* fp, Json* current_index) {
/**
* Values in USD
* a[0] -- current balance
* a[1] -- amount spent
* a[2] -- 1d gain
*/
- double* a = malloc(sizeof(double) * 3);
- a[0] = portfolio_get_quantity_shares(ticker_name_string, fp);
- a[1] = portfolio_get_usd_spent(ticker_name_string, fp);
- a[2] = 0;
- if (a[0] == 0 && a[1] == 0) {
- free(a);
- a = NULL;
- } else {
- double* ticker_data;
- double ticker_current_price_usd = 1, ticker_1d_price_usd, ticker_1d_percent_change = 0;
- if (strcmp(ticker_name_string, "USD$") != 0) {
- ticker_data = api_get_current_price(ticker_name_string);
- ticker_current_price_usd = ticker_data[0];
- ticker_1d_price_usd = ticker_data[1];
- free(ticker_data);
- a[2] = ((ticker_current_price_usd - ticker_1d_price_usd) * a[0]);
- ticker_1d_percent_change = 100 * (ticker_current_price_usd / ticker_1d_price_usd - 1);
- a[0] *= ticker_current_price_usd;
+ double* data = malloc(sizeof(double) * 3);
+ char* portfolio_string = NULL;
+ if (fp == NULL) { //if being called from portfolio_print_all
+ ticker_name_string = (char*) strip_char(
+ (char*) json_object_get_string(json_object_object_get(current_index, "Symbol")), '\"');
+ data[0] = json_object_get_double(json_object_object_get(current_index, "Shares"));
+ data[1] = json_object_get_double(json_object_object_get(current_index, "USD_Spent"));
+ } else { //if being called directly from main
+ portfolio_string = portfolio_file_get_string(fp);
+ Json* jobj = json_tokener_parse(portfolio_string);
+ int index = portfolio_symbol_index(ticker_name_string, jobj);
+ if (index == -1) {
+ printf("You do not have %s in your portfolio.\n", ticker_name_string);
+ return data;
}
- printf("%8.2lf %6s %8.2lf %8.2lf %8.2lf (%6.2lf%%) %8.2lf (%6.2lf%%)\n",
- a[0] / ticker_current_price_usd, ticker_name_string, a[0], a[1], a[0] - a[1],
- (100 * (a[0] - a[1])) / a[1],
- a[2], ticker_1d_percent_change);
+ current_index = json_object_array_get_idx(jobj, (size_t) index);
+ }
+ data[0] = json_object_get_double(json_object_object_get(current_index, "Shares"));
+ data[1] = json_object_get_double(json_object_object_get(current_index, "USD_Spent"));
+ data[2] = 0;
+ double* ticker_data, ticker_current_price_usd = 1, ticker_1d_price_usd, ticker_1d_percent_change = 0;
+ if (strcmp(ticker_name_string, "USD$") != 0) {
+ ticker_data = api_get_current_price(ticker_name_string);
+ ticker_current_price_usd = ticker_data[0];
+ ticker_1d_price_usd = ticker_data[1];
+ free(ticker_data);
+ data[2] = ((ticker_current_price_usd - ticker_1d_price_usd) * data[0]);
+ ticker_1d_percent_change = 100 * (ticker_current_price_usd / ticker_1d_price_usd - 1);
+ data[0] *= ticker_current_price_usd;
}
- return a;
+ printf("%8.2lf %6s %8.2lf %8.2lf %8.2lf (%6.2lf%%) %8.2lf (%6.2lf%%)\n",
+ data[0] / ticker_current_price_usd, ticker_name_string, data[0], data[1], data[0] - data[1],
+ (100 * (data[0] - data[1])) / data[1],
+ data[2], ticker_1d_percent_change);
+ if (fp == NULL) {
+ free(ticker_name_string);
+ free(portfolio_string);
+ }
+ return data;
}
-int portfolio_contains(char* ticker_name_string, FILE* fp) {
- fseek(fp, 0, SEEK_SET);
- char* temp = (char*) calloc(16, sizeof(char)), c;
- int i = 0;
- while (1) {
- c = (char) fgetc(fp);
- if (feof(fp)) {
- fseek(fp, 0, 0);
- free(temp);
- return 0;
- }
- if (c == ' ') {
- if (strcmp(ticker_name_string, temp) == 0) {
- free(temp);
- return 1;
- } else {
- while (fgetc(fp) != '\n') {
- if (feof(fp))
- break;
- }
- memset(temp, '\0', 16);
- i = 0;
- }
- } else {
- temp[i] = c;
- i++;
+int portfolio_symbol_index(char* ticker_name_string, Json* jarray) {
+ int num_symbols = (int) json_object_array_length(jarray);
+ char* str;
+ for (int i = 0; i < num_symbols; i++) {
+ str = (char*) json_object_to_json_string(
+ json_object_object_get(json_object_array_get_idx(jarray, (size_t) i), "Symbol"));
+ str = (char*) strip_char(str, '\"');
+ if (strcmp(str, ticker_name_string) == 0) {
+ free(str);
+ return i;
}
+ free(str);
}
+ return -1;
}
-double get_next_val(FILE* fp) {
- char* temp = (char*) calloc(32, sizeof(char)), c;
- int i = 0;
- while ((c = (char) fgetc(fp)) != ' ' && c != '\n') {
- if (feof(fp))
+char* portfolio_legacy_get_next_val(FILE* fp) {
+ char* val = calloc(16, 1);
+ char c;
+ for (int i = 0; i < 16; i++) {
+ c = (char) fgetc(fp);
+ if (c == ' ' || c == '\n'|| feof(fp))
break;
- temp[i] = c;
- i++;
+ val[i] = c;
}
- double val = strtod(temp, NULL);
- free(temp);
return val;
+}
+
+void portfolio_legacy_convert() {
+ printf("Warning: this will overwrite your JSON formatted portfolio, which is stored at: \"$HOME/.tick_portfolio\". Continue? y/n\n");
+ char c = 0;
+ while (c != 'y' && c != 'n')
+ scanf("%c", &c);
+ if (c == 'n'){
+ printf("Aborted.\n");
+ return;
+ }
+ FILE* fp = fopen(portfolio_file, "a+");
+ char* pf = portfolio_file_get_string(fp);
+ if (strcmp(pf, "") != 0)
+ remove(portfolio_file);
+ free(pf);
+ char* legacy_path = malloc(64);
+ strcpy(legacy_path, portfolio_file);
+ legacy_path[strlen(legacy_path) - 5] = '\0';
+ FILE* fp_legacy = fopen(legacy_path, "r");
+ free(legacy_path);
+ if (!fp) {
+ printf("Could not open legacy portfolio.\n");
+ return;
+ }
+ char* symbol, * amount, * spent;
+ while (1) {
+ if (feof(fp_legacy))
+ break;
+ symbol = portfolio_legacy_get_next_val(fp_legacy);
+ amount = portfolio_legacy_get_next_val(fp_legacy);
+ spent = portfolio_legacy_get_next_val(fp_legacy);
+ portfolio_modify(symbol, strtod(amount, NULL), strtod(spent, NULL), fp, ADD);
+ free(symbol);
+ free(amount);
+ free(spent);
+ }
+ printf("Successfully converted portfolio!\n");
+ fclose(fp_legacy);
+ fclose(fp);
} \ No newline at end of file
diff --git a/portfolio.h b/portfolio.h
index 0f140e52f538..a59584ad12b8 100644
--- a/portfolio.h
+++ b/portfolio.h
@@ -4,6 +4,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <math.h>
#include "api.h"
#define REMOVE 0
@@ -12,11 +13,15 @@
const char* portfolio_file;
+/**
+ * Sets portfolio_file to $HOME/.tick_portfolio.json
+ */
void portfolio_file_init();
+char* portfolio_file_get_string(FILE* fp);
+
/**
- * Adds quantity_shares of given currency at
- * given price to portfolio
+ * Adds quantity_shares of given symbol at given price to portfolio
* @param id name of currency
* @param quantity_shares quantity_shares of currency
* @param purchase_price quantity_shares of money spent
@@ -25,25 +30,7 @@ void portfolio_file_init();
void portfolio_modify(char* ticker_name_string, double quantity_shares, double usd_spent, FILE* fp, int option);
/**
- * Taken from the portfolio file, returns a double
- * of the quantity_shares of crypto
- * @param id the crypto
- * @param fp portfolio file
- * @return the quantity_shares
- */
-double portfolio_get_quantity_shares(char* ticker_name_string, FILE* fp);
-
-/**
- * Taken from the portfolio file, returns a double
- * of the quantity_shares of money spent of a crypto
- * @param id the crypto
- * @param fp portfolio file
- * @return the quantity_shares spent
- */
-double portfolio_get_usd_spent(char* ticker_name_string, FILE* fp);
-
-/**
- * Prints current holdings of all currencies
+ * Prints current holdings of all symbols
* @param api_data STRING to hold data in
* @param crypto_data JSON to hold STRING data in
* @param fp portfolio file
@@ -51,29 +38,32 @@ double portfolio_get_usd_spent(char* ticker_name_string, FILE* fp);
void portfolio_print_all(FILE* fp);
/**
- * Prints current holdings of a currency
- * @param id name of currency
- * @param fp portfolio file
+ * Prints current holdings of a symbol
+ * @param id name of currency, NULL if printing all
+ * @param fp portfolio file, NULL if printing all
+ * @param current_index portfolio file index, NULL if printing one
*/
-double* portfolio_print_stock(char* ticker_name_string, FILE* fp);
+double* portfolio_print_stock(char* ticker_name_string, FILE* fp, Json* current_index);
/**
- * Sets stream pointer to first digit
- * of currency quantity_shares if exists, otherwise
- * sets stream pointer to beginning of stream
- * @param id name of currency
- * @param fp portfolio file
- * @return 1 if contains, 0 if not
+ * Returns the index in the Json array of the given symbol
+ * @param ticker_name_string the symbol
+ * @param jarray the array
+ * @return -1 if not found, the index otherwise
*/
-int portfolio_contains(char* ticker_name_string, FILE* fp);
+int portfolio_symbol_index(char* ticker_name_string, Json* jarray);
/**
- * Given a file stream pointing to
- * the first digit of a number,
- * return the number as a double
+ * Returns a string from the current stream pointer to the next space, newline, or EOF
* @param fp the file
- * @return the value
+ * @return the string
+ */
+char* portfolio_legacy_get_next_val(FILE* fp);
+
+/**
+ * Adds contents of legacy portfolio to JSON formatted portfolio
+ * @param fp JSON formatted portfolio
*/
-double get_next_val(FILE* fp);
+void portfolio_legacy_convert();
#endif \ No newline at end of file
diff --git a/tick.1 b/tick.1
index 845a474f2ab7..ff517f678448 100644
--- a/tick.1
+++ b/tick.1
@@ -1,4 +1,4 @@
-.TH TICK "1" "January 2018" "Tick 1.6.4" "User Commands"
+.TH TICK "1" "January 2018" "Tick 1.7.0" "User Commands"
.SH NAME
Tick - Command line stock and cryptocurrency portfolio tracker.
@@ -28,6 +28,11 @@ are not reinvested into a specific security, you should update your portfolio wi
Prints information about your current portfolio holdings. Either a symbol or the keyword 'all' can be used. The keyword "all"
will print information about all your current holdings, as well as a grand total.
+.TP
+[convert]
+If it exists, removes $HOME/.tick_portfolio.json and adds your current holding from the legacy portfolio, located at
+$HOME/.tick_portfolio.
+
.SS
News:
@@ -39,10 +44,15 @@ your phrase in double quotes. Technically, the input may be something completely
in the future.
.SH FILES
-.I ~/.tick_portfolio
+.I ~/.tick_portfolio.json
.RS
Portfolio file.
+.RE
+.I ~/.tick_portfolio
+.RS
+Portfolio file (legacy).
+
.SH GITHUB
Please report any bugs using the GitHub issue tracker: https://github.com/aokellermann/tick/issues