diff options
-rw-r--r-- | data/mock_tradeClarks.sqlite3 | bin | 53248 -> 0 bytes | |||
-rw-r--r-- | data/profit_testing.sqlite3 | bin | 0 -> 8192 bytes | |||
-rw-r--r-- | react-frontend/src/App.js | 224 | ||||
-rw-r--r-- | react-frontend/src/components/HubList.js | 2 | ||||
-rw-r--r-- | react-frontend/src/components/Visualization.js | 41 | ||||
-rw-r--r-- | react-frontend/src/components/WatchDogs.js | 89 | ||||
-rw-r--r-- | react-frontend/src/components/images/person.svg | 1 | ||||
-rw-r--r-- | react-frontend/src/css/App.css | 1 | ||||
-rw-r--r-- | react-frontend/src/css/Landing.css | 322 | ||||
-rw-r--r-- | react-frontend/src/images/mainlogo.png (renamed from react-frontend/src/components/images/mainlogo.png) | bin | 269065 -> 269065 bytes | |||
-rw-r--r-- | src/main/java/edu/brown/cs/student/term/Main.java | 6 | ||||
-rw-r--r-- | src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java | 32 | ||||
-rw-r--r-- | src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java | 334 | ||||
-rw-r--r-- | src/main/java/edu/brown/cs/student/term/profit/StockHolding.java | 26 | ||||
-rw-r--r-- | src/test/java/edu/brown/cs/student/ProfitCalculationTest.java | 94 | ||||
-rw-r--r-- | tatus | 52 |
16 files changed, 883 insertions, 341 deletions
diff --git a/data/mock_tradeClarks.sqlite3 b/data/mock_tradeClarks.sqlite3 Binary files differdeleted file mode 100644 index 980c539..0000000 --- a/data/mock_tradeClarks.sqlite3 +++ /dev/null diff --git a/data/profit_testing.sqlite3 b/data/profit_testing.sqlite3 Binary files differnew file mode 100644 index 0000000..33dd2b8 --- /dev/null +++ b/data/profit_testing.sqlite3 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 Binary files differindex a26df0f..a26df0f 100644 --- a/react-frontend/src/components/images/mainlogo.png +++ b/react-frontend/src/images/mainlogo.png 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 @@ -0,0 +1,52 @@ +[1mdiff --git a/data/mock_tradeClarks.sqlite3 b/data/mock_tradeClarks.sqlite3[m +[1mindex 980c539..e816ee8 100644[m +Binary files a/data/mock_tradeClarks.sqlite3 and b/data/mock_tradeClarks.sqlite3 differ +[1mdiff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java[m +[1mindex 31bf7a3..c4a7814 100644[m +[1m--- a/src/main/java/edu/brown/cs/student/term/Main.java[m +[1m+++ b/src/main/java/edu/brown/cs/student/term/Main.java[m +[36m@@ -47,12 +47,21 @@[m [mpublic final class Main {[m + //do a gui type thing[m + //runSparkServer((int) options.valueOf("port"));[m + }[m +[31m- [m +[31m- HashMap<String, Command> commandHashMap = new HashMap<>();[m +[31m- commandHashMap.put("setup", new SetupCommand());[m +[31m- /** add commands to map here! */[m +[31m- REPL repl = new REPL(commandHashMap);[m +[31m- repl.runREPL();[m +[32m+[m +[32m+[m[32m ProfitCalculation person = new ProfitCalculation(null, "Vincent", new Date(1515629591000L), new Date(1715507898000L));[m +[32m+[m[32m try {[m +[32m+[m[32m person.setConnection("./data/mock_tradeClarks.sqlite3");[m +[32m+[m[32m } catch(Exception e) {[m +[32m+[m[32m e.printStackTrace();[m +[32m+[m[32m }[m +[32m+[m +[32m+[m[32m person.calculateGains();[m +[32m+[m +[32m+[m[32m// HashMap<String, Command> commandHashMap = new HashMap<>();[m +[32m+[m[32m// commandHashMap.put("setup", new SetupCommand());[m +[32m+[m[32m// /** add commands to map here! */[m +[32m+[m[32m// REPL repl = new REPL(commandHashMap);[m +[32m+[m[32m// repl.runREPL();[m + }[m + [m + [m +[1mdiff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java[m +[1mindex aa1bc09..94f87f7 100644[m +[1m--- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java[m +[1m+++ b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java[m +[36m@@ -265,10 +265,10 @@[m [mpublic class ProfitCalculation {[m + double totalGains = unrealizedGains + realizedGains;[m + [m + System.out.println("Money In: " + moneyInput);[m +[32m+[m[32m System.out.println("SP500 on money In: " + (moneyInput * compareToSP500()));[m + System.out.println("Money Out: " + (moneyInput + totalGains));[m +[31m- System.out.println("NASDAQ on money In: " + (moneyInput * compareToSP500()));[m + System.out.println([m +[31m- "Total: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " +[m +[32m+[m[32m "Total gain: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " +[m + realizedGains);[m + }[m + [m |