Skip to content

Commit

Permalink
Merge pull request #6 from SurferSamuel/ui-update-2
Browse files Browse the repository at this point in the history
UI Update 2
  • Loading branch information
SurferSamuel authored Sep 14, 2024
2 parents 606eee8 + 68fe2af commit 7b65a54
Show file tree
Hide file tree
Showing 70 changed files with 3,129 additions and 541 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# Stash

A stock portfolio management application, written in Typescript using React.
A stock management application written in TypeScript, using Electron and React.

![image](https://github.com/user-attachments/assets/a4613299-3daf-4c85-9f33-fe2b8e1c943f)

## Installation

1. Clone repo
1. Clone the repository

```
git clone [email protected]:SurferSamuel/Stash.git
```

2. Install packages

```
npm install
```

3. Run the application

```
npm start
npm run start
```
35 changes: 14 additions & 21 deletions electron/api/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,24 @@ if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}

// Rename existing 'latest.log' (if exists)
const latestPath = path.join(dirPath, "latest.log");
if (fs.existsSync(latestPath)) {
// Get the date of when 'latest.log' was made
const dateCreated = fs.statSync(latestPath).birthtime;
const dateString = dayjs(dateCreated).format("DD-MM-YYYY");
const today = dayjs().format('DD-MM-YYYY');
const pattern = new RegExp(`^${today}_(\\d{2}).log$`);

// Helper function that returns the path name given the number
// Eg. 05-07-2024_01
const newPath = (num: number) => {
return path.join(dirPath, `${dateString}_${String(num).padStart(2, '0')}.log`);
// Find the highest number already used in the logs
let maxNumber = 0;
fs.readdirSync(dirPath).forEach((filename) => {
const match = filename.match(pattern);
if (match != null) {
const num = parseInt(match[1]) || 0;
if (num > maxNumber) {
maxNumber = num;
}
}

// Find the next avaliable number
let i = 1;
while (fs.existsSync(newPath(i))) {
i++;
}

// Rename the latest log
fs.renameSync(latestPath, newPath(i));
}
});

// Setup the log file
const logPath = path.join(dirPath, "latest.log");
const logFilename = `${today}_${String(maxNumber + 1).padStart(2, '0')}.log`;
const logPath = path.join(dirPath, logFilename);
fs.writeFileSync(logPath, "");
const stream = fs.createWriteStream(logPath, { flags: "a" });

Expand Down
41 changes: 16 additions & 25 deletions electron/api/portfolio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export const getPortfolioGraphData = async (filterValues: PortfolioFilterValues)
// Calculate the value at the time of the historical entry
const time = dayjs(historical.date);
const units = countUnitsAtTime(company, filterValues.user, time);
const value = units * historical.adjClose;
const value = units * historical.adjclose;

// Add value to the existing data point (if possible), otherwise make new data point
const graphEntry = graphData.find(entry => time.isSame(entry.date, "day"));
Expand Down Expand Up @@ -282,26 +282,14 @@ export const getPortfolioGraphData = async (filterValues: PortfolioFilterValues)
}
}

// 1, 3, and 6 month interval data
const oneMonth = graphData.filter(entry => dayjs().subtract(1, "month").isBefore(entry.date, "day"));
const threeMonth = graphData.filter(entry => dayjs().subtract(3, "month").isBefore(entry.date, "day"));
const sixMonth = graphData.filter(entry => dayjs().subtract(6, "month").isBefore(entry.date, "day"));

// 1 and 5 year interval data
// NOTE: Uses weekly data, not daily data
const oneYear = graphData.filter(entry =>
(entry.date.getDay() == 1 && dayjs().subtract(12, "month").isBefore(entry.date, "day")) ||
dayjs().isSame(entry.date, "day")
);
const fiveYear = graphData.filter(entry => entry.date.getDay() == 1 || dayjs().isSame(entry.date, "day"));

