aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author9308233900 <reagan_hunt@brown.edu>2021-04-20 10:24:34 -0700
committer9308233900 <reagan_hunt@brown.edu>2021-04-20 10:24:34 -0700
commit2e3243bb52b23571df529697d841f883846a8954 (patch)
tree315eda2621ddc65d96472e2fc29548356d25425b
parent564295d2ac6b40e349a1cbc3e3bd329989e9ec82 (diff)
parent4411ae1564d716e5aa063e4c47302ffc907a078a (diff)
Merge branch 'master' of https://github.com/cs0320-2021/term-project-cohwille-jmccaul3-mfoiani-rhunt2master
-rw-r--r--data/mock_tradeClarks.sqlite3bin53248 -> 0 bytes
-rw-r--r--data/profit_testing.sqlite3bin0 -> 8192 bytes
-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/EdgeInfo.js59
-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.js81
-rw-r--r--react-frontend/src/components/WatchDogs.js21
-rw-r--r--react-frontend/src/components/images/person.svg1
-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/EdgeInfo.css0
-rw-r--r--react-frontend/src/css/InvesterInfo.css62
-rw-r--r--react-frontend/src/css/UserCheckin.css39
-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.java40
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java32
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java32
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java366
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/StockHolding.java61
-rw-r--r--src/main/java/edu/brown/cs/student/term/repl/commands/LoadCommand.java2
-rw-r--r--src/main/java/edu/brown/cs/student/term/trade/Trade.java22
-rw-r--r--src/test/java/edu/brown/cs/student/ProfitCalculationTest.java96
-rw-r--r--tatus52
37 files changed, 1115 insertions, 260 deletions
diff --git a/data/mock_tradeClarks.sqlite3 b/data/mock_tradeClarks.sqlite3
deleted file mode 100644
index 980c539..0000000
--- a/data/mock_tradeClarks.sqlite3
+++ /dev/null
Binary files differ
diff --git a/data/profit_testing.sqlite3 b/data/profit_testing.sqlite3
new file mode 100644
index 0000000..33dd2b8
--- /dev/null
+++ b/data/profit_testing.sqlite3
Binary files differ
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 2cef235..1461c5a 100644
--- a/react-frontend/src/App.js
+++ b/react-frontend/src/App.js
@@ -12,18 +12,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/EdgeInfo.js b/react-frontend/src/components/EdgeInfo.js
new file mode 100644
index 0000000..566c70a
--- /dev/null
+++ b/react-frontend/src/components/EdgeInfo.js
@@ -0,0 +1,59 @@
+
+import '../css/UserCheckin.css';
+import { useEffect, useState } from "react";
+
+/**
+ * Componenet for checkins. Has a toggle to show more info.
+ * @param {Object} props The props of the component.
+ * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
+ */
+function EdgeInfo(props) {
+ /* // State - toggled
+ const [stockList, setStockList] = useState([]);
+
+ const stockInfo = stockList.map((stock) =>
+ <li>{stock}</li>
+ );
+
+
+ const getEdgeInfo = (fromID, toID) => {
+ fetch("http://localhost:4567/edge-data", {
+ method: "POST",
+ body: JSON.stringify({
+ followerID: fromID,
+ leaderID: toID,
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin"
+ })
+ .then(res => res.json())
+ .then(data => {
+ console.log(data);
+ setStockList(data);
+ })
+ .catch(err => console.log(err));
+ }
+
+ useEffect(() => getEdgeInfo(), [props.selectedFollowerID, props.selectedLeaderID]);
+ */
+
+
+ return (
+ <div>
+
+ </div>
+ /*<div className='Chosen-user' hidden={!props.showEdgeInfo}>
+ <h3>
+ <span onClick={() => props.setShowEdgeInfo(false)}>
+ <img className="Img-btn" src="assets/round_arrow_back_white_18dp.png" alt="image" />
+ </span>
+ </h3>
+ <div className = 'edge-info'>
+ {stockInfo}
+ </div>
+ </div>*/);
+}
+
+export default EdgeInfo; \ No newline at end of file
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 0df3020..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..f0434d7
--- /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="search" 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..9e18299 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> gained
+ </div>
+
+ <div>
+ <p className="bigNumber">{info.percentGain.toFixed(3)}%</p>
+ compared to {info.percentSP500.toFixed(3)}% on SP500
+ </div>
+ </div>
+
+ <div>
+ <div className="Checkin" hidden={info.holdings.length === 0}>
+ <div className="Img-flex">
+ <span className="tableHeader">View holdings</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 1975e86..d65d106 100644
--- a/react-frontend/src/components/Visualization.js
+++ b/react-frontend/src/components/Visualization.js
@@ -1,63 +1,114 @@
// 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
import '../css/Canvas.css';
+import EdgeInfo from "./EdgeInfo";
+
/**
- * This function renders and mantains thhe canvas.
+ * This function renders and mantains the canvas.
* @param {Object} props The props for the canvas.
* @returns {import("react").HtmlHTMLAttributes} The canvas to be retured.
*/
function Visualization(props) {
+
+
+
+ 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 getNodes = () => {
let nodes = [];
+ const maxScore = props.data[0].suspicionScore;
+ const interval = maxScore / 4;
+
props.data.forEach(hub => {
if (hub.followers) {
+ let colorVal = '#f6f7d4';
+ const score = hub.suspicionScore;
+
+ if(score > (maxScore - interval)){
+ colorVal = '#d92027'
+ }
+ if(score <= (maxScore - interval) && score > (maxScore - interval*2)){
+ colorVal = '#f37121'
+ }
+ if(score <= (maxScore - interval*2) && score > (maxScore - interval*3)){
+ colorVal = '#fdca40'
+ }
nodes.push({
id: hub.id,
+ autoResize: true,
label: hub.name,
- size: hub.suspicionScore
+ labelHighlightBold: true,
+ shape: "dot",
+ value: hub.suspicionScore*1000,
+ color: {
+ background: colorVal,
+ border: '#2b2e4a',
+ highlight:{
+ background: '#29bb89',
+ border: '#fdca40'
+ }
+ },
+ font: {
+ color: '#9fd8df',
+ size: 20,
+ }
});
}
});
return nodes;
}
+
const getEdges = () => {
let edges = []
props.data.forEach(hub => {
hub.followers.forEach(follower => {
edges.push({
- from: hub.id,
- to: follower.id
+ from: follower.id,
+ to: hub.id,
+ dashes: false,
+ color:{
+ opacity: 0.7,
+ highlight:'#fdca40',
+ },
});
});
});
return edges;
}
- // 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..f7a9e74 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,14 @@ 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);
+ console.log(data);
+ if(data.holders.length === 0) {
+ alert("There is no data between those timeframes :(");
+ return;
+ }
+ setData(data.holders);
setHasLoaded(true);
})
.catch(err => console.log(err));
@@ -77,9 +82,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/components/images/person.svg b/react-frontend/src/components/images/person.svg
new file mode 100644
index 0000000..6a93d8f
--- /dev/null
+++ b/react-frontend/src/components/images/person.svg
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Person</title><path d='M332.64 64.58C313.18 43.57 286 32 256 32c-30.16 0-57.43 11.5-76.8 32.38-19.58 21.11-29.12 49.8-26.88 80.78C156.76 206.28 203.27 256 256 256s99.16-49.71 103.67-110.82c2.27-30.7-7.33-59.33-27.03-80.6zM432 480H80a31 31 0 01-24.2-11.13c-6.5-7.77-9.12-18.38-7.18-29.11C57.06 392.94 83.4 353.61 124.8 326c36.78-24.51 83.37-38 131.2-38s94.42 13.5 131.2 38c41.4 27.6 67.74 66.93 76.18 113.75 1.94 10.73-.68 21.34-7.18 29.11A31 31 0 01432 480z'/></svg> \ No newline at end of file
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/EdgeInfo.css b/react-frontend/src/css/EdgeInfo.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/react-frontend/src/css/EdgeInfo.css
diff --git a/react-frontend/src/css/InvesterInfo.css b/react-frontend/src/css/InvesterInfo.css
new file mode 100644
index 0000000..c1bb137
--- /dev/null
+++ b/react-frontend/src/css/InvesterInfo.css
@@ -0,0 +1,62 @@
+#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: 16pt;
+ margin: 0;
+}
+
+.Chosen-user {
+ background-color: #333333;
+ color: lightgreen;
+ width: 25vw;
+ border-radius: 10px;
+ overflow: scroll;
+}
+
+.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..e60b348 100644
--- a/react-frontend/src/css/UserCheckin.css
+++ b/react-frontend/src/css/UserCheckin.css
@@ -10,6 +10,7 @@
cursor: default;
/* Transparent background */
background: rgba(0, 0, 0, 0);
+ overflow: scroll;
}
ul {
@@ -20,7 +21,7 @@ ul {
z-index: 10;
background-color: #333333;
border-radius: 20px;
- margin: 5px;
+ margin: 5px 5px 5px 0;
}
.Coord-ex {
@@ -30,23 +31,29 @@ 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;
- overflow-y: scroll;
- cursor: default;
+ height: 85vh;
+ overflow: scroll;
}
.User-checkin-list {
- height: 80vh;
- overflow-y: scroll;
+ height: 75vh;
+ overflow: scroll;
list-style-position: inside;
padding: 0 20px;
@@ -79,7 +86,7 @@ ul {
.Img-btn {
background-color: #424242;
border-radius: 50%;
- margin-right: 10px;
+ margin-left: auto;
}
.Img-btn:hover {
@@ -91,4 +98,18 @@ ul {
cursor: pointer;
text-decoration: underline;
color: lightgreen;
+}
+
+/* CSS borrowed from W3 Schools */
+input[type=search] {
+ 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 ee3bec1..37317c6 100644
--- a/src/main/java/edu/brown/cs/student/term/Main.java
+++ b/src/main/java/edu/brown/cs/student/term/Main.java
@@ -2,6 +2,7 @@ package edu.brown.cs.student.term;
import com.google.common.collect.ImmutableMap;
import edu.brown.cs.student.term.hub.Holder;
+import edu.brown.cs.student.term.hub.LinkMapper;
import edu.brown.cs.student.term.profit.ProfitCalculation;
import edu.brown.cs.student.term.profit.StockHolding;
import edu.brown.cs.student.term.hub.SuspicionRanker;
@@ -78,12 +79,12 @@ public final class Main {
setConnection.run(new String[] {"data/trades.sqlite3"});
}
- if (!options.has("debug")) {
+ /*if (!options.has("debug")) {
System.setErr(new PrintStream(new OutputStream() {
public void write(int b) {
}
}));
- }
+ }*/
HashMap<String, Command> commandHashMap = new HashMap<>();
@@ -137,6 +138,7 @@ public final class Main {
Spark.post("/data", new SuspicionRankHandler());
Spark.post("/profit", new ProfitQueryHandler());
Spark.post("/trade-lookup", new TradeQueryHandler());
+ Spark.post("/edge-data", new EdgeDataQueryHandler());
}
/**
@@ -178,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.calculateGains();
+ 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);
}
@@ -205,15 +214,26 @@ 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);
+ }
+ }
+ private static class EdgeDataQueryHandler implements Route {
+ @Override
+ public Object handle(Request request, Response response) throws Exception {
+ JSONObject req = new JSONObject(request.body());
+ int leaderID = req.getInt("leaderID");
+ int followerID = req.getInt("followerID");
+ List<String> commonStocks = LinkMapper.getCommonTrades(leaderID, followerID);
+ System.out.println(commonStocks);
+ return GSON.toJson(commonStocks);
}
}
diff --git a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java
index 31e2625..e749aff 100644
--- a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java
+++ b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java
@@ -11,8 +11,9 @@ public class LinkMapper {
//TODO: Review what we actually need in here
//not strictly necessary but may be nice to maintain
- private List<List<Trade>> allTrades = new ArrayList<>();
+ //private List<List<Trade>> allTrades = new ArrayList<>();
private Map<Holder, Set<Holder>> followerToLeaders = new HashMap<>();
+ private static Map<Integer, Set<Trade>> holderIDToTrades = new HashMap<>();
private DatabaseQuerier databaseQuerier;
public LinkMapper(DatabaseQuerier db){
@@ -55,6 +56,25 @@ public class LinkMapper {
return followerToLeaders;
}
+ public static List<String> getCommonTrades(int leaderID, int followerID){
+ Set<Trade> leaderTrades = new HashSet<>(holderIDToTrades.get(leaderID));
+ Set<Trade> followerTrades = new HashSet<>(holderIDToTrades.get(followerID));
+
+ leaderTrades.retainAll(followerTrades);
+ //TODO: Could retain WAY more info in here!
+ List<String> commonTrades = new ArrayList<>();
+ for(Trade leaderTrade: leaderTrades){
+ String buyType = "";
+ if(leaderTrade.isBuy()){
+ buyType = "Buy";
+ } else{
+ buyType = "Sell";
+ }
+ commonTrades.add(buyType + ": " + leaderTrade.getStock());
+ }
+ return commonTrades;
+ }
+
/**
* Converts a single trade list into entries in the follower to leader map
* @param tradeList - a list of trades for a single stock (either buy or sell)
@@ -64,7 +84,15 @@ public class LinkMapper {
//gets in order list of people
for (Trade trade : tradeList) {
- holderList.add(trade.getHolder());
+ Holder currentHolder = trade.getHolder();
+ holderList.add(currentHolder);
+ if(!holderIDToTrades.containsKey(currentHolder.getId())){
+ Set<Trade> tradeSet = new HashSet<>();
+ tradeSet.add(trade);
+ holderIDToTrades.put(currentHolder.getId(), tradeSet);
+ } else {
+ holderIDToTrades.get(currentHolder.getId()).add(trade);
+ }
}
//Set<Holder> followers = new HashSet<>(holderList);
diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java
index 3283f5c..d37910e 100644
--- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java
+++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java
@@ -16,8 +16,9 @@ public class SuspicionRanker {
}
private <K, V extends Comparable<V>> V getMaxOfMap(Map<K, V> map) {
- Map.Entry<K, V> maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue());
- return maxEntry.getValue();
+ //Map.Entry<K, V> maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue());
+ Collection<V> values = map.values();
+ return Collections.max(map.values());
}
private <K, V extends Comparable<V>> V getMinOfMap(Map<K, V> map) {
@@ -52,13 +53,12 @@ public class SuspicionRanker {
HubSearch hub = new HubSearch(lm);
Map<Holder, Double> holderToHubScore = hub.runHubSearch(start, end);
- /*
-
ProfitCalculation pc = new ProfitCalculation(DatabaseQuerier.getConn(), "",
new Date(start.toEpochMilli()),
new Date(end.toEpochMilli()));
Map<Integer, Double> profitMap = pc.getProfitMap();
+ System.out.println(profitMap);
//if the maps are empty, we abort because we have entirely incomplete data
if(profitMap.isEmpty() || holderToHubScore.isEmpty()){
@@ -66,30 +66,34 @@ public class SuspicionRanker {
}
double profitMax = getMaxOfMap(profitMap);
- /*if all of our values are negative, we need to flip sides so that the
- * biggest loser doesn't end up being the most suspicious person*/
- /*
+
+ //if all of our values are negative, we need to flip sides so that the
+ //biggest loser doesn't end up being the most suspicious person*/
if(profitMax <= 0) {
profitMax = Math.abs(getMinOfMap(profitMap));
}
- /*if both the min we found and max we found are 0, then we have
- the special case where all the values are 0, in which case we
- need to avoid dividing by 0*/
- /*
+
+ //if both the min we found and max we found are 0, then we have
+ //the special case where all the values are 0, in which case we
+ //need to avoid dividing by 0
+
if(profitMax == 0){
profitMax = 1;
}
- */
double hubMax = getMaxOfMap(holderToHubScore);
for (Holder guy : holderToHubScore.keySet()) {
- //double normalizedProfitScore = profitMap.get(guy.getId()) / profitMax;
+ double normalizedProfitScore = 0;
+ if (profitMap.containsKey(guy.getId())) {
+ normalizedProfitScore = profitMap.get(guy.getId()) / profitMax;
+ }
double normalizedHubScore = holderToHubScore.get(guy) / hubMax;
- double suspicionScore = normalizedHubScore; //* 0.6 + normalizedProfitScore * 0.4;
+ double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4;
+
guy.setSuspicionScore(suspicionScore);
orderedSuspicion.add(guy);
}
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 15f31cc..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
@@ -19,6 +19,11 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.text.SimpleDateFormat;
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.*;
public class ProfitCalculation {
@@ -71,6 +76,7 @@ public class ProfitCalculation {
tablesFilled = false;
}
+
/**
* This method fills the maps of sell and buy orders with lists of oldest - new trades.
*/
@@ -78,25 +84,35 @@ public class ProfitCalculation {
private String validateTicker(String ticker) {
//this is cleaning some improperly formatted tickers
ticker = ticker.replaceAll("[^a-zA-Z0-9]", "").toUpperCase();
- if(ticker.contains("[0-9]") ||
- ticker.length() > 5 ||
- ticker.length() < 2 ||
- ticker.contains("NONE")) {
+ if (ticker.contains("[0-9]") ||
+ ticker.length() > 5 ||
+ ticker.length() < 2 ||
+ ticker.contains("NONE")) {
return "";
}
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();
@@ -104,7 +120,7 @@ public class ProfitCalculation {
while (rs.next()) {
String ticker = rs.getString("stock_name");
ticker = validateTicker(ticker);
- if(ticker.equals("")){
+ if (ticker.equals("")) {
continue;
}
int shares = rs.getInt("number_of_shares");
@@ -125,13 +141,12 @@ public class ProfitCalculation {
}
} else {
//ignore sell orders for which we do not have buys for
- if (buyHistoryMap.containsKey(ticker)) {
- if (sellHistoryMap.containsKey(ticker)) {
- sellHistoryMap.get(ticker).addLast(order);
- } else {
- sellHistoryMap.put(ticker, oneElement);
- }
+ if (sellHistoryMap.containsKey(ticker)) {
+ sellHistoryMap.get(ticker).addLast(order);
+ } else {
+ sellHistoryMap.put(ticker, oneElement);
}
+
}
}
@@ -150,39 +165,41 @@ public class ProfitCalculation {
LinkedList<OrderTuple> sells = sellHistoryMap.get(ticker);
LinkedList<OrderTuple> buys = buyHistoryMap.get(ticker);
double realizedGain = 0;
+ if (sells != null && buys != null) {
+ //process each sell order (unless all buy orders are "drained"
+ for (OrderTuple sell : sells) {
+ //stop if buys are empty, stop if buy happened after sell
+ if (buys.isEmpty()) {
+ break;
+ }
- //process each sell order (unless all buy orders are "drained"
- for (OrderTuple sell : sells) {
- //stop if buys are empty, stop if buy happened after sell
- if (buys.isEmpty()) {
- break;
- }
-
- int sharesToSell = sell.getShares();
-
- //sell off through list of buys
- while (sharesToSell > 0 && !buys.isEmpty()) {
- //dont sell from buys which didn't exist at the time.
- if (sell.getDate().after(buys.getFirst().getDate())) {
- OrderTuple buyBundle = buys.removeFirst();
- int sharesAtBundlePrice;
- //the buy has more shares than we want to sell
- if (buyBundle.getShares() > sharesToSell) {
- sharesAtBundlePrice = sharesToSell;
- sharesToSell = 0;
- //add back the holdings that were not sold
- buyBundle.setShares(buyBundle.getShares() - sharesAtBundlePrice);
- buys.addFirst(buyBundle);
+ int sharesToSell = sell.getShares();
+
+ //sell off through list of buys
+ while (sharesToSell > 0 && !buys.isEmpty()) {
+ //dont sell from buys which didn't exist at the time.
+ if (sell.getDate().after(buys.getFirst().getDate())
+ || sell.getDate().equals(buys.getFirst().getDate())) {
+ OrderTuple buyBundle = buys.removeFirst();
+ int sharesAtBundlePrice;
+ //the buy has more shares than we want to sell
+ if (buyBundle.getShares() > sharesToSell) {
+ sharesAtBundlePrice = sharesToSell;
+ sharesToSell = 0;
+ //add back the holdings that were not sold
+ buyBundle.setShares(buyBundle.getShares() - sharesAtBundlePrice);
+ buys.addFirst(buyBundle);
+ } else {
+ sharesToSell -= buyBundle.getShares();
+ sharesAtBundlePrice = buyBundle.getShares();
+ }
+ realizedGain += sharesAtBundlePrice * (sell.getCost() - buyBundle.getCost());
} else {
- sharesToSell -= buyBundle.getShares();
- sharesAtBundlePrice = buyBundle.getShares();
+ break;
}
- realizedGain += sharesAtBundlePrice * (sell.getCost() - buyBundle.getCost());
- } else {
- break;
- }
+ }
}
}
@@ -242,12 +259,16 @@ public class ProfitCalculation {
if (currentStockPrices.containsKey(ticker)) {
return currentStockPrices.get(ticker);
} else {
- String PRICE_URL = BASE_URL + "/last/stocks/" + ticker;
- System.out.println("LOG: Making call to " + PRICE_URL + " in " + getClass());
+ SimpleDateFormat localDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+ String url = "https://data.alpaca.markets/v1/bars/"
+ + "day?"
+ + "symbols=" + ticker
+ + "&start=" + localDateFormat.format(startTime)
+ + "&end=" + localDateFormat.format(endTime);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(PRICE_URL)).setHeader("APCA-API-KEY-ID", API_KEY)
+ .uri(URI.create(url)).setHeader("APCA-API-KEY-ID", API_KEY)
.setHeader("APCA-API-SECRET-KEY", SECRET_KEY)
.build();
@@ -255,18 +276,15 @@ public class ProfitCalculation {
try {
response = client.send(request,
HttpResponse.BodyHandlers.ofString());
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ System.out.println("ERROR: error getting price for profit calculation");
}
-
- JSONObject object = new JSONObject(response.body());
+ JSONArray object = new JSONObject(response.body()).getJSONArray(ticker);
try {
- double price = object.getJSONObject("last").getDouble("price");
- currentStockPrices.put(ticker, price);
- return price;
+ double endPrice = object.getJSONObject(object.length() - 1).getDouble("c");
+ currentStockPrices.put(ticker, endPrice);
+ return endPrice;
} catch (JSONException e) {
currentStockPrices.put(ticker, -1.0);
return -1.0;
@@ -276,29 +294,37 @@ public class ProfitCalculation {
}
- public double calculateGains() {
+ public double calculateGainsSingle(Integer id) {
+
if (!tablesFilled) {
- organizeOrders();
+ organizeOrders(id);
getRealizedGains();
getUnrealizedGains();
tablesFilled = true;
}
- double realizedGains = 0;
- double unrealizedGains = 0;
+
+
+ double gains = 0;
for (double value : realizedGainsMap.values()) {
- realizedGains += value;
+ gains += value;
}
- for (double value : unrealizedGainsMap.values()) {
- unrealizedGains += value;
+ for (double value: unrealizedGainsMap.values()) {
+ gains += value;
}
- return unrealizedGains + 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;
@@ -329,11 +355,12 @@ public class ProfitCalculation {
* return percent change in SPY (SP 500) over the time period.
*/
public double compareToSP500() {
+ SimpleDateFormat localDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String url = "https://data.alpaca.markets/v1/bars/"
+ "day?"
+ "symbols=SPY"
- + "&start=" + startTime
- + "&end=" + endTime;
+ + "&start=" + localDateFormat.format(startTime)
+ + "&end=" + localDateFormat.format(endTime);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
@@ -369,29 +396,199 @@ public class ProfitCalculation {
*/
public Map<Integer, Double> getProfitMap() {
Map<Integer, Double> profitMap = new HashMap<>();
+ if (conn == null) {
+ System.out.println("ERROR: no database connection");
+ return profitMap;
+ }
try {
PreparedStatement prep;
+ long START = System.currentTimeMillis();
prep =
- conn.prepareStatement("SELECT * from trades group by holder_name;");
+ conn.prepareStatement(
+ "SELECT * From trades GROUP BY holder_id having max(is_buy) = 1;");
ResultSet rs = prep.executeQuery();
+
+ long QUERY = System.currentTimeMillis();
+ //System.out.println((QUERY - START) + " query time");
+
+ //set of all people who have made both buy and sell orders
+ Set<Integer> people = new HashSet<>();
+
while (rs.next()) {
- int id = rs.getInt("holder_id");
- this.person = rs.getString("holder_name");
- resetClass();
- double gain = this.calculateGains();
- if (moneyInput == 0) {
- profitMap.put(id, 0.0);
- } else {
- profitMap.put(id, gain / moneyInput);
- }
+// int id = rs.getInt("holder_id");
+// this.person = rs.getString("holder_name");
+// resetClass();
+//
+//
+//
+// double gain = this.calculateGains();
+// if (moneyInput == 0) {
+// profitMap.put(id, 0.0);
+// } else {
+// profitMap.put(id, gain / moneyInput);
+// }
+ people.add(rs.getInt("holder_id"));
}
+
+ profitMap = calculateGains(people);
+
+ long LOOP = System.currentTimeMillis();
+ //System.out.println((LOOP - QUERY) + " loop");
+
} catch (SQLException throwables) {
System.out.println("ERROR: SQl error in profit calculation");
}
return profitMap;
}
+ private Map<Integer, Double> calculateGains(Set<Integer> people) {
+ Map<Integer, Double> gainsMap = new HashMap<>();
+
+ //map of stock to list of buy orders, first element in list is oldest
+ Map<Integer, Map<String, LinkedList<OrderTuple>>> sellMap = new HashMap<>();
+ //map of stock to list of buy orders, first element in list is oldest
+ Map<Integer, Map<String, LinkedList<OrderTuple>>> buyMap = new HashMap<>();
+ //money input
+ Map<Integer, Double> moneyInMap = new HashMap<>();
+
+
+ try {
+ PreparedStatement prep;
+ prep =
+ conn.prepareStatement("SELECT * FROM \'trades\'"
+ + " WHERE NOT number_of_shares = 0 AND trade_timestamp BETWEEN ? AND ? "
+ + "order by trade_timestamp asc;");
+ prep.setDate(1, startTime);
+ prep.setDate(2, endTime);
+ ResultSet rs = prep.executeQuery();
+ while (rs.next()) {
+ if (people.contains(rs.getInt("holder_id"))) {
+ String ticker = rs.getString("stock_name");
+ ticker = validateTicker(ticker);
+ if (ticker.equals("")) {
+ continue;
+ }
+ int shares = rs.getInt("number_of_shares");
+ double price = rs.getDouble("share_price");
+ int holder_id = rs.getInt("holder_id");
+ if (!buyMap.containsKey(holder_id)) {
+ buyMap.put(holder_id, new HashMap<>());
+ }
+ if (!sellMap.containsKey(holder_id)) {
+ sellMap.put(holder_id, new HashMap<>());
+ }
+
+
+ OrderTuple order = new OrderTuple(shares, price, rs.getDate("trade_timestamp"));
+
+ //one element list for first time ticker is seen.
+ LinkedList<OrderTuple> oneElement = new LinkedList<OrderTuple>();
+ oneElement.addLast(order);
+
+ //for buy orders, build up buy history
+ if (rs.getInt("is_buy") != 0) {
+
+ if (moneyInMap.containsKey(holder_id)) {
+ moneyInMap.put(holder_id, moneyInMap.get(holder_id) + shares * price);
+ } else {
+ moneyInMap.put(holder_id, shares * price);
+ }
+
+
+ if (buyMap.get(holder_id).containsKey(ticker)) {
+ buyMap.get(holder_id).get(ticker).addLast(order);
+ } else {
+ buyMap.get(holder_id).put(ticker, oneElement);
+ }
+ } else {
+ //ignore sell orders for which we do not have buys for
+ if (sellMap.get(holder_id).containsKey(ticker)) {
+ sellMap.get(holder_id).get(ticker).addLast(order);
+ } else {
+ sellMap.get(holder_id).put(ticker, oneElement);
+ }
+
+ }
+ }
+ }
+ } catch (SQLException e) {
+ System.out.println("ERROR: sql error getting trades");
+ }
+
+
+ //part 2 doing math...
+ for (Integer person : people) {
+ this.buyHistoryMap = buyMap.get(person);
+ this.sellHistoryMap = sellMap.get(person);
+ if (sellHistoryMap == null) {
+ continue;
+ }
+
+ for (String ticker : sellHistoryMap.keySet()) {
+ //use FIFO selling
+ LinkedList<OrderTuple> sells = sellHistoryMap.get(ticker);
+ LinkedList<OrderTuple> buys = buyHistoryMap.get(ticker);
+ double realizedGain = 0;
+ if (sells != null && buys != null) {
+ //process each sell order (unless all buy orders are "drained"
+ for (OrderTuple sell : sells) {
+ //stop if buys are empty, stop if buy happened after sell
+ if (buys.isEmpty()) {
+ break;
+ }
+
+ int sharesToSell = sell.getShares();
+
+ //sell off through list of buys
+ while (sharesToSell > 0 && !buys.isEmpty()) {
+ //dont sell from buys which didn't exist at the time.
+ if (sell.getDate().after(buys.getFirst().getDate())
+ || sell.getDate().equals(buys.getFirst().getDate())) {
+ OrderTuple buyBundle = buys.removeFirst();
+ int sharesAtBundlePrice;
+ //the buy has more shares than we want to sell
+ if (buyBundle.getShares() > sharesToSell) {
+ sharesAtBundlePrice = sharesToSell;
+ sharesToSell = 0;
+ //add back the holdings that were not sold
+ buyBundle.setShares(buyBundle.getShares() - sharesAtBundlePrice);
+ buys.addFirst(buyBundle);
+ } else {
+ sharesToSell -= buyBundle.getShares();
+ sharesAtBundlePrice = buyBundle.getShares();
+ }
+ realizedGain += sharesAtBundlePrice * (sell.getCost() - buyBundle.getCost());
+ } else {
+ break;
+ }
+
+
+ }
+ }
+ }
+
+ if (gainsMap.containsKey(person)) {
+ gainsMap.put(person, gainsMap.get(person) + realizedGain);
+ } else {
+ gainsMap.put(person, realizedGain);
+ }
+
+ }
+
+ //percent gain
+ if (gainsMap.containsKey(person) && moneyInMap.containsKey(person)) {
+ gainsMap.put(person, gainsMap.get(person) / moneyInMap.get(person));
+ } else {
+ gainsMap.put(person, 0.0);
+ }
+
+
+ }
+
+ return gainsMap;
+ }
+
public double getMoneyInput() {
return this.moneyInput;
}
@@ -406,15 +603,4 @@ public class ProfitCalculation {
tablesFilled = false;
}
- public void setConnection(String filename) throws SQLException, ClassNotFoundException {
-
- // Initialize the database connection, turn foreign keys on
- Class.forName("org.sqlite.JDBC");
- String urlToDB = "jdbc:sqlite:" + filename;
- conn = DriverManager.getConnection(urlToDB);
-
- Statement stat = conn.createStatement();
- stat.executeUpdate("PRAGMA foreign_keys=ON;");
- }
-
}
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 f7924f2..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,11 +1,23 @@
package edu.brown.cs.student.term.profit;
+import java.util.Objects;
+
+/**
+ * class to map holding info for JSON.
+ */
public class StockHolding {
private String ticker;
private Double realizedGain;
private Double unrealizedGain;
private int shares;
+ /**
+ * constructor.
+ * @param ticker - stock.
+ * @param realizedGain realized gain.
+ * @param unrealizedGain unrealized gain.
+ * @param shares - number of shares
+ */
public StockHolding(String ticker, Double realizedGain, Double unrealizedGain, int shares) {
this.ticker = ticker;
this.realizedGain = realizedGain;
@@ -13,11 +25,60 @@ public class StockHolding {
this.shares = shares;
}
+ /**
+ * getter method.
+ * @return realized gain.
+ */
public Double getRealizedGain() {
return realizedGain;
}
+ /**
+ * getter method.
+ * @return unrealized gain.
+ */
public Double getUnrealizedGain() {
return unrealizedGain;
}
+
+ /**
+ * getter method for testing.
+ * @return shares.
+ */
+ 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/main/java/edu/brown/cs/student/term/trade/Trade.java b/src/main/java/edu/brown/cs/student/term/trade/Trade.java
index 353de8d..df52a4f 100644
--- a/src/main/java/edu/brown/cs/student/term/trade/Trade.java
+++ b/src/main/java/edu/brown/cs/student/term/trade/Trade.java
@@ -2,6 +2,8 @@ package edu.brown.cs.student.term.trade;
import edu.brown.cs.student.term.hub.Holder;
+import java.util.Objects;
+
public class Trade {
private int id;
@@ -54,6 +56,26 @@ public class Trade {
return price;
}
+ /**
+ * This equals method differs from what may be expected,
+ * it considers trades "equal" if they have the same buy value and stock name
+ * because they are the same type of trade of the same stock in that case
+ * @param o - object to compare to
+ * @return true if equal by the considerations above
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Trade trade = (Trade) o;
+ return isBuy == trade.isBuy && stock.equals(trade.stock);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stock, isBuy);
+ }
+
@Override
public String toString() {
return "Trade{" +
diff --git a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
index 256afed..fcb0de4 100644
--- a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
+++ b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
@@ -12,6 +12,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.sql.Date;
import java.time.Instant;
@@ -35,7 +38,7 @@ public class ProfitCalculationTest {
@Before
public void setUp() {
try {
- db = new DatabaseQuerier("data/testing/test_trades.sqlite3");
+ db = new DatabaseQuerier("data/profit_testing.sqlite3");
} catch (Exception e) {
System.out.println("DBQuerier Test, couldn't connect to db???");
}
@@ -47,16 +50,95 @@ public class ProfitCalculationTest {
}
@Test
- public void testEmptyDB() {
+ public void testBasicTrades() {
setUp();
ProfitCalculation profitCalculation =
- new ProfitCalculation(DatabaseQuerier.getConn(), "CAKEBREAD STEVEN", new Date(1518010558000l),
- new Date(1718010556000l));
- List<StockHolding> trade = profitCalculation.getHoldingsList();
- double gain = trade.get(0).getUnrealizedGain();
- assertEquals(294800.0, gain, .01);
+ 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(-1);
+ //buy with no sell
+ assertEquals(trade.get(0).getUnrealizedGain(), 3842.25, .25);
+ assertEquals(trade.get(0).getRealizedGain(), 0, .01);
+
+ //just sell
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "SELL", new Date(1518010558000l),
+ new Date(1618698807000l));
+ trade = profitCalculation.getHoldingsList(-1);
+ assertTrue(trade.isEmpty());
+ assertEquals(profitCalculation.calculateGainsSingle(-1), 0, 0.001);
+
+ tearDown();
}
+ @Test
+ public void otherBuySellCases() {
+ setUp();
+ //buy and sell at same timestamp
+ ProfitCalculation profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "concurrentBS", new Date(1518010558000l),
+ new Date(1715629591000l));
+
+ Map<Integer, Double> map = profitCalculation.getProfitMap();
+
+ assertEquals(map.get(100), 1, .01);
+ //buys at multiple prices
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "mulitpleBuyPrices",
+ new Date(1518010558000l),
+ new Date(1715629591000l));
+
+ assertEquals(profitCalculation.getHoldingsList(-1).get(0).getRealizedGain(), 3750, 0.01);
+ assertEquals(profitCalculation.getMoneyInput(), 3750, .01);
+
+ //left over holdings
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "dontSellAll",
+ new Date(1518010558000l),
+ new Date(1715629591000l));
+
+ assertEquals(profitCalculation.getHoldingsList(-1).get(0).getShares(), 25, .01);
+ tearDown();
+ }
+
+ @Test
+ public void testAPICalls() {
+ ProfitCalculation profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "Don", new Date(1618234200000l),
+ new Date(1618703800000l));
+
+ //check sp500 calculation. 411.28 to 417.30
+ assertEquals(profitCalculation.compareToSP500(), .01464, .001);
+
+ tearDown();
+
+ }
+
+ @Test
+ public void databaseAndConnectionIssues() {
+ //no database connection
+ ProfitCalculation profitCalculation =
+ new ProfitCalculation(null, "Don", new Date(1518010558000l),
+ new Date(1618698807000l));
+ assertEquals(profitCalculation.getProfitMap(), new HashMap<>());
+
+ assertEquals(profitCalculation.getHoldingsList(-1), new LinkedList<>());
+
+ setUp();
+ //invalid person
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "1234", new Date(1518010558000l),
+ new Date(1618698807000l));
+ assertEquals(profitCalculation.getHoldingsList(-1), new LinkedList<>());
+
+
+ //invalid stock ticker
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "invalidTicker", new Date(1518010558000l),
+ new Date(1618698807000l));
+ assertTrue(profitCalculation.getHoldingsList(-1).isEmpty());
+ }
} \ No newline at end of file
diff --git a/tatus b/tatus
new file mode 100644
index 0000000..29e3798
--- /dev/null
+++ b/tatus
@@ -0,0 +1,52 @@
+diff --git a/data/mock_tradeClarks.sqlite3 b/data/mock_tradeClarks.sqlite3
+index 980c539..e816ee8 100644
+Binary files a/data/mock_tradeClarks.sqlite3 and b/data/mock_tradeClarks.sqlite3 differ
+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 31bf7a3..c4a7814 100644
+--- a/src/main/java/edu/brown/cs/student/term/Main.java
++++ b/src/main/java/edu/brown/cs/student/term/Main.java
+@@ -47,12 +47,21 @@ public final class Main {
+ //do a gui type thing
+ //runSparkServer((int) options.valueOf("port"));
+ }
+- 
+- HashMap<String, Command> commandHashMap = new HashMap<>();
+- commandHashMap.put("setup", new SetupCommand());
+- /** add commands to map here! */
+- REPL repl = new REPL(commandHashMap);
+- repl.runREPL();
++
++ ProfitCalculation person = new ProfitCalculation(null, "Vincent", new Date(1515629591000L), new Date(1715507898000L));
++ try {
++ person.setConnection("./data/mock_tradeClarks.sqlite3");
++ } catch(Exception e) {
++ e.printStackTrace();
++ }
++
++ person.calculateGains();
++
++// HashMap<String, Command> commandHashMap = new HashMap<>();
++// commandHashMap.put("setup", new SetupCommand());
++// /** add commands to map here! */
++// REPL repl = new REPL(commandHashMap);
++// repl.runREPL();
+ }
+ 
+ 
+diff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java
+index aa1bc09..94f87f7 100644
+--- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java
++++ b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java
+@@ -265,10 +265,10 @@ public class ProfitCalculation {
+ double totalGains = unrealizedGains + realizedGains;
+ 
+ System.out.println("Money In: " + moneyInput);
++ System.out.println("SP500 on money In: " + (moneyInput * compareToSP500()));
+ System.out.println("Money Out: " + (moneyInput + totalGains));
+- System.out.println("NASDAQ on money In: " + (moneyInput * compareToSP500()));
+ System.out.println(
+- "Total: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " +
++ "Total gain: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " +
+ realizedGains);
+ }
+