aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulia McCauley <skurvyj@gmail.com>2021-04-20 11:29:24 -0400
committerJulia McCauley <skurvyj@gmail.com>2021-04-20 11:29:24 -0400
commit0f86bfc8182dd6b8559f352ebad7ff5ea8db8c73 (patch)
tree385c86356e7f95058c72aaa1f1fbbe6abe492c6d
parent8535db483bd2a153013976a0ad540d00707405ba (diff)
parent3910a31e5418343e427305bb0b77cf5ec3e2dfbf (diff)
Merge branch 'master' of github.com:cs0320-2021/term-project-cohwille-jmccaul3-mfoiani-rhunt2
# Conflicts: # react-frontend/src/components/Visualization.js
-rw-r--r--data/trades.sqlite3bin16539648 -> 16699392 bytes
-rw-r--r--react-frontend/public/assets/outline_cancel_white_18dp.pngbin0 -> 388 bytes
-rw-r--r--react-frontend/public/assets/outline_close_white_18dp.pngbin0 -> 181 bytes
-rw-r--r--react-frontend/public/assets/outline_minimize_white_18dp.pngbin0 -> 97 bytes
-rw-r--r--react-frontend/public/assets/outline_search_white_18dp.pngbin0 -> 397 bytes
-rw-r--r--react-frontend/public/assets/outline_tune_white_18dp.pngbin0 -> 171 bytes
-rw-r--r--react-frontend/public/assets/round_arrow_back_white_18dp.png (renamed from react-frontend/public/round_arrow_back_white_18dp.png)bin163 -> 163 bytes
-rw-r--r--react-frontend/public/assets/round_expand_less_white_18dp.pngbin0 -> 170 bytes
-rw-r--r--react-frontend/public/assets/round_expand_more_white_18dp.pngbin0 -> 166 bytes
-rw-r--r--react-frontend/public/link-icon.svg1
-rw-r--r--react-frontend/src/App.js23
-rw-r--r--react-frontend/src/components/Hub.js17
-rw-r--r--react-frontend/src/components/HubList.js89
-rw-r--r--react-frontend/src/components/HubWidget.js84
-rw-r--r--react-frontend/src/components/InvestorInfo.js181
-rw-r--r--react-frontend/src/components/TimeSelector.js3
-rw-r--r--react-frontend/src/components/Visualization.js34
-rw-r--r--react-frontend/src/components/WatchDogs.js16
-rw-r--r--react-frontend/src/css/App.css1
-rw-r--r--react-frontend/src/css/CoordSelector.css4
-rw-r--r--react-frontend/src/css/InvesterInfo.css61
-rw-r--r--react-frontend/src/css/UserCheckin.css34
-rw-r--r--src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java6
-rw-r--r--src/main/java/edu/brown/cs/student/term/Main.java24
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java42
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/StockHolding.java35
-rw-r--r--src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java2
-rw-r--r--src/test/java/edu/brown/cs/student/ProfitCalculationTest.java16
28 files changed, 512 insertions, 161 deletions
diff --git a/data/trades.sqlite3 b/data/trades.sqlite3
index 878261a..a15d12e 100644
--- a/data/trades.sqlite3
+++ b/data/trades.sqlite3
Binary files differ
diff --git a/react-frontend/public/assets/outline_cancel_white_18dp.png b/react-frontend/public/assets/outline_cancel_white_18dp.png
new file mode 100644
index 0000000..b989e20
--- /dev/null
+++ b/react-frontend/public/assets/outline_cancel_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/outline_close_white_18dp.png b/react-frontend/public/assets/outline_close_white_18dp.png
new file mode 100644
index 0000000..65867f3
--- /dev/null
+++ b/react-frontend/public/assets/outline_close_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/outline_minimize_white_18dp.png b/react-frontend/public/assets/outline_minimize_white_18dp.png
new file mode 100644
index 0000000..a6a12d6
--- /dev/null
+++ b/react-frontend/public/assets/outline_minimize_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/outline_search_white_18dp.png b/react-frontend/public/assets/outline_search_white_18dp.png
new file mode 100644
index 0000000..52ffe5f
--- /dev/null
+++ b/react-frontend/public/assets/outline_search_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/outline_tune_white_18dp.png b/react-frontend/public/assets/outline_tune_white_18dp.png
new file mode 100644
index 0000000..4168a63
--- /dev/null
+++ b/react-frontend/public/assets/outline_tune_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/round_arrow_back_white_18dp.png b/react-frontend/public/assets/round_arrow_back_white_18dp.png
index bbaccda..bbaccda 100644
--- a/react-frontend/public/round_arrow_back_white_18dp.png
+++ b/react-frontend/public/assets/round_arrow_back_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/round_expand_less_white_18dp.png b/react-frontend/public/assets/round_expand_less_white_18dp.png
new file mode 100644
index 0000000..a64f430
--- /dev/null
+++ b/react-frontend/public/assets/round_expand_less_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/assets/round_expand_more_white_18dp.png b/react-frontend/public/assets/round_expand_more_white_18dp.png
new file mode 100644
index 0000000..f8c7213
--- /dev/null
+++ b/react-frontend/public/assets/round_expand_more_white_18dp.png
Binary files differ
diff --git a/react-frontend/public/link-icon.svg b/react-frontend/public/link-icon.svg
new file mode 100644
index 0000000..926cbcb
--- /dev/null
+++ b/react-frontend/public/link-icon.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg fill="none" height="24" stroke="lightgreen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" x2="21" y1="14" y2="3"/></svg> \ No newline at end of file
diff --git a/react-frontend/src/App.js b/react-frontend/src/App.js
index a639edd..982f6d4 100644
--- a/react-frontend/src/App.js
+++ b/react-frontend/src/App.js
@@ -9,18 +9,17 @@ import mainlogo from './images/mainlogo.png';
import './css/Landing.css';
function App() {
- const [startApp, setStartApp] = useState(false);
-
- const startModal = () => {
- document.getElementById("main-modal").style.display = 'block';
- setStartApp(false);
- }
- const exitModal = () => {
- document.getElementById("main-modal").style.display = 'none';
- setStartApp(true);
- }
-
-
+ const [startApp, setStartApp] = useState(false);
+
+ const startModal = () => {
+ document.getElementById("main-modal").style.display = 'block';
+ setStartApp(false);
+ }
+ const exitModal = () => {
+ document.getElementById("main-modal").style.display = 'none';
+ setStartApp(true);
+ }
+
return (
<>
diff --git a/react-frontend/src/components/Hub.js b/react-frontend/src/components/Hub.js
index 8a3ac1c..1906684 100644
--- a/react-frontend/src/components/Hub.js
+++ b/react-frontend/src/components/Hub.js
@@ -10,12 +10,25 @@ import '../css/UserCheckin.css';
* @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
*/
function Hub(props) {
- // State - toggled
+ const LEN_NAME = 15;
+
+ const [isHover, setIsHover] = useState(false);
+
+ const formatName = name => {
+ if (name.length >= LEN_NAME) {
+ return props.name.substring(0, LEN_NAME - 3) + '...';
+ }
+ return props.name;
+ }
return (
<li className='Checkin'>
<div className="Img-flex">
- <span className="Clickable-name" onClick= {() => console.log(props.id)}>{props.name}</span>
+ <span
+ className="Clickable-name"
+ onMouseOver = {() => setIsHover(true)}
+ onMouseLeave = {() => setIsHover(false)}
+ onClick = {() => props.setSelectedId(props.id)}>{isHover || props.searching ? props.name : formatName(props.name)}</span>
<span>{props.value.toFixed(3)}</span>
</div>
</li>);
diff --git a/react-frontend/src/components/HubList.js b/react-frontend/src/components/HubList.js
index c9a5156..383d570 100644
--- a/react-frontend/src/components/HubList.js
+++ b/react-frontend/src/components/HubList.js
@@ -1,63 +1,64 @@
// React and component imports
import { useEffect, useState } from "react";
import Hub from "./Hub.js";
-import InvestorInfo from "./InvestorInfo.js";
+import uuid from 'react-uuid';
// CSS import
-import '../css/UserCheckin.css';
+import "../css/UserCheckin.css";
/**
* Component that build the checkin list and displays checkin info.
- * @returns {import('react').HtmlHTMLAttributes} A div with the hubs
+ * @returns {import('react').HtmlHTMLAttributes} A div with the hubs
* in a vertical layout.
*/
function HubList(props) {
- const [hubItems, setHubItems] = useState([]);
- const [isSelected, setIsSelected] = useState(false);
- const [name, setName] = useState('');
-
+ const [displayedItems, setDisplayedItems] = useState([]);
+
/**
- * Loads new the checkins into the current cache/map of hubs.
+ * Method that determines whehter the Hub should be showed.
+ * @returns {Boolean} True if to be shown, false if not.
*/
- const updateHubItems = () => {
- // sort and create the elemnts
- let hubs = [];
- //const sorted = props.data.sort((a, b) => b.suspicionScore - a.suspicionScore);
- props.data.forEach(hub => hubs.push(
- <Hub key={hub.id} id={hub.id} name={hub.name} value={hub.suspicionScore} setSelected={props.setSelected}></Hub>
- ));
-
- setHubItems(hubs);
+ const toInclude = holder => {
+ // TODO: add number search or differentiate between it
+ // TODO: add sus score range....
+ if (!holder) {
+ return false;
+ };
+
+ // const matchingId = holder.id.toString().includes(queryString.toLowerCase());
+ //console.log(props.queryString.toLowerCase(), props.data.length);
+ const matchingName = holder.name.toLowerCase().includes(props.queryString.toLowerCase());
+ return matchingName;
}
- const getName = () => {
- props.data.forEach(hub => {
- if (hub.id === props.selected) {
- setName(hub.name);
- }
- })
- setName('');
+ const toHubElement = hub =>
+ <Hub
+ id={hub.id}
+ name={hub.name}
+ value={hub.suspicionScore}
+ setSelectedId={props.setSelectedId}
+ searching={props.searching}
+ ></Hub>;
+
+ /**
+ * Filters the items to be shown, then created the iteams and sets the state with the items.
+ */
+ const filterItems = () => {
+ if (!props.queryString) {
+ // don't need to show all unless searching
+ return setDisplayedItems(props.data.slice(0,600).map(hub => toHubElement(hub)))
+ }
+
+ const criteria = props.data.filter(holder => toInclude(holder));
+ setDisplayedItems(criteria.map(hub => toHubElement(hub)));
}
-
- // React hook that updates when the hubs are recalculated
- useEffect(() => updateHubItems(), [props.data]);
-
- //React hook to show data for an investor
- useEffect(() => {
- setIsSelected(true)
- getName();
- }, [props.selected]);
-
- return (
- <div className="User-checkin">
- <div className="Checkins">
- <h2>Suspicion Ranks</h2>
- <ul className='Checkin-list'>{hubItems}</ul>
- </div>
- <InvestorInfo personId={props.selected} isSelected={isSelected} name={name} dates={props.dates}></InvestorInfo>
- </div>
- );
+ /**
+ * Hook to update the items on change of the search string or update of data.
+ */
+ useEffect(() => filterItems(), [props.queryString, props.data, props.searching]);
+
+ return <ul className='Checkin-list'>{displayedItems}</ul>;
}
-export default HubList; \ No newline at end of file
+export default HubList;
diff --git a/react-frontend/src/components/HubWidget.js b/react-frontend/src/components/HubWidget.js
new file mode 100644
index 0000000..7a82c61
--- /dev/null
+++ b/react-frontend/src/components/HubWidget.js
@@ -0,0 +1,84 @@
+// React and component imports
+import { useEffect, useState, useRef } from "react";
+import Hub from "./Hub.js";
+import InvestorInfo from "./InvestorInfo.js";
+
+// CSS import
+import '../css/UserCheckin.css';
+import HubList from "./HubList.js";
+
+/**
+ * Component that build the checkin list and displays checkin info.
+ * @returns {import('react').HtmlHTMLAttributes} A div with the hubs
+ * in a vertical layout.
+ */
+function HubWidget(props) {
+ // States for selected person
+ const [followers, setFollowers] = useState([]);
+ const [selectedName, setSelectedName] = useState([]);
+
+ const [searching, setSearching] = useState(false);
+ const textInput = useRef();
+
+ const [showSelectedInfo, setShowSelectedInfo] = useState(false);
+
+ const updateSelected = () => {
+ props.data.forEach(holder => {
+ if (holder.id === props.selectedId) {
+ console.log(holder);
+ setFollowers(holder.followers);
+ setSelectedName(holder.name);
+ }
+ });
+ }
+
+ // Don't need to fetch if repeat id...
+ const updateSelectedId = id => {
+ setShowSelectedInfo(true);
+ props.setSelectedId(id);
+ }
+
+ // Hook to update and show the info when a user is clicked on ...
+ useEffect(() => {
+ updateSelected();
+ }, [props.selectedId]);
+ // On init, don't show...
+ useEffect(() => setShowSelectedInfo(false), []);
+
+ useEffect(() => {
+ if (searching) {
+ return;
+ }
+ textInput.current.value = "";
+ setQueryString("");
+ }, [searching])
+
+ const [queryString, setQueryString] = useState("");
+
+ return (
+ <>
+ <div className="User-checkin">
+ <div className="Checkins">
+ <h2 className="Img-flex Title">
+ <span hidden={searching}>Sus Ranks</span>
+ <input ref={textInput} hidden={!searching} type="text" onChange={e => setQueryString(e.target.value)} placeholder="Search by name"></input>
+ <img className="Img-btn" hidden={searching} onClick={() => setSearching(true)} src="assets/outline_search_white_18dp.png" alt="image"/>
+ <img className="Img-btn" hidden={!searching} onClick={() => setSearching(false)} src="assets/outline_cancel_white_18dp.png" alt="image"/>
+ </h2>
+ <HubList data={props.data} setSelectedId={updateSelectedId} queryString={queryString} searching={searching}></HubList>
+ </div>
+ <InvestorInfo
+ selectedId={props.selectedId}
+ setSelectedId={updateSelectedId}
+ dates={props.dates}
+ setShowSelectedInfo={setShowSelectedInfo}
+ showSelectedInfo={showSelectedInfo}
+ followers={followers}
+ name={selectedName}
+ ></InvestorInfo>
+ </div>
+ </>
+ );
+}
+
+export default HubWidget; \ No newline at end of file
diff --git a/react-frontend/src/components/InvestorInfo.js b/react-frontend/src/components/InvestorInfo.js
index d368984..436fa17 100644
--- a/react-frontend/src/components/InvestorInfo.js
+++ b/react-frontend/src/components/InvestorInfo.js
@@ -2,7 +2,9 @@
import { useEffect, useState } from "react";
// CSS import
-import '../css/UserCheckin.css';
+import "../css/UserCheckin.css";
+import "../css/InvesterInfo.css";
+import uuid from "react-uuid";
/**
* Componenet for checkins. Has a toggle to show more info.
@@ -10,57 +12,154 @@ import '../css/UserCheckin.css';
* @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
*/
function InvestorInfo(props) {
- const [info, setInfo] = useState({});
+ const [info, setInfo] = useState({
+ percentGain: 0,
+ moneyIn: 0,
+ moneyOut: 0,
+ percentSP500: 0,
+ holdings: [],
+ });
- const toEpochMilli = date => Date.parse(date);
- const getInfo = () => {
- console.log({
- person: props.name,
- start: toEpochMilli(props.dates.start),
- end: toEpochMilli(props.dates.end)
- });
+ const [showStocks, setShowStocks] = useState(false);
+ const [showFollowers, setShowFollowers] = useState(false);
- if (props.name === "") {
+ const getInfo = () => {
+ if (props.selectedId === -1) {
return;
}
-
+
+ const toEpochMilli = (date) => Date.parse(date);
+
+ console.log({
+ selectedId: props.selectedId,
+ startTime: toEpochMilli(props.dates.start),
+ endTime: toEpochMilli(props.dates.end),
+ });
+
fetch("http://localhost:4567/profit", {
- method: "POST",
- body: JSON.stringify({
- person: props.name,
- start: toEpochMilli(props.dates.start),
- end: toEpochMilli(props.dates.end)
- }),
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin"
+ method: "POST",
+ body: JSON.stringify({
+ selectedId: props.selectedId,
+ startTime: toEpochMilli(props.dates.start),
+ endTime: toEpochMilli(props.dates.end),
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin",
})
- .then(res => {
- console.log(res);
- res.json();
- })
- .then(data => {
- console.log(data);
- setInfo(data);
- })
- .catch(err => console.log(err));
- }
- /*
+ .then((res) => res.json())
+ .then((data) => {
+ console.log(data);
+ setInfo(data);
+ props.setShowSelectedInfo(true);
+ })
+ .catch((err) => console.log(err));
+ };
+
+ const stockTable = () => {
+ return (
+ <>
+ <li class="Stock-row">
+ <div className="tableHeader">
+ <div className="symbol-row">Symbol</div>
+ <div className="gain-row">Realized gain</div>
+ <div className="gain-row">Unrealized gain</div>
+ </div>
+ </li>
+ {info.holdings.map((holding) => (
+ <li class="Stock-row">
+ <div className="symbol-row">{holding.ticker}</div>
+ <div className="gain-row">{holding.realizedGain.toFixed(3)}</div>
+ <div className="gain-row">{holding.unrealizedGain.toFixed(3)}</div>
+ </li>
+ ))}
+ </>
+ );
+ };
- const coords = userCoords.map((coord, index) =>
- <li key={index}>
- <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span>
- </li>
- );*/
+ const followerList = () =>
+ props.followers.map((follower) => (
+ <li key={uuid()} class="Clickable-name" onClick={() => props.setSelectedId(follower.id)}>
+ {follower.name}
+ </li>
+ ));
- useEffect(() => getInfo(), [props.name, props.isSelected, props.personId])
+ // Hook that updates when selected has changed
+ useEffect(() => getInfo(), [props.selectedId]);
return (
- <div className="Chosen-user" hidden={props.isSelected}>
- hi
+ <div className="Chosen-user" hidden={!props.showSelectedInfo}>
+ <h3>
+ <span onClick={() => props.setShowSelectedInfo(false)}>
+ <img className="Img-btn" src="assets/round_arrow_back_white_18dp.png" alt="image" />
+ </span>
+ <span>CIK: {props.selectedId}</span>
+ </h3>
+ <h2 id="investerName">
+ <a href={"https://sec.report/CIK/" + props.selectedId + "/Insider-Trades"} target="_blank">
+ {props.name}
+ <img src="link-icon.svg" alt="image" />
+ </a>
+ </h2>
+ <div id="top-bar">
+ <div id="gain-number">
+ <p className="bigNumber">{(info.moneyOut - info.moneyIn).toFixed(3)}$</p> gain
+ </div>
+
+ <div>
+ <p className="bigNumber">{info.percentGain.toFixed(3)}%</p>
+ compared to {info.percentSP500.toFixed(3)}% on SP500
+ </div>
+ </div>
+
+ <div>
+ <div className="Checkin">
+ <div className="Img-flex">
+ <span className="tableHeader">View returns</span>
+ <img
+ className="Img-btn"
+ hidden={showStocks}
+ onClick={() => setShowStocks((toggle) => !toggle)}
+ src="assets/round_expand_more_white_18dp.png"
+ alt="image"
+ />
+ <img
+ className="Img-btn"
+ hidden={!showStocks}
+ onClick={() => setShowStocks((toggle) => !toggle)}
+ src="assets/round_expand_less_white_18dp.png"
+ alt="image"
+ />
+ </div>
+ <ul hidden={!showStocks} class="Stock-table">
+ {stockTable()}
+ </ul>
+ </div>
+
+ <div className="Checkin" hidden={!followerList}>
+ <div className="Img-flex">
+ <span className="tableHeader">View followers</span>
+ <img
+ className="Img-btn"
+ hidden={showFollowers}
+ onClick={() => setShowFollowers((toggle) => !toggle)}
+ src="assets/round_expand_more_white_18dp.png"
+ alt="image"
+ />
+ <img
+ className="Img-btn"
+ hidden={!showFollowers}
+ onClick={() => setShowFollowers((toggle) => !toggle)}
+ src="assets/round_expand_less_white_18dp.png"
+ alt="image"
+ />
+ </div>
+ <ul hidden={!showFollowers}>{followerList()}</ul>
+ </div>
+ </div>
</div>
);
}
-export default InvestorInfo; \ No newline at end of file
+export default InvestorInfo;
diff --git a/react-frontend/src/components/TimeSelector.js b/react-frontend/src/components/TimeSelector.js
index 652a9ec..997494d 100644
--- a/react-frontend/src/components/TimeSelector.js
+++ b/react-frontend/src/components/TimeSelector.js
@@ -34,9 +34,8 @@ function TimeSelector(props) {
<DateSelector side={"left"} name={"Start Date"} className="Coord-select-left" clickedFunc={setCurrent}
changedFunc={setStartDate} disabled={current==='start' || props.isChanging} value={toValue(startDate)}></DateSelector>
<div>
- <h2>Adjust Timeframe</h2>
<button className="Btn Route-btn" onClick={() => changeTimeframe()}
- disabled={current!=="" || props.isChanging}>Change Timeframe</button>
+ disabled={current!=="" || props.isChanging}>Adjust Timeframe</button>
</div>
<DateSelector side={"right"} name={"End Date"} className="Coord-select-right" clickedFunc={setCurrent}
changedFunc={setEndDate} disabled={current==='end' || props.isChanging} value={toValue(endDate)}></DateSelector>
diff --git a/react-frontend/src/components/Visualization.js b/react-frontend/src/components/Visualization.js
index 33f96c9..01093dc 100644
--- a/react-frontend/src/components/Visualization.js
+++ b/react-frontend/src/components/Visualization.js
@@ -1,6 +1,5 @@
// JS module imports
-import { useEffect, useRef, useState } from "react";
-import uuid from 'react-uuid';
+import { useMemo, useState } from "react";
import Graph from 'vis-react';
// CSS imports
@@ -26,19 +25,26 @@ function Visualization(props) {
hideEdgeInfo = val;
}*/
+ const [key, setKey] = useState(0);
+
+ const [graphState, setGraphState] = useState({
+ nodes: [],
+ edges: []
+ });
+
const options = {
+ autoResize: true,
edges: {
color: "#ffffff"
}
};
+
const events = {
- select: () => event => props.setSelected(event.nodes[0])
- };
+ selectNode: event => {
+ props.setSelectedId(event.nodes[0])
+ }
+ }
- const [graphState, setGraphState] = useState({
- nodes: [],
- edges: []
- });
/*const getEdgeInfo = (fromID, toID) => {
fetch("http://localhost:4567/edge-data", {
@@ -64,6 +70,7 @@ function Visualization(props) {
let nodes = [];
const maxScore = props.data[0].suspicionScore;
const interval = maxScore / 4;
+
props.data.forEach(hub => {
if (hub.followers) {
let colorVal = '#f6f7d4';
@@ -102,6 +109,7 @@ function Visualization(props) {
});
return nodes;
}
+
const getEdges = () => {
let edges = []
props.data.forEach(hub => {
@@ -129,13 +137,17 @@ function Visualization(props) {
hidden={hideEdgeInfo}>
</EdgeInfo>*/
- // Hooks to update graph state
- useEffect(() => setGraphState({nodes: getNodes(), edges: getEdges()}), [JSON.stringify(props.data)]);
+ // Hooks to update graph state when data changes
+ useMemo(() => {
+ setKey(key => key + 1);
+ setGraphState({nodes: getNodes(), edges: getEdges()})
+ }, [JSON.stringify(props.data)]);
+
return (
<div className="Map-canvas">
<Graph
- key={uuid()}
+ key={key}
graph={graphState}
options={options}
events={events}>
diff --git a/react-frontend/src/components/WatchDogs.js b/react-frontend/src/components/WatchDogs.js
index d631ea9..dfe47c8 100644
--- a/react-frontend/src/components/WatchDogs.js
+++ b/react-frontend/src/components/WatchDogs.js
@@ -2,7 +2,7 @@
import React, {useEffect, useState} from 'react';
import TimeSelector from './TimeSelector.js';
import Visualization from './Visualization.js';
-import HubList from './HubList.js';
+import HubWidget from './HubWidget.js';
import Loading from './Loading.js';
import Modal from './Modal.js';
import logo from './images/logo.png';
@@ -26,8 +26,8 @@ function WatchDogs() {
});
// State for visualization data
const [data, setData] = useState([]);
- // State for selected person
- const [selected, setSelected] = useState(-1);
+ // State for selectedId
+ const [selectedId, setSelectedId] = useState(-1);
const toEpochMilli = date => Date.parse(date);
const getGraphData = () => {
@@ -49,9 +49,9 @@ function WatchDogs() {
.then(res => res.json())
.then(data => {
//TODO: optimize this
- const sliced = data.holders.slice(0, 500);
- console.log(sliced);
- setData(sliced);
+ //const sliced = data.holders.slice(0, 500);
+ //console.log(sliced);
+ setData(data.holders);
setHasLoaded(true);
})
.catch(err => console.log(err));
@@ -77,9 +77,9 @@ function WatchDogs() {
<div className="Canvas-filler Canvas-filler-1"></div>
<div className="Canvas-filler Canvas-filler-2"></div>
<div className="Canvas-filler Canvas-filler-3"></div>
- <HubList setHasLoaded={setHasLoaded} data={data} setSelected={setSelected} selected={selected} dates={dates}></HubList>
+ <HubWidget setHasLoaded={setHasLoaded} data={data} dates={dates} selectedId={selectedId} setSelectedId={setSelectedId}></HubWidget>
<TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>
- <Visualization hasLoaded={hasLoaded} data={data} setSelected={setSelected}></Visualization>
+ <Visualization hasLoaded={hasLoaded} data={data.slice(0, 600)} setSelectedId={setSelectedId}></Visualization>
</div>
}
</>
diff --git a/react-frontend/src/css/App.css b/react-frontend/src/css/App.css
index e39eb3e..1b8de5f 100644
--- a/react-frontend/src/css/App.css
+++ b/react-frontend/src/css/App.css
@@ -7,6 +7,7 @@
grid-template-columns: max-content auto max-content max-content;
background-color: #121212;
+
}
.App-logo {
diff --git a/react-frontend/src/css/CoordSelector.css b/react-frontend/src/css/CoordSelector.css
index 881be08..e1fde99 100644
--- a/react-frontend/src/css/CoordSelector.css
+++ b/react-frontend/src/css/CoordSelector.css
@@ -49,4 +49,8 @@
width: 90%;
}
+.Flex-coord {
+ margin: auto;
+}
+
diff --git a/react-frontend/src/css/InvesterInfo.css b/react-frontend/src/css/InvesterInfo.css
new file mode 100644
index 0000000..3501b70
--- /dev/null
+++ b/react-frontend/src/css/InvesterInfo.css
@@ -0,0 +1,61 @@
+#top-bar {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ margin-right: 10px;
+ margin-left: 10px;
+ border-bottom: solid 1px white;
+ margin-top: 0px;
+ padding-bottom: 20px;
+}
+
+/* div {
+ border: solid white;
+} */
+
+/* p {
+ border: solid white;
+} */
+
+.bigNumber {
+ font-size: 25pt;
+ margin: 0;
+}
+
+.Chosen-user {
+ background-color: #333333;
+ color: lightgreen;
+ width: 25vw;
+ border-radius: 10px;
+}
+
+.Stock-row {
+ display: flex;
+ justify-content: space-evenly;
+ color: white;
+ margin-bottom: 5px;
+}
+
+.tableHeader {
+ display: flex;
+ flex-direction: row;
+ justify-content: left;
+ margin-right: 10px;
+ margin-left: 10px;
+ font-size: 120%;
+ padding: 5px;
+ color: lightgreen;
+}
+
+.symbol-row {
+ margin-right: 20px;
+}
+
+.gain-row {
+ margin-right: 20px;
+}
+
+a {
+ color: lightgreen;
+ text-decoration: none;
+}
diff --git a/react-frontend/src/css/UserCheckin.css b/react-frontend/src/css/UserCheckin.css
index 141cc01..389bca6 100644
--- a/react-frontend/src/css/UserCheckin.css
+++ b/react-frontend/src/css/UserCheckin.css
@@ -20,7 +20,7 @@ ul {
z-index: 10;
background-color: #333333;
border-radius: 20px;
- margin: 5px;
+ margin: 5px 5px 5px 0;
}
.Coord-ex {
@@ -30,22 +30,28 @@ ul {
text-align: center;
}
+.Chosen-user > h3, .Checkins > h3 {
+ display: flex;
+ justify-content: space-between;
+ height: 5vh;
+ padding: 0 30px;
+}
+
.Chosen-user > h2, .Checkins > h2 {
display: flex;
- justify-content: space-evenly;
+ justify-content: center;
height: 5vh;
- padding: 0 10px;
+ padding: 0 30px;
}
.Checkin-list {
padding: 0 20px;
- height: 86vh;
+ height: 85vh;
overflow-y: scroll;
- cursor: default;
}
.User-checkin-list {
- height: 80vh;
+ height: 75vh;
overflow-y: scroll;
list-style-position: inside;
@@ -79,7 +85,7 @@ ul {
.Img-btn {
background-color: #424242;
border-radius: 50%;
- margin-right: 10px;
+ margin-left: auto;
}
.Img-btn:hover {
@@ -91,4 +97,18 @@ ul {
cursor: pointer;
text-decoration: underline;
color: lightgreen;
+}
+
+/* CSS borrowed from W3 Schools */
+input[type=text] {
+ width: 80%;
+ padding: 12px 20px;
+ box-sizing: border-box;
+ font-size: 80%;
+ background-color: lightgoldenrodyellow;
+ border-radius: 2;
+}
+
+.Title {
+ margin-top: 20px;
} \ No newline at end of file
diff --git a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
index 53c8cdc..2a9af65 100644
--- a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
+++ b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
@@ -125,16 +125,16 @@ public class DatabaseQuerier {
return trades;
}
- public List<Trade> getAllTradesByHolder(String person, Date startDate, Date endDate) {
+ public List<Trade> getAllTradesByHolder(Integer holder_id, Date startDate, Date endDate) {
LinkedList<Trade> trades = new LinkedList<>();
try {
PreparedStatement prep;
prep =
- conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? "
+ conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_id = ?"
+ " AND trade_timestamp BETWEEN ? AND ?"
+ "order by trade_timestamp asc;");
- prep.setString(1, person);
+ prep.setInt(1, holder_id);
prep.setDate(2, startDate);
prep.setDate(3, endDate);
ResultSet rs = prep.executeQuery();
diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java
index 7b9bf0f..37317c6 100644
--- a/src/main/java/edu/brown/cs/student/term/Main.java
+++ b/src/main/java/edu/brown/cs/student/term/Main.java
@@ -180,24 +180,31 @@ public final class Main {
@Override
public Object handle(Request request, Response response) throws Exception {
JSONObject req = new JSONObject(request.body());
- String person = req.getString("person");
+ System.err.println("LOG: Call to /profit with " + req.toMap());
+ Integer holder_id = req.getInt("selectedId");
Date startPeriod = new Date(req.getLong("startTime"));
Date endPeriod = new Date(req.getLong("endTime"));
ProfitCalculation profit =
- new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod);
- List<StockHolding> holdings = profit.getHoldingsList();
- double gains = profit.calculateGainsSingle();
+ new ProfitCalculation(DatabaseQuerier.getConn(), "", startPeriod, endPeriod);
+ List<StockHolding> holdings = profit.getHoldingsList(holder_id);
+ double gains = profit.calculateGainsSingle(holder_id);
double sp500PercentGain = profit.compareToSP500();
+ double percentGain = 100 * (gains / profit.getMoneyInput());
+ if (profit.getMoneyInput() == 0) {
+ percentGain = 0;
+ }
+
Map<String, Object> res = new HashMap<>();
- res.put("person", person);
+ res.put("holder_id", holder_id);
res.put("moneyIn", profit.getMoneyInput());
res.put("moneyOut", profit.getMoneyInput() + gains);
res.put("holdings", holdings);
- res.put("percentGain", 100 * gains / profit.getMoneyInput());
+ res.put("percentGain", percentGain);
res.put("SP500", (1 + sp500PercentGain) * profit.getMoneyInput());
res.put("percentSP500", 100 * sp500PercentGain);
+ System.err.println("LOG: Returning to GUI " + res);
return GSON.toJson(res);
}
@@ -207,15 +214,14 @@ public final class Main {
@Override
public Object handle(Request request, Response response) throws Exception {
JSONObject req = new JSONObject(request.body());
- String person = req.getString("person");
+ Integer holder_id = req.getInt("selectedId");
Date startPeriod = new Date(req.getLong("startTime"));
Date endPeriod = new Date(req.getLong("endTime"));
DatabaseQuerier db = SetupCommand.getDq();
- List<Trade> trades = db.getAllTradesByHolder(person, startPeriod, endPeriod);
+ List<Trade> trades = db.getAllTradesByHolder(holder_id, startPeriod, endPeriod);
return GSON.toJson(trades);
-
}
}
diff --git a/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java
index d0df8a8..4b19899 100644
--- a/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java
+++ b/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java
@@ -76,6 +76,7 @@ public class ProfitCalculation {
tablesFilled = false;
}
+
/**
* This method fills the maps of sell and buy orders with lists of oldest - new trades.
*/
@@ -93,15 +94,25 @@ public class ProfitCalculation {
return ticker;
}
- private void organizeOrders() {
+ private void organizeOrders(Integer id) {
//get a list of trades for a person to consider
try {
PreparedStatement prep;
- prep =
- conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? "
- + " AND trade_timestamp BETWEEN ? AND ? "
- + "order by trade_timestamp asc;");
- prep.setString(1, this.person);
+ if (id == -1) {
+ //search by name
+ prep =
+ conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? "
+ + " AND trade_timestamp BETWEEN ? AND ? "
+ + "order by trade_timestamp asc;");
+ prep.setString(1, this.person);
+ } else {
+ //search by id
+ prep =
+ conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_id = ? "
+ + " AND trade_timestamp BETWEEN ? AND ? "
+ + "order by trade_timestamp asc;");
+ prep.setInt(1, id);
+ }
prep.setDate(2, startTime);
prep.setDate(3, endTime);
ResultSet rs = prep.executeQuery();
@@ -283,32 +294,37 @@ public class ProfitCalculation {
}
- public double calculateGainsSingle() {
+ public double calculateGainsSingle(Integer id) {
if (!tablesFilled) {
- organizeOrders();
+ organizeOrders(id);
getRealizedGains();
+ getUnrealizedGains();
tablesFilled = true;
}
- double realizedGains = 0;
+ double gains = 0;
for (double value : realizedGainsMap.values()) {
- realizedGains += value;
+ gains += value;
+ }
+
+ for (double value: unrealizedGainsMap.values()) {
+ gains += value;
}
- return realizedGains;
+ return gains;
}
- public List<StockHolding> getHoldingsList() {
+ public List<StockHolding> getHoldingsList(Integer id) {
if (conn == null) {
System.out.println("ERROR: No database connection");
return new LinkedList<>();
}
if (!tablesFilled) {
- organizeOrders();
+ organizeOrders(id);
getRealizedGains();
getUnrealizedGains();
tablesFilled = true;
diff --git a/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java b/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java
index 5edb5f7..dd57ce1 100644
--- a/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java
+++ b/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java
@@ -1,5 +1,7 @@
package edu.brown.cs.student.term.profit;
+import java.util.Objects;
+
/**
* class to map holding info for JSON.
*/
@@ -46,4 +48,37 @@ public class StockHolding {
public int getShares() {
return shares;
}
+
+ public String getTicker() {
+ return ticker;
+ }
+
+ @Override
+ public String toString() {
+ return "StockHolding{" +
+ "ticker='" + ticker + '\'' +
+ ", realizedGain=" + realizedGain +
+ ", unrealizedGain=" + unrealizedGain +
+ ", shares=" + shares +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ StockHolding that = (StockHolding) o;
+ return shares == that.shares && Objects.equals(ticker, that.ticker) &&
+ Objects.equals(realizedGain, that.realizedGain) &&
+ Objects.equals(unrealizedGain, that.unrealizedGain);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ticker, realizedGain, unrealizedGain, shares);
+ }
} \ No newline at end of file
diff --git a/src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java b/src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java
index 00ba3ad..6f8f610 100644
--- a/src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java
+++ b/src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java
@@ -125,7 +125,7 @@ public class LoadCommand implements Command {
"text=form-type%3D4+and+(filing-date%3D" + filingDate + ")" +
"&start=" + (100*counter++ + shift) +
"&count=" + 100 +
- "&first=2020" +
+ "&first=2021" +
"&last=2021" +
"&output=atom"
:
diff --git a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
index 0d22109..fcb0de4 100644
--- a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
+++ b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
@@ -56,7 +56,7 @@ public class ProfitCalculationTest {
new ProfitCalculation(DatabaseQuerier.getConn(), "Don", new Date(1518010558000l),
new Date(1618698807000l));
//price of GME at end time is 154.69
- List<StockHolding> trade = profitCalculation.getHoldingsList();
+ List<StockHolding> trade = profitCalculation.getHoldingsList(-1);
//buy with no sell
assertEquals(trade.get(0).getUnrealizedGain(), 3842.25, .25);
assertEquals(trade.get(0).getRealizedGain(), 0, .01);
@@ -65,9 +65,9 @@ public class ProfitCalculationTest {
profitCalculation =
new ProfitCalculation(DatabaseQuerier.getConn(), "SELL", new Date(1518010558000l),
new Date(1618698807000l));
- trade = profitCalculation.getHoldingsList();
+ trade = profitCalculation.getHoldingsList(-1);
assertTrue(trade.isEmpty());
- assertEquals(profitCalculation.calculateGainsSingle(), 0, 0.001);
+ assertEquals(profitCalculation.calculateGainsSingle(-1), 0, 0.001);
tearDown();
}
@@ -90,7 +90,7 @@ public class ProfitCalculationTest {
new Date(1518010558000l),
new Date(1715629591000l));
- assertEquals(profitCalculation.getHoldingsList().get(0).getRealizedGain(), 3750, 0.01);
+ assertEquals(profitCalculation.getHoldingsList(-1).get(0).getRealizedGain(), 3750, 0.01);
assertEquals(profitCalculation.getMoneyInput(), 3750, .01);
//left over holdings
@@ -99,7 +99,7 @@ public class ProfitCalculationTest {
new Date(1518010558000l),
new Date(1715629591000l));
- assertEquals(profitCalculation.getHoldingsList().get(0).getShares(), 25, .01);
+ assertEquals(profitCalculation.getHoldingsList(-1).get(0).getShares(), 25, .01);
tearDown();
}
@@ -124,21 +124,21 @@ public class ProfitCalculationTest {
new Date(1618698807000l));
assertEquals(profitCalculation.getProfitMap(), new HashMap<>());
- assertEquals(profitCalculation.getHoldingsList(), new LinkedList<>());
+ assertEquals(profitCalculation.getHoldingsList(-1), new LinkedList<>());
setUp();
//invalid person
profitCalculation =
new ProfitCalculation(DatabaseQuerier.getConn(), "1234", new Date(1518010558000l),
new Date(1618698807000l));
- assertEquals(profitCalculation.getHoldingsList(), new LinkedList<>());
+ assertEquals(profitCalculation.getHoldingsList(-1), new LinkedList<>());
//invalid stock ticker
profitCalculation =
new ProfitCalculation(DatabaseQuerier.getConn(), "invalidTicker", new Date(1518010558000l),
new Date(1618698807000l));
- assertTrue(profitCalculation.getHoldingsList().isEmpty());
+ assertTrue(profitCalculation.getHoldingsList(-1).isEmpty());
}
} \ No newline at end of file