aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/mock_tradeClarks.sqlite3bin53248 -> 0 bytes
-rw-r--r--data/profit_testing.sqlite3bin0 -> 8192 bytes
-rw-r--r--react-frontend/src/App.js224
-rw-r--r--react-frontend/src/components/HubList.js2
-rw-r--r--react-frontend/src/components/Visualization.js41
-rw-r--r--react-frontend/src/components/WatchDogs.js89
-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/Landing.css322
-rw-r--r--react-frontend/src/images/mainlogo.png (renamed from react-frontend/src/components/images/mainlogo.png)bin269065 -> 269065 bytes
-rw-r--r--src/main/java/edu/brown/cs/student/term/Main.java6
-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.java334
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/StockHolding.java26
-rw-r--r--src/test/java/edu/brown/cs/student/ProfitCalculationTest.java94
-rw-r--r--tatus52
16 files changed, 883 insertions, 341 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/react-frontend/src/App.js b/react-frontend/src/App.js
index e242115..982f6d4 100644
--- a/react-frontend/src/App.js
+++ b/react-frontend/src/App.js
@@ -1,90 +1,160 @@
// React/component imports
import React, {useEffect, useState} from 'react';
-import TimeSelector from './components/TimeSelector.js';
-import Visualization from './components/Visualization.js';
-import HubList from './components/HubList.js';
-import Loading from './components/Loading.js';
-import Modal from './components/Modal.js';
-import logo from './components/images/logo.png';
-
-// CSS import
-import './css/App.css';
-
-/**
- * Main component of the app. Holds the main layout of the big components.
- * @returns {import('react').HtmlHTMLAttributes} A div of the body of the page.
- */
-function App() {
- // State to open app when loaded
- const [hasLoaded, setHasLoaded] = useState(false);
- // State indicate if canvas is redrawing
- const [isChanging, setIsChanging] = useState(false);
- // State to hold dates -> two weeks apart on initialization.
- const [dates, setDates] = useState({
- start: new Date(Date.now() - 12096e5),
- end: new Date()
- });
- // State for visualization data
- const [data, setData] = useState([]);
- // State for selected person
- const [selected, setSelected] = useState(-1);
-
- const toEpochMilli = date => Date.parse(date);
- const getGraphData = () => {
- console.log({
- start: toEpochMilli(dates.start),
- end: toEpochMilli(dates.end)
- });
- fetch("http://localhost:4567/data", {
- method: "POST",
- body: JSON.stringify({
- start: toEpochMilli(dates.start),
- end: toEpochMilli(dates.end)
- }),
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin"
- })
- .then(res => res.json())
- .then(data => {
- //TODO: optimize this
- console.log(data.holders.length);
- const sliced = data.holders.slice(0, 500);
- console.log(sliced);
- setData(sliced);
- setHasLoaded(true);
- })
- .catch(err => console.log(err));
-
- setIsChanging(false);
- }
+import WatchDogs from './components/WatchDogs.js';
+import reagan from './images/reagan.png';
+import previewwatchdog from './images/previewwatchdog.png';
+import mainlogo from './images/mainlogo.png';
- // Hooks to update data on init and switching of data
- //useEffect(() => getGraphData(), []);
- useEffect(() => {
- setIsChanging(true);
- getGraphData();
- }, [dates]);
+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);
+ }
+
return (
<>
- {(!hasLoaded) ? <Loading></Loading> :
- <div className="App">
- <Modal>PHP</Modal>
- <header id="in-app-logo-holder" className="App-header">
- <img id="in-app-logo" src={logo} alt="logo"/></header>
- <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>
- <TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>
- <Visualization hasLoaded={hasLoaded} data={data} setSelected={setSelected}></Visualization>
+
+ {(!startApp) ?
+ <div className="body" id="main-modal">
+ <div class="nav-bar">
+ <div class="topnav">
+ <a href="#team">Team</a>
+ <a href="#app-intro">About</a>
+ <a href="#intro">Home</a>
+ </div>
+ </div>
+
+ <main>
+
+ <section id="intro" class="intro">
+ <img src={mainlogo} alt="logo"></img>
+ <button id="enter-watchdogs" onClick={exitModal}>ENTER</button>
+ </section>
+
+ <section id="1" class="app-preview">
+ <img id="preview" src={previewwatchdog}></img>
+ <span id="preview-text">preview</span>
+ </section>
+
+ <section id="app-intro">
+ <h1 class="heading">About Our App</h1>
+ <p></p>
+ <p class="text">WatchDogs utilizes a simple,
+ interactive interface to provide you with the latest
+ data relating to high-profile investors’ trades. Directly from
+ the SEC, WatchDogs relays information regarding “inside” investors
+ and their recent trades, as well as provides computed values such
+ as an individual’s net profit from a stock at time of trade and a
+ ranking of individuals most likely recently involved in insider trading.
+ The computed “suspicion ranks” are determined using multiple factors
+ (including recent trade profitability and investor connectedness)
+ and a complex algorithm. For added convenience, YOU choose the
+ timeframe WatchDogs considers when analyzing trade data. WatchDogs
+ makes insider trade data accessible to the public, and provides
+ low-level intuition regarding which investors are more and less
+ likely to be committing unlawful insider trading.</p>
+ <div class="appinfotxt">It is important to remember that suspicion
+ ranks returned by WatchDogs’ algorithm DO NOT prove--or even suggest--that
+ an individual has engaged in insider trading. WatchDogs suspicion
+ ranks should not be interpreted as indication of an individual’s
+ participation in illegal activity. WatchDogs data, including suspicion ranks,
+ cannot be used as evidence in legal proceedings. Please use WatchDogs
+ as it is intended, and use discretion when interpreting algorithmic
+ results.</div>
+ <p></p>
+ <p class="text h2">The Data</p>
+ <div class="text">WatchDogs uses data retrieved from SEC.gov’s
+ EDGAR API. Trades analyzed by WatchDogs are of the type Form 4,
+ meaning the filing individual is an “insider” (e.g. the CEO) at
+ the company whose stock they are trading. In-app data relating
+ to investors, trades, and profitability is accessible to the public via
+ the SEC, and only public data is input to the SuspicionRank algorithm.</div>
+ <p></p>
+ <p class="text h2">Our Algorithm</p>
+ <div class="text">WatchDogs suspicion rank represents the likelihood
+ of an individual being involved in insider trading. The SuspicionRank algorithm,
+ a derivative of Lary Page’s PageRank algorithm, considers “insiders” who similarly
+ trade stocks (within a given timeframe) as “linked”, while simultaneously
+ considering involved individuals’ net profit on their stocks at the time
+ of trade. (Obviously, an “inside” investor who makes a counter-productive
+ trade is not likely to be insider trading.)</div>
+ <p></p><p></p><p></p><p></p><p></p>
+ </section>
+
+ <section id="team">
+ <div class="team">
+ <div id="team-holder" class="center">
+ <h1 id="team-heading" class="heading">Meet the Team</h1>
+ <div id="people-holder">
+ <div class="team-person-holder">
+ <div class="team-person">
+ <img src="" height="100%"></img>
+ </div>
+ <p class="team-text">Clark Oh-Willeke</p>
+ </div>
+
+ <div class="team-person-holder">
+ <div class="team-person">
+ <img src="" height="100%"></img>
+ </div>
+ <p class="team-text">Julia McCauley</p>
+ </div>
+
+ <div class="team-person-holder">
+ <div class="team-person">
+ <img src="" height="100%"></img>
+ </div>
+ <p class="team-text">Michael Foiani</p>
+ </div>
+
+ <div class="team-person-holder">
+ <div class="team-person">
+ <img src={reagan} height="100%"></img>
+ </div>
+ <p class="team-text">Reagan Hunt</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ </main>
+
+ <footer id="footer">
+ <p></p>
+ <div class="footer-item">Disclaimer</div>
+ <p></p>
+ <div class="footer-subtext">Suspicion ranks returned by <span>WatchDogs</span>’
+ algorithm DO NOT prove--or even suggest--that an individual has
+ engaged in insider trading. <span>WatchDogs</span> suspicion ranks should not
+ be interpreted as indication of an individual’s participation in
+ illegal activity. <span>WatchDogs</span> data, including suspicion ranks, cannot
+ be used as evidence in legal proceedings. Please use WatchDogs as
+ it is intended, and use discretion when interpreting algorithmic results.
+ </div>
+ <p></p>
+ </footer>
</div>
+
+ :
+ <WatchDogs></WatchDogs>
}
+
</>
);
}
-export default App;
+
+
+
+
+
+export default App; \ No newline at end of file
diff --git a/react-frontend/src/components/HubList.js b/react-frontend/src/components/HubList.js
index 0df3020..c9a5156 100644
--- a/react-frontend/src/components/HubList.js
+++ b/react-frontend/src/components/HubList.js
@@ -32,7 +32,7 @@ function HubList(props) {
const getName = () => {
props.data.forEach(hub => {
- if (hub.id == props.selected) {
+ if (hub.id === props.selected) {
setName(hub.name);
}
})
diff --git a/react-frontend/src/components/Visualization.js b/react-frontend/src/components/Visualization.js
index 1975e86..9a837a1 100644
--- a/react-frontend/src/components/Visualization.js
+++ b/react-frontend/src/components/Visualization.js
@@ -6,6 +6,7 @@ import Graph from 'vis-react';
// CSS imports
import '../css/Canvas.css';
+
/**
* This function renders and mantains thhe canvas.
* @param {Object} props The props for the canvas.
@@ -27,12 +28,41 @@ function Visualization(props) {
});
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,
+ }
});
}
});
@@ -43,8 +73,13 @@ function Visualization(props) {
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',
+ }
});
});
});
diff --git a/react-frontend/src/components/WatchDogs.js b/react-frontend/src/components/WatchDogs.js
new file mode 100644
index 0000000..d631ea9
--- /dev/null
+++ b/react-frontend/src/components/WatchDogs.js
@@ -0,0 +1,89 @@
+// React/component imports
+import React, {useEffect, useState} from 'react';
+import TimeSelector from './TimeSelector.js';
+import Visualization from './Visualization.js';
+import HubList from './HubList.js';
+import Loading from './Loading.js';
+import Modal from './Modal.js';
+import logo from './images/logo.png';
+
+// CSS import
+import '../css/App.css';
+
+/**
+ * Main component of the app. Holds the main layout of the big components.
+ * @returns {import('react').HtmlHTMLAttributes} A div of the body of the page.
+ */
+function WatchDogs() {
+ // State to open app when loaded
+ const [hasLoaded, setHasLoaded] = useState(false);
+ // State indicate if canvas is redrawing
+ const [isChanging, setIsChanging] = useState(false);
+ // State to hold dates -> two weeks apart on initialization.
+ const [dates, setDates] = useState({
+ start: new Date(Date.now() - 12096e5),
+ end: new Date()
+ });
+ // State for visualization data
+ const [data, setData] = useState([]);
+ // State for selected person
+ const [selected, setSelected] = useState(-1);
+
+ const toEpochMilli = date => Date.parse(date);
+ const getGraphData = () => {
+ console.log({
+ start: toEpochMilli(dates.start),
+ end: toEpochMilli(dates.end)
+ });
+ fetch("http://localhost:4567/data", {
+ method: "POST",
+ body: JSON.stringify({
+ start: toEpochMilli(dates.start),
+ end: toEpochMilli(dates.end)
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin"
+ })
+ .then(res => res.json())
+ .then(data => {
+ //TODO: optimize this
+ const sliced = data.holders.slice(0, 500);
+ console.log(sliced);
+ setData(sliced);
+ setHasLoaded(true);
+ })
+ .catch(err => console.log(err));
+
+ setIsChanging(false);
+ }
+
+
+ // Hooks to update data on init and switching of data
+ //useEffect(() => getGraphData(), []);
+ useEffect(() => {
+ setIsChanging(true);
+ getGraphData();
+ }, [dates]);
+
+ return (
+ <>
+ {(!hasLoaded) ? <Loading></Loading> :
+ <div className="App">
+ <Modal>PHP</Modal>
+ <header id="in-app-logo-holder" className="App-header">
+ <img id="in-app-logo" src={logo} alt="logo"/></header>
+ <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>
+ <TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>
+ <Visualization hasLoaded={hasLoaded} data={data} setSelected={setSelected}></Visualization>
+ </div>
+ }
+ </>
+ );
+}
+
+export default WatchDogs;
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 f632a7f..e39eb3e 100644
--- a/react-frontend/src/css/App.css
+++ b/react-frontend/src/css/App.css
@@ -6,6 +6,7 @@
grid-template-rows: max-content auto max-content;
grid-template-columns: max-content auto max-content max-content;
background-color: #121212;
+
}
.App-logo {
diff --git a/react-frontend/src/css/Landing.css b/react-frontend/src/css/Landing.css
index c2bc567..58658b0 100644
--- a/react-frontend/src/css/Landing.css
+++ b/react-frontend/src/css/Landing.css
@@ -1,244 +1,256 @@
html {
- scroll-behavior: smooth;
- }
+ scroll-behavior: smooth;
+ z-index: 1000;
+
+}
.nav-bar{
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100vw;
- height: 70px;
- background-color: rgb(7, 7, 44);
- z-index: 100;
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100vw;
+ height: 70px;
+ background-color: rgb(7, 7, 44);
+ z-index: 100;
}
.topnav {
- background-color: rgb(7, 7, 44);
- overflow: hidden;
- margin-right: 30px;
+ background-color: rgb(7, 7, 44);
+ overflow: hidden;
+ margin-right: 30px;
}
.topnav a {
- float: right;
- color: #f2f2f2;
- text-align: center;
- padding: 14px 16px;
- text-decoration: none;
- font-size: 17px;
- height: 30px;
- padding-top: 25px;
+ float: right;
+ color: #f2f2f2;
+ text-align: center;
+ padding: 14px 16px;
+ text-decoration: none;
+ font-size: 17px;
+ height: 30px;
+ padding-top: 25px;
}
.topnav a:hover {
- background-color: rgb(17, 11, 99);
- color: white;
+ background-color: rgb(17, 11, 99);
+ color: white;
}
.topnav a.active {
- background-color: #4CAF50;
- color: white;
+ background-color: #4CAF50;
+ color: white;
}
-body {
- background-color: #f3f3f3;
- font-family:Verdana, Geneva, Tahoma, sans-serif;
- overflow-y: scroll;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
- width: 80vw;
- scroll-behavior: smooth slow;
+.body {
+ background-color: #f3f3f3;
+ font-family:Verdana, Geneva, Tahoma, sans-serif;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ width: 100vw;
+ scroll-behavior: smooth slow;
+ z-index: 100;
}
main {
- margin-top: 65px;
- display: flex;
- flex-direction: column;
+ margin-top: 65px;
+ display: flex;
+ flex-direction: column;
}
.intro {
- height: 900px;
- width: 100vw;
- background-color: rgb(3, 2, 24);
- color: white;
- position: relative;
- left: -10px;
+ height: 900px;
+ width: 100vw;
+ background-color: rgb(3, 2, 24);
+ color: white;
+ position: relative;
+ left: -10px;
}
#enter-watchdogs {
- background-color: rgb(206, 206, 206);
- color: rgba(10, 9, 71);
- border: 2px solid rgb(255, 255, 255);
- border-radius: 5px;
- padding: 5px 10px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- height: 70px;
- width: 200px;
- font-weight: bold;
- font-family:sans-serif;
+ background-color: rgb(206, 206, 206);
+ color: rgba(10, 9, 71);
+ border: 2px solid rgb(255, 255, 255);
+ border-radius: 5px;
+ padding: 5px 10px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ height: 70px;
+ width: 200px;
+ font-weight: bold;
+ font-family:sans-serif;
}
#enter-watchdogs:hover {
- background-color: #94ff73;
- }
+ background-color: #94ff73;
+}
.app-preview {
- height: 900px;
- width: 100vw;
- background-color: #b7ffb9;
- color: white;
- position: relative;
- left: 0px;
- position: relative;
- left: -10px;
+ height: 900px;
+ width: 100vw;
+ background-color: #b7ffb9;
+ color: white;
+ position: relative;
+ left: 0px;
+ position: relative;
+ left: -10px;
}
#preview {
- width: 70vw;
- border: 10px solid rgb(255, 255, 255);
- border-radius: 5px;
+ width: 70vw;
+ border: 10px solid rgb(255, 255, 255);
+ border-radius: 5px;
}
#preview-text {
- color:#868686;
- font-size: small;
+ color:#868686;
+ font-size: small;
}
section {
- width: 100vw;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
+width: 100vw;
+overflow: hidden;
+display: flex;
+flex-direction: column;
+justify-content: center;
+align-items: center;
}
#app-intro{
- padding-top: 160px;
- height: fit-content;
- width: 100vw;
+padding-top: 160px;
+height: fit-content;
+width: 100vw;
}
.team {
- width: 100%;
- max-width: 1600px;
- display: flex;
- flex-direction: row;
- justify-content: space-evenly;
- padding-top: 0px;
- width: 80%;
- min-height: 500px;
- align-items: center;
+width: 100%;
+max-width: 1600px;
+display: flex;
+flex-direction: row;
+justify-content: space-evenly;
+padding-top: 0px;
+width: 80%;
+min-height: 500px;
+align-items: center;
}
.center {
- display: flex;
- flex-direction: column;
- justify-content: center;
+display: flex;
+flex-direction: column;
+justify-content: center;
}
.heading{
- font-size: 45px;
- margin: 10px 0 0 0;
- text-align: center;
- font-weight: bold;
+font-size: 45px;
+margin: 10px 0 0 0;
+text-align: center;
+font-weight: bold;
}
.text{
- color: rgba(0,0,0, 0.6);
- font-size: 18px;
- display: flex;
- justify-content: center;
- align-items: center;
- text-align: center;
- max-width: 700px;
- margin: 10px 0 20px 0;
+color: rgba(0,0,0, 0.6);
+font-size: 18px;
+display: flex;
+justify-content: center;
+align-items: center;
+text-align: center;
+max-width: 70vw;
+margin: 10px 0 20px 0;
}
#team {
- width: 100vw;
- padding: 50px 0 200px 0;
- justify-content: flex-start;
- background-color: #cccccc;
- position: relative;
- left: -10px;
+width: 100vw;
+padding: 50px 0 200px 0;
+justify-content: flex-start;
+background-color: #cccccc;
+position: relative;
+left: -10px;
}
#team-heading{
- margin-bottom: 0px;
+margin-bottom: 0px;
}
#team-holder {
- flex-direction: column;
- max-width: 1000px;
- max-height: 500px;
- height: 500px;
+flex-direction: column;
+max-width: 1000px;
+max-height: 500px;
+height: 500px;
}
#people-holder{
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- width: 100%;
+display: flex;
+flex-direction: row;
+justify-content: center;
+align-items: center;
+width: 100%;
}
.team-person-holder{
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- margin-top: 50px;
+display: flex;
+flex-direction: column;
+justify-content: center;
+align-items: center;
+margin-top: 50px;
}
.team-person{
- border-radius: 100px;
- overflow: hidden;
- margin: 20px;
- height: 200px;
- width: 200px;
- box-shadow: 0 0 2px 2px rgba(0,0,0,0.15);
+border-radius: 100px;
+overflow: hidden;
+margin: 20px;
+height: 200px;
+width: 200px;
+box-shadow: 0 0 2px 2px rgba(0,0,0,0.15);
}
.team-text{
- color: rgba(0,0,0, 1);
- font-size: 18px;
+color: rgba(0,0,0, 1);
+font-size: 18px;
+display: flex;
+justify-content: center;
+align-items: center;
+text-align: center;
+max-width: 700px;
+margin: 0;
+}
+
+#footer {
+ width: 100vw;
+ background: rgb(7, 7, 44);
display: flex;
+ flex-direction: column;
justify-content: center;
align-items: center;
- text-align: center;
- max-width: 700px;
- margin: 0;
+ position: relative;
+ left: -10px;
+ height: fit-content;
+ width: 100vw;
}
-#footer {
- width: 100vw;
- background: rgb(7, 7, 44);
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- position: relative;
- left: -10px;
- height: fit-content;
- width: 100vw;
- }
-
.footer-item{
- color: white;
- font-size: 20px;
+ color: white;
+ font-size: 20px;
}
.footer-subtext{
- color: white;
- font-size: 15px;
- width: 80vw;
+ color: white;
+ font-size: 15px;
+ width: 80vw;
}
.appinfotxt {
- font-size: 15px;
- color:rgb(3, 2, 24);
+font-size: 15px;
+color:rgb(3, 2, 24);
+max-width: 70vw;
+}
+
+.h2 {
+font-size: 30px;
+color: black;
}
+#return-to-landing {
+z-index: 1000;
+}
diff --git a/react-frontend/src/components/images/mainlogo.png b/react-frontend/src/images/mainlogo.png
index a26df0f..a26df0f 100644
--- a/react-frontend/src/components/images/mainlogo.png
+++ b/react-frontend/src/images/mainlogo.png
Binary files 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 ee3bec1..dd304c5 100644
--- a/src/main/java/edu/brown/cs/student/term/Main.java
+++ b/src/main/java/edu/brown/cs/student/term/Main.java
@@ -78,12 +78,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<>();
@@ -185,7 +185,7 @@ public final class Main {
ProfitCalculation profit =
new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod);
List<StockHolding> holdings = profit.getHoldingsList();
- double gains = profit.calculateGains();
+ double gains = profit.calculateGainsSingle();
double sp500PercentGain = profit.compareToSP500();
Map<String, Object> res = new HashMap<>();
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..d0df8a8 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 {
@@ -78,10 +83,10 @@ 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 "";
}
@@ -94,7 +99,7 @@ public class ProfitCalculation {
PreparedStatement prep;
prep =
conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? "
- + " AND trade_timestamp BETWEEN ? AND ?"
+ + " AND trade_timestamp BETWEEN ? AND ? "
+ "order by trade_timestamp asc;");
prep.setString(1, this.person);
prep.setDate(2, startTime);
@@ -104,7 +109,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 +130,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 +154,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 +248,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 +265,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,27 +283,30 @@ public class ProfitCalculation {
}
- public double calculateGains() {
+ public double calculateGainsSingle() {
+
if (!tablesFilled) {
organizeOrders();
getRealizedGains();
- getUnrealizedGains();
tablesFilled = true;
}
+
+
double realizedGains = 0;
- double unrealizedGains = 0;
for (double value : realizedGainsMap.values()) {
realizedGains += value;
}
- for (double value : unrealizedGainsMap.values()) {
- unrealizedGains += value;
- }
- return unrealizedGains + realizedGains;
+ return realizedGains;
}
public List<StockHolding> getHoldingsList() {
+ if (conn == null) {
+ System.out.println("ERROR: No database connection");
+ return new LinkedList<>();
+ }
+
if (!tablesFilled) {
organizeOrders();
getRealizedGains();
@@ -329,11 +339,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 +380,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 +587,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..5edb5f7 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,21 @@
package edu.brown.cs.student.term.profit;
+/**
+ * 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 +23,27 @@ 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;
+ }
} \ No newline at end of file
diff --git a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
index 256afed..0d22109 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));
+ 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();
- double gain = trade.get(0).getUnrealizedGain();
- assertEquals(294800.0, gain, .01);
+ //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();
+ assertTrue(trade.isEmpty());
+ assertEquals(profitCalculation.calculateGainsSingle(), 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().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().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(), new LinkedList<>());
+
+ setUp();
+ //invalid person
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "1234", new Date(1518010558000l),
+ new Date(1618698807000l));
+ assertEquals(profitCalculation.getHoldingsList(), new LinkedList<>());
+
+
+ //invalid stock ticker
+ profitCalculation =
+ new ProfitCalculation(DatabaseQuerier.getConn(), "invalidTicker", new Date(1518010558000l),
+ new Date(1618698807000l));
+ assertTrue(profitCalculation.getHoldingsList().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);
+ }
+