// Return data for each graph range (in months)
// NOTE: Only use weekly data for 5 years interval
return {
1: oneMonth,
3: threeMonth,
6: sixMonth,
12: oneYear,
60: fiveYear,
1: graphData.filter(entry => dayjs().subtract(1, "month").isBefore(entry.date, "day")),
3: graphData.filter(entry => dayjs().subtract(3, "month").isBefore(entry.date, "day")),
6: graphData.filter(entry => dayjs().subtract(6, "month").isBefore(entry.date, "day")),
12: graphData.filter(entry => dayjs().subtract(12, "month").isBefore(entry.date, "day")),
60: graphData.filter(entry => entry.date.getDay() == 1 || dayjs().isSame(entry.date, "day")),
};
}

Expand Down Expand Up @@ -368,27 +356,30 @@ const getHistoricalData = async (asxcodes: string[]) => {
// If no updates needed, can return early
if (needUpdate.length === 0) return data;

// Query options for each historical request
// Query options for each chart request
const queryOptions = {
period1: dayjs().subtract(5, "year").toDate(),
interval: "1d" as const,
};

// Send historical requests in parallel (within concurrency limit)
// Send chart requests in parallel (within concurrency limit)
const responseArray = await Promise.allSettled(needUpdate.map(async (asxcode) => {
const historical = await yahooFinance.historical(`${asxcode}.AX`, queryOptions);
const chart = await yahooFinance.chart(`${asxcode}.AX`, queryOptions);
const historical = chart.quotes;
return { asxcode, historical };
}));

// Last updated is right now
const lastUpdated = dayjs().format("DD/MM/YYYY hh:mm A");

// Update data using responses
for (const response of responseArray) {
if (response.status === "fulfilled") {
// Extract values from response
// NOTE: Only keep weekly data for entries >6 months ago
// NOTE: Only keep weekly data for entries >1 year ago
const asxcode = response.value.asxcode;
const lastUpdated = dayjs().format("DD/MM/YYYY hh:mm A");
const historical = response.value.historical
.filter(entry => dayjs().diff(entry.date, "month") < 6 || entry.date.getDay() == 1);
.filter(entry => dayjs().diff(entry.date, "year") < 1 || entry.date.getDay() == 1);

// Find the existing entry and update it (if possible), otherwise add a new entry
const existingEntry = data.find(entry => entry.asxcode === response.value.asxcode);
Expand Down
4 changes: 2 additions & 2 deletions electron/api/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const quickValidateASXCode = (asxcode: string) => {

// ASX code must not already exist in data (ie. a new asxcode)
if (data.some(obj => obj.asxcode === asxcode)) {
return "Already existing company";
return "Company already added";
}

return "Valid";
Expand Down Expand Up @@ -51,7 +51,7 @@ export const validateASXCode = async (asxcode: string, existing: boolean) => {

// ASX code must not already exist in data (ie. a new asxcode)
if (!existing && data.some(obj => obj.asxcode === asxcode)) {
return { status: "Already existing company", companyName: "", unitPrice: undefined };
return { status: "Company already added", companyName: "", unitPrice: undefined };
}

try {
Expand Down
4 changes: 2 additions & 2 deletions electron/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export { PortfolioFormValues as PortfolioFilterValues } from "../src/pages/portf

// Yahoo-finance2 types
export { HistoricalOptionsEventsHistory } from "yahoo-finance2/dist/esm/src/modules/historical";
import { HistoricalRowHistory } from "yahoo-finance2/dist/esm/src/modules/historical";
import { ChartResultArrayQuote } from "yahoo-finance2/dist/esm/src/modules/chart";

// Valid option keys, used for company details and users
export type OptionKey =
Expand Down Expand Up @@ -176,7 +176,7 @@ export interface PortfolioTableData {
export interface HistoricalEntry {
asxcode: string;
lastUpdated: string;
historical: HistoricalRowHistory[];
historical: ChartResultArrayQuote[];
}

// Data point type for the portfolio graph
Expand Down
Loading

0 comments on commit 7b65a54

Please sign in to comment.