aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@michaels-mbp-3.lan>2021-04-16 16:37:08 -0400
committerMichael Foiani <sotech117@michaels-mbp-3.lan>2021-04-16 16:37:08 -0400
commita2dc033f7d80ec4599e6c0f7bb1ef5753d8799fa (patch)
tree119bf795ffda3a915deadb4ed51d2536ebf4f1cb
parent3182aec0fa9f1707435f92b0c0644c602125b0be (diff)
parent7cf6c2b08add62df693a9a2e2c1ee0ed6f0c5aee (diff)
Added the new frontend. Still needs querying and lots of testing.
-rwxr-xr-xcs32-test222
-rw-r--r--data/empty.sqlite3bin8192 -> 0 bytes
-rw-r--r--data/testing/empty.sqlite3bin0 -> 8192 bytes
-rw-r--r--frontend/src/DateInput.js35
-rw-r--r--frontend/src/SECAPIData.js44
-rw-r--r--src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java68
-rw-r--r--src/main/java/edu/brown/cs/student/term/Main.java46
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/Holder.java24
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/HubSearch.java3
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java48
-rw-r--r--src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java15
-rw-r--r--src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java48
-rw-r--r--src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java1
-rw-r--r--src/test/java/edu/brown/cs/student/DBQuerierTest.java2
-rw-r--r--src/test/java/edu/brown/cs/student/HubRankTest.java45
-rw-r--r--src/test/java/edu/brown/cs/student/LinkMapperTest.java2
-rw-r--r--src/test/java/edu/brown/cs/student/ProfitCalculationTest.java56
-rw-r--r--src/test/java/edu/brown/cs/student/SuspicionRankerTest.java53
-rw-r--r--tests/student/hub/data_not_loaded.test5
-rw-r--r--tests/student/hub/fake_command.test5
-rw-r--r--tests/student/hub/load_data.test5
-rw-r--r--tests/student/hub/no_trades_for_dates.test6
-rw-r--r--tests/student/hub/rank_lil_mock.test12
-rw-r--r--tests/student/hub/too_many_arguments_setup.test5
24 files changed, 692 insertions, 58 deletions
diff --git a/cs32-test b/cs32-test
new file mode 100755
index 0000000..8ca6a99
--- /dev/null
+++ b/cs32-test
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+
+from optparse import OptionParser
+import difflib, io, os, subprocess, sys, tempfile, codecs
+sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1)
+
+# color constants
+END = '\033[0m'
+HEADER = '\033[48;5;60m\033[38;5;15m'
+TITLE = '\033[1;30m'
+FAIL = '\033[0;31m'
+CRASH = '\033[1;31m'
+SEP = '\033[0;37m'
+PASS = '\033[0;32m'
+GOOD = '\033[38;5;107m'
+BAD = '\033[38;5;52m'
+
+DIVIDER_WIDTH = 80
+
+# Section tags in the test case
+ARGS_TAG = 'ARGS'
+INPUT_TAG = 'INPUT'
+OUTPUT_TAG = 'OUTPUT'
+SECTION_TAGS = [ARGS_TAG, INPUT_TAG, OUTPUT_TAG]
+END_TAG = 'END'
+
+# Message prefixes in the program's output
+ERROR_PREFIX = 'ERROR:'
+IGNORE_PREFIX = 'INFO:'
+
+differ = difflib.Differ()
+
+class TestFormatError(Exception):
+ """Raised when a test file contains syntax errors."""
+ def __init__(self, message):
+ self.message = message
+
+
+class Test:
+ """An individual test case."""
+ def __init__(self, executable, filepath, ignore_whitespace=False):
+ if not os.path.isfile(executable):
+ print('Executable', executable, 'does not exist.')
+ sys.exit(1)
+
+ self.executable = executable
+
+ if not os.path.isfile(filepath):
+ print('Test ', filepath, 'does not exist.')
+ sys.exit(1)
+
+ self.filename = os.path.basename(filepath)
+ if self.filename.find('.') >= 0:
+ self.name = self.filename[:self.filename.index('.')]
+
+ try:
+ sections = self.readtest(filepath, ignore_whitespace)
+ except TestFormatError as tfe:
+ print('Failed to parse "{0}": {1}'.format(filepath, tfe.message))
+ self.valid = False
+ return
+
+ self.valid = True
+ self.args = sections[ARGS_TAG]
+ self.input = sections[INPUT_TAG]
+ self.expected = sections[OUTPUT_TAG]
+
+ self.command = self.executable + ' ' + self.args
+ if self.executable[-4:] == '.jar':
+ self.command = 'java -jar ' + self.command
+ self.description = self.command
+
+ @staticmethod
+ def readtest(filename, ignore_whitespace=False):
+ """Extract the ARGS, INPUT, and OUTPUT sections from a test file.
+
+ Returns a dict with:
+ ARGS_TAG -> str
+ INPUT_TAG -> list of str
+ OUTPUT_TAG -> list of str
+ """
+ sections = {
+ ARGS_TAG: [],
+ INPUT_TAG: [],
+ OUTPUT_TAG: []
+ }
+ current_section = None
+
+ with open(filename, 'r', encoding = "utf-8") as file:
+ lines = [line for line in file.readlines()]
+
+ for idx, line in enumerate(lines):
+ if line.startswith(END_TAG):
+ current_section = END_TAG
+ break
+ elif any(line.startswith(tag) for tag in SECTION_TAGS):
+ current_section = line.rstrip()
+ else:
+ sections[current_section].append(line.rstrip('\n') if not ignore_whitespace else line.rstrip())
+
+ # print(sections)
+
+ if len(sections[ARGS_TAG]) > 1:
+ raise TestFormatError('All arguments under the ARGS tag must be on the same line.')
+ elif len(sections[ARGS_TAG]) == 1:
+ sections[ARGS_TAG] = sections[ARGS_TAG][0]
+ else:
+ sections[ARGS_TAG] = ""
+
+ if current_section != END_TAG:
+ raise TestFormatError('No END tag found. Every test file must contain an END tag.')
+
+ return sections
+
+ # For collapsing consecutive "ERROR: ..." lines down into one line
+ @staticmethod
+ def collapse_errors(lines):
+ collapsed_lines = []
+ prev_was_error = False
+ for line in lines:
+ if not line.startswith("ERROR:"):
+ collapsed_lines.append(line)
+ prev_was_error = False
+ elif line.startswith("ERROR:") and not prev_was_error:
+ collapsed_lines.append("ERROR:")
+ prev_was_error = True
+ else: # Line is an error line, but we already added the previous line as an error
+ continue
+ return collapsed_lines
+
+ @staticmethod
+ def acceptable(expected, actual):
+ expected = Test.collapse_errors(expected)
+ actual = Test.collapse_errors(actual)
+ if len(expected) != len(actual):
+ return False
+ for expected_line, actual_line in zip(expected, actual):
+ if expected_line != actual_line:
+ if not expected_line.startswith("ERROR:") or not actual_line.startswith("ERROR:"):
+ return False
+ return True
+
+ """
+ Runs the test. Returns True if passed, False otherwise.
+
+ @param timeout time limit on waiting for a response from the student
+ executable (in seconds)
+ """
+ def run(self, timeout, ignore_whitespace):
+ print('Running ' + self.name)
+
+ with tempfile.NamedTemporaryFile(mode='w+',encoding="utf-8") as temp:
+ with tempfile.NamedTemporaryFile(mode='r+',encoding ="utf-8") as input:
+ for line in self.input:
+ print(line, file=input)
+ input.seek(0)
+
+ try:
+ cp = subprocess.call(["sh", "-c", self.command], timeout=timeout,
+ stdin=input, stdout=temp, stderr=subprocess.STDOUT)
+ except subprocess.TimeoutExpired:
+ print("%s timed out after %f seconds" % (self.name, timeout))
+ self.passed = False
+ return self.passed
+ temp.seek(0)
+
+ actual = []
+ for line in temp:
+ if not line.startswith(IGNORE_PREFIX):
+ actual.append(line.rstrip('\n') if not ignore_whitespace else line.rstrip())
+
+ passed = Test.acceptable(self.expected, actual)
+ if not passed:
+ # Only diff if the test failed (diff'ing will almost always point out ERROR tags)
+ for line in differ.compare(self.expected, actual):
+ if line[0:2] != ' ':
+ print(line)
+
+ print('Result: ', (PASS + 'Passed' if passed else FAIL + 'Failed'), END)
+ print(SEP + ('-' * DIVIDER_WIDTH) + END)
+
+ self.passed = passed
+ return self.passed
+
+
+if __name__ == '__main__':
+ parser = OptionParser()
+ parser.add_option('-t', '--timeout', dest='timeout',
+ help=("The timeout (in seconds) to use when waiting for each test to run."
+ " 5 by default."), type='float', default=5.0)
+ parser.add_option('-e', '--executable', dest='executable',
+ help=("The executable to test. `./run` by default."),
+ type='string', default='./run')
+ parser.add_option('-i', '--ignore-whitespace', action='store_true', dest='ignore_whitespace',
+ help=("Whether or not to ignore trailing whitespace. False (not ignored) by default."),
+ default=False)
+ (opts, args) = parser.parse_args()
+
+ if len(args) < 1:
+ print('Usage:', sys.argv[0], '<test file> [<test file> ...]')
+ print('Run `', sys.argv[0], ' --help` for more options.')
+ sys.exit(1)
+
+ executable = opts.executable
+ tests = [Test(executable, arg, opts.ignore_whitespace) for arg in args]
+
+ passed = 0
+ for test in tests:
+ if test.valid:
+ test.run(opts.timeout, opts.ignore_whitespace)
+ if test.passed:
+ passed += 1
+
+ print(END)
+
+ # Print summary
+ print(str(passed), '/', str(len(tests)), 'tests passed' + END)
+
+ if passed == len(tests):
+ print(PASS + 'TEST SUITE PASSED' + END)
+ else:
+ print(FAIL + 'TEST SUITE FAILED' + END)
diff --git a/data/empty.sqlite3 b/data/empty.sqlite3
index 3d10907..e69de29 100644
--- a/data/empty.sqlite3
+++ b/data/empty.sqlite3
Binary files differ
diff --git a/data/testing/empty.sqlite3 b/data/testing/empty.sqlite3
new file mode 100644
index 0000000..3d10907
--- /dev/null
+++ b/data/testing/empty.sqlite3
Binary files differ
diff --git a/frontend/src/DateInput.js b/frontend/src/DateInput.js
new file mode 100644
index 0000000..b5962f0
--- /dev/null
+++ b/frontend/src/DateInput.js
@@ -0,0 +1,35 @@
+// React import
+import { useEffect, useState, useRef } from "react";
+
+/**
+ * Componenet for checkins. Has a toggle to show more info.
+ * @param {Object} props The props of the component.
+ * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
+ */
+function DateInput(props) {
+ const startInput = useRef();
+ const endInput = useRef();
+
+ const toValue = date => date.toISOString().slice(0, 10);
+
+ useEffect(() => setInitDates(), []);
+
+ const setInitDates = () => {
+ startInput.current.value = toValue(new Date());
+
+ // Two weeks away -> from stack overflow,
+ const fortnightAway = toValue(new Date(Date.now() - 12096e5));
+ endInput.current.value = fortnightAway;
+ }
+
+ return (
+ <>
+ <label for="start">Start date:</label>
+ <input type="date" id="start" ref={startInput} onChange={(e) => props.setStart(e.target.value)}/>
+ <label for="end">End date:</label>
+ <input type="date" id="end" ref={endInput} onChange={(e) => props.setEnd(e.target.value)}/>
+ </>
+ );
+}
+
+export default DateInput; \ No newline at end of file
diff --git a/frontend/src/SECAPIData.js b/frontend/src/SECAPIData.js
new file mode 100644
index 0000000..8d0611f
--- /dev/null
+++ b/frontend/src/SECAPIData.js
@@ -0,0 +1,44 @@
+import React, {useState, useEffect} from 'react';
+import Button from './Button';
+import HubList from './HubList';
+import DateInput from './DateInput';
+
+
+
+function SECAPIData() {
+ const [displayData, setDisplayData] = useState({});
+ const [startDate, setStartDate] = useState("");
+ const [endDate, setEndDate] = useState("");
+
+ const toEpochMilli = date => Date.parse(date);
+
+ const getLinks = () => {
+ fetch("http://localhost:4567/data", {
+ method: "POST",
+ body: JSON.stringify(
+ {
+ "start" : toEpochMilli(startDate),
+ "end" : toEpochMilli(endDate)
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin"
+ })
+ .then(res => res.json())
+ .then(data => setDisplayData(data))
+ .catch(err => console.log(err));
+ }
+
+ return (
+ <div>
+ <h1>SECAPIData</h1>
+ <DateInput setStart={setStartDate} setEnd={setEndDate}></DateInput>
+ <Button onPress={getLinks}></Button>
+ <HubList data={displayData}></HubList>
+ </div>
+ );
+}
+
+export default SECAPIData;
+ \ No newline at end of file
diff --git a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
index 688270f..6900a19 100644
--- a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
+++ b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java
@@ -1,24 +1,29 @@
package edu.brown.cs.student.term;
+
import edu.brown.cs.student.term.hub.Holder;
+import edu.brown.cs.student.term.profit.ProfitCalculation;
import edu.brown.cs.student.term.trade.Trade;
import java.sql.*;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
public class DatabaseQuerier {
private static Connection conn = null;
//TODO: Be prepared to overhaul this to account for IDs
+
/**
* Makes a database querier for a particular sqlite database
+ *
* @param filename - String representing filepath of database
* @throws SQLException
* @throws ClassNotFoundException
*/
public DatabaseQuerier(String filename) throws SQLException,
- ClassNotFoundException {
+ ClassNotFoundException {
Class.forName("org.sqlite.JDBC");
String urlToDB = "jdbc:sqlite:" + filename;
// AutoClosable TRY-WITH-RESOURCES ensures database connection will be closed when it is done
@@ -31,8 +36,9 @@ public class DatabaseQuerier {
/**
* Gets the names of all stocks traded between start and end ddate
+ *
* @param startDate - the start date
- * @param endDate - the end date
+ * @param endDate - the end date
* @return a list of stock names
* @throws SQLException
*/
@@ -40,13 +46,13 @@ public class DatabaseQuerier {
List<String> stocks = new ArrayList<>();
PreparedStatement prep = conn.prepareStatement(
- "SELECT DISTINCT stock_name FROM trades WHERE trade_timestamp <= ? AND trade_timestamp >= ?");
+ "SELECT DISTINCT stock_name FROM trades WHERE trade_timestamp <= ? AND trade_timestamp >= ?");
prep.setLong(1, endDate.toEpochMilli());
prep.setLong(2, startDate.toEpochMilli());
ResultSet rs = prep.executeQuery();
- while(rs.next()){
+ while (rs.next()) {
stocks.add(rs.getString(1));
}
@@ -57,16 +63,18 @@ public class DatabaseQuerier {
/**
* Gets all the trades in by stock and buy type, ordered by time
+ *
* @param startDate - the start date of these trades
- * @param endDate - the end date of these trades
+ * @param endDate - the end date of these trades
* @return a list of list of trades as specified above
* @throws SQLException - if something goes wrong with connection
*/
- public List<List<Trade>> getAllTradesByStock(Instant startDate, Instant endDate) throws SQLException {
+ public List<List<Trade>> getAllTradesByStock(Instant startDate, Instant endDate)
+ throws SQLException {
List<List<Trade>> allTrades = new ArrayList<>();
List<String> stocks = getRecentStocks(startDate, endDate);
//get the buys and sells for each stock
- for(String stock: stocks){
+ for (String stock : stocks) {
allTrades.add(getTradeByStock(stock, 1, startDate, endDate));
allTrades.add(getTradeByStock(stock, 0, startDate, endDate));
}
@@ -75,19 +83,21 @@ public class DatabaseQuerier {
/**
* Gets a single stock's list of trades for that time period (either buy or sell)
- * @param stock - string name of the stock to get the trades for
- * @param isBuy - integer whether it's a buy or sell
+ *
+ * @param stock - string name of the stock to get the trades for
+ * @param isBuy - integer whether it's a buy or sell
* @param startDate - an Instant representing the start of the time period
- * @param endDate - an Instant representing the end of the time period
+ * @param endDate - an Instant representing the end of the time period
* @return - a list of trades for that stock
* @throws SQLException - if issue getting connection
*/
- public List<Trade> getTradeByStock(String stock, int isBuy, Instant startDate, Instant endDate) throws SQLException{
+ public List<Trade> getTradeByStock(String stock, int isBuy, Instant startDate, Instant endDate)
+ throws SQLException {
List<Trade> trades = new ArrayList<>();
PreparedStatement prep = conn.prepareStatement(
- "SELECT * FROM trades WHERE (stock_name = ? AND is_buy = ?) "
- + "AND (trade_timestamp <= ? AND trade_timestamp >= ?) ORDER BY trade_timestamp");
+ "SELECT * FROM trades WHERE (stock_name = ? AND is_buy = ?) "
+ + "AND (trade_timestamp <= ? AND trade_timestamp >= ?) ORDER BY trade_timestamp");
prep.setString(1, stock);
prep.setInt(2, isBuy);
@@ -95,7 +105,7 @@ public class DatabaseQuerier {
prep.setLong(4, startDate.toEpochMilli());
ResultSet rs = prep.executeQuery();
- while(rs.next()){
+ while (rs.next()) {
trades.add(new Trade(rs.getInt(1), rs.getString(2),
rs.getLong(4), rs.getInt(5),
rs.getInt(6), new Holder(rs.getInt(7), rs.getString(3)),
@@ -107,4 +117,34 @@ public class DatabaseQuerier {
return trades;
}
+
+ public List<Trade> getAllTradesByHolder(String person, Date startDate, Date endDate) {
+ LinkedList<Trade> trades = new LinkedList<>();
+
+ try {
+ PreparedStatement prep;
+ prep =
+ conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? "
+ + " AND trade_timestamp BETWEEN ? AND ?"
+ + "order by trade_timestamp asc;");
+ prep.setString(1, person);
+ prep.setDate(2, startDate);
+ prep.setDate(3, endDate);
+ ResultSet rs = prep.executeQuery();
+
+ while (rs.next()) {
+ trades.addFirst(new Trade(rs.getInt("trade_id"),
+ rs.getString("stock_name"),
+ rs.getDouble("trade_timestamp"),
+ rs.getInt("is_buy"),
+ rs.getInt("number_of_shares"),
+ new Holder(rs.getInt("holder_id"), rs.getString("holder_name")),
+ rs.getDouble("share_price")));
+ }
+ prep.close();
+ } catch (SQLException e) {
+ System.out.println("ERROR: sql error getting trades");
+ }
+ return trades;
+ }
}
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 23d8bd6..62c4ccf 100644
--- a/src/main/java/edu/brown/cs/student/term/Main.java
+++ b/src/main/java/edu/brown/cs/student/term/Main.java
@@ -8,7 +8,9 @@ import edu.brown.cs.student.term.hub.SuspicionRanker;
import edu.brown.cs.student.term.repl.Command;
import edu.brown.cs.student.term.repl.REPL;
import edu.brown.cs.student.term.repl.commands.LoadCommand;
+import edu.brown.cs.student.term.repl.commands.RankCommand;
import edu.brown.cs.student.term.repl.commands.SetupCommand;
+import edu.brown.cs.student.term.trade.Trade;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
@@ -73,7 +75,7 @@ public final class Main {
runSparkServer((int) options.valueOf("port"));
//will auto connect to correct db when running gui!
SetupCommand setConnection = new SetupCommand();
- setConnection.run(new String[]{"data/trades.sqlite3"});
+ setConnection.run(new String[] {"data/trades.sqlite3"});
}
if (!options.has("debug")) {
@@ -87,6 +89,7 @@ public final class Main {
HashMap<String, Command> commandHashMap = new HashMap<>();
commandHashMap.put("setup", new SetupCommand());
commandHashMap.put("load", new LoadCommand());
+ commandHashMap.put("rank", new RankCommand());
/** add commands to map here! */
REPL repl = new REPL(commandHashMap);
repl.runREPL();
@@ -132,6 +135,7 @@ public final class Main {
Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*"));
Spark.post("/data", new SuspicionRankHandler());
Spark.post("/profit", new ProfitQueryHandler());
+ Spark.post("/trade-lookup", new TradeQueryHandler());
}
/**
@@ -160,7 +164,7 @@ public final class Main {
Map<String, Object> variables = ImmutableMap.of("holders", suspiciousHolders);
return GSON.toJson(variables);
} catch (Exception e) {
- System.out.println("DBQuerier Test, couldn't connect to db???");
+ System.out.println("Error retrieving the suspicion ranks for GUI");
return "Error";
}
}
@@ -173,22 +177,12 @@ public final class Main {
String person = req.getString("person");
Date startPeriod = new Date(req.getLong("startTime"));
Date endPeriod = new Date(req.getLong("endTime"));
-
- List<StockHolding> holdings = new LinkedList<>();
- ProfitCalculation profit;
- double gains = 0.0;
- double sp500PercentGain = 0.0;
- double sp500Gain = 0.0;
- try {
- profit =
- new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod);
- holdings = profit.getHoldingsList();
- gains = profit.calculateGains();
- sp500PercentGain = profit.compareToSP500();
- } catch (Exception e) {
- System.out.println("DBQuerier Test, couldn't connect to db???");
- return "Error";
- }
+
+ ProfitCalculation profit =
+ new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod);
+ List<StockHolding> holdings = profit.getHoldingsList();
+ double gains = profit.calculateGains();
+ double sp500PercentGain = profit.compareToSP500();
Map<String, Object> res = new HashMap<>();
res.put("person", person);
@@ -204,6 +198,22 @@ public final class Main {
}
+ private static class TradeQueryHandler implements Route {
+ @Override
+ public Object handle(Request request, Response response) throws Exception {
+ JSONObject req = new JSONObject(request.body());
+ String person = req.getString("person");
+ Date startPeriod = new Date(req.getLong("startTime"));
+ Date endPeriod = new Date(req.getLong("endTime"));
+
+ DatabaseQuerier db = SetupCommand.getDq();
+ List<Trade> trades = db.getAllTradesByHolder(person, startPeriod, endPeriod);
+
+ return GSON.toJson(trades);
+
+ }
+ }
+
/**
* Display an error page when an exception occurs in the server.
*/
diff --git a/src/main/java/edu/brown/cs/student/term/hub/Holder.java b/src/main/java/edu/brown/cs/student/term/hub/Holder.java
index 1151e74..2d11079 100644
--- a/src/main/java/edu/brown/cs/student/term/hub/Holder.java
+++ b/src/main/java/edu/brown/cs/student/term/hub/Holder.java
@@ -1,15 +1,17 @@
package edu.brown.cs.student.term.hub;
-import java.util.Objects;
+import java.util.*;
public class Holder {
private int id;
private String name;
private double suspicionScore;
+ private Set<Holder> followers;
public Holder(int id, String name) {
this.id = id;
this.name = name;
+ followers = new HashSet<>();
}
public int getId() {
@@ -26,11 +28,29 @@ public class Holder {
return name;
}
+ public Set<Holder> getFollowers() {
+ return followers;
+ }
+
+ public void addFollower(Holder follower){
+ followers.add(follower);
+ }
+
@Override
- public String toString() {
+ public String toString() {
return name;
}
+
+ public String toTestString() {
+ return "Holder{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", suspicionScore=" + suspicionScore +
+ ", followers=" + followers +
+ '}';
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java
index ccefeef..86b883f 100644
--- a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java
+++ b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java
@@ -60,9 +60,12 @@ public class HubSearch {
who followed leader or the number of people who follower followed
--- probably the second option ;( */
Set<Holder> peopleFollowed = followerToLeaderMap.get(follower);
+
int numberFollowed = peopleFollowed.size();
if(peopleFollowed.contains(leader)){
+ //constructs the leader to follower links as we go for use later on
+ leader.addFollower(follower);
return ((damp / numHolders) + (1 - damp) * (1.0 / numberFollowed));
} else if(numberFollowed == 0){
return ((damp / numHolders) + (1 - damp) * (1.0 / numHolders));
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 9f5f9c1..74628aa 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
@@ -15,14 +15,31 @@ public class SuspicionRanker {
this.querier = db;
}
- public <K, V extends Comparable<V>> V getMaxOfMap(Map<K, V> map) {
+ private <K, V extends Comparable<V>> V getMaxOfMap(Map<K, V> map) {
Map.Entry<K, V> maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue());
- System.out.println(maxEntry);
- System.out.println(maxEntry.getValue());
return maxEntry.getValue();
}
+ private <K, V extends Comparable<V>> V getMinOfMap(Map<K, V> map) {
+ Map.Entry<K, V> maxEntry = Collections.min(map.entrySet(), Map.Entry.comparingByValue());
+ return maxEntry.getValue();
+ }
+
+ private class SuspicionComparator implements Comparator<Holder> {
+ @Override
+ public int compare(Holder o1, Holder o2) {
+ if (o1.getSuspicionScore() > o2.getSuspicionScore()) {
+ return -1;
+ } else if (o1.getSuspicionScore() < o2.getSuspicionScore()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
public List<Holder> getSuspicionScoreList(Instant start, Instant end) {
+ PriorityQueue<Holder> orderedSuspicion = new PriorityQueue<>(new SuspicionComparator());
List<Holder> suspicionList = new ArrayList<>();
try {
LinkMapper lm = new LinkMapper(querier);
@@ -35,20 +52,43 @@ public class SuspicionRanker {
Map<Integer, Double> profitMap = pc.getProfitMap();
+ //if the maps are empty, we abort because we have entirely incomplete data
+ if(profitMap.isEmpty() || holderToHubScore.isEmpty()){
+ return new ArrayList<>();
+ }
+
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(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(profitMax == 0){
+ profitMax = 1;
+ }
double hubMax = getMaxOfMap(holderToHubScore);
for (Holder guy : holderToHubScore.keySet()) {
double normalizedProfitScore = profitMap.get(guy.getId()) / profitMax;
+
double normalizedHubScore = holderToHubScore.get(guy) / hubMax;
double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4;
guy.setSuspicionScore(suspicionScore);
- suspicionList.add(guy);
+ orderedSuspicion.add(guy);
}
} catch (Exception e) {
+ e.printStackTrace();
System.out.println("ERROR: Could not connect to database querier");
}
+ int size = orderedSuspicion.size();
+ for(int i = 0; i < size; i++){
+ suspicionList.add(orderedSuspicion.poll());
+ }
return suspicionList;
}
}
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 85b2a9a..0fe35d9 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
@@ -361,17 +361,18 @@ public class ProfitCalculation {
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, this.calculateGains() / moneyInput);
+ profitMap.put(id, gain / moneyInput);
}
}
} catch (SQLException throwables) {
System.out.println("ERROR: SQl error in profit calculation");
}
- System.out.println(profitMap.toString());
return profitMap;
}
@@ -379,6 +380,16 @@ public class ProfitCalculation {
return this.moneyInput;
}
+ private void resetClass() {
+ tablesFilled = false;
+ moneyInput = 0;
+ buyHistoryMap = new HashMap<>();
+ sellHistoryMap = new HashMap<>();
+ realizedGainsMap = new HashMap<>();
+ unrealizedGainsMap = new HashMap<>();
+ tablesFilled = false;
+ }
+
public void setConnection(String filename) throws SQLException, ClassNotFoundException {
// Initialize the database connection, turn foreign keys on
diff --git a/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java b/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java
new file mode 100644
index 0000000..2005f19
--- /dev/null
+++ b/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java
@@ -0,0 +1,48 @@
+package edu.brown.cs.student.term.repl.commands;
+
+import edu.brown.cs.student.term.DatabaseQuerier;
+import edu.brown.cs.student.term.hub.Holder;
+import edu.brown.cs.student.term.hub.SuspicionRanker;
+import edu.brown.cs.student.term.repl.Command;
+import org.json.JSONObject;
+
+import java.time.Instant;
+import java.util.List;
+
+public class RankCommand implements Command {
+
+ private String error;
+
+ @Override
+ public String run(String[] args) {
+
+ error = "";
+ if(args.length == 2){
+ try{
+ DatabaseQuerier dq = SetupCommand.getDq();
+ DatabaseQuerier db = SetupCommand.getDq();
+ SuspicionRanker ranker = new SuspicionRanker(db);
+
+ long startMilli = Long.parseLong(args[0]);
+ long endMilli = Long.parseLong(args[1]);
+ Instant start = Instant.ofEpochMilli(startMilli);
+ Instant end = Instant.ofEpochMilli(endMilli);
+ List<Holder> suspiciousHolders = ranker.getSuspicionScoreList(start, end);
+ for(Holder h: suspiciousHolders){
+ System.out.println(h.toString());
+ }
+ } catch (Exception e) {
+ error = "ERROR: Could not connect to database. Ensure this is a valid database.";
+ System.out.println(error);
+ return error;
+ }
+ } else {
+ error = "ERROR: Incorrect number of arguments for suspicion rank command:"
+ + " rank [start date in epoch milliseconds] [end date in epoch milliseconds]";
+ System.out.println(error);
+ return error;
+ }
+ return error;
+
+ }
+}
diff --git a/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java b/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java
index a6ef1f0..5aa1c5a 100644
--- a/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java
+++ b/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java
@@ -24,6 +24,7 @@ public class SetupCommand implements Command {
if(args.length == 1){
try{
dq = new DatabaseQuerier(args[0]);
+ System.out.println("Connected to db at " + args[0]);
} catch (Exception e) {
error = "ERROR: Could not connect to database. Ensure this is a valid database.";
System.out.println(error);
diff --git a/src/test/java/edu/brown/cs/student/DBQuerierTest.java b/src/test/java/edu/brown/cs/student/DBQuerierTest.java
index b3ea140..4bcb193 100644
--- a/src/test/java/edu/brown/cs/student/DBQuerierTest.java
+++ b/src/test/java/edu/brown/cs/student/DBQuerierTest.java
@@ -54,7 +54,7 @@ public class DBQuerierTest {
@Test
public void testEmptyDatabase(){
try{
- db = new DatabaseQuerier("data/empty.sqlite3");
+ db = new DatabaseQuerier("data/testing/empty.sqlite3");
} catch(Exception e){
System.out.println("DBQuerier Test, couldn't connect to db???");
}
diff --git a/src/test/java/edu/brown/cs/student/HubRankTest.java b/src/test/java/edu/brown/cs/student/HubRankTest.java
index cbe6112..07fd282 100644
--- a/src/test/java/edu/brown/cs/student/HubRankTest.java
+++ b/src/test/java/edu/brown/cs/student/HubRankTest.java
@@ -24,6 +24,13 @@ public class HubRankTest {
private DatabaseQuerier db;
+ Holder don = new Holder(1, "Don");
+ Holder mitch = new Holder(2, "Mitch");
+ Holder nancy = new Holder(3, "Nancy");
+ Holder midge = new Holder(4, "Midge");
+ Holder bob = new Holder(5, "Bob");
+ Holder jane = new Holder(6, "Jane");
+
@Before
public void setUp() {
try{
@@ -41,7 +48,7 @@ public class HubRankTest {
@Test
public void testEmptyDB(){
try{
- db = new DatabaseQuerier("data/empty.sqlite3");
+ db = new DatabaseQuerier("data/testing/empty.sqlite3");
} catch(Exception e){
System.out.println("DBQuerier Test, couldn't connect to db???");
}
@@ -79,12 +86,6 @@ public class HubRankTest {
LinkMapper lm = new LinkMapper(db);
HubSearch hub = new HubSearch(lm);
Map<Holder, Double> hubRanks = hub.runHubSearch(start, end);
- Holder don = new Holder(1, "Don");
- Holder mitch = new Holder(2, "Mitch");
- Holder nancy = new Holder(3, "Nancy");
- Holder midge = new Holder(4, "Midge");
- Holder bob = new Holder(5, "Bob");
- Holder jane = new Holder(6, "Jane");
System.out.println(hubRanks);
assertTrue(hubRanks.get(mitch) > hubRanks.get(don));
@@ -95,4 +96,34 @@ public class HubRankTest {
tearDown();
}
+
+ @Test
+ public void testFollowersSetsConstructedProperly(){
+ setUp();
+ LinkMapper lm = new LinkMapper(db);
+ HubSearch hub = new HubSearch(lm);
+ Map<Holder, Double> hubRanks = hub.runHubSearch(start, end);
+
+ for(Holder person: hubRanks.keySet()){
+ System.out.println(person.getName() + " " + person.getFollowers());
+ if(person.equals(don)) {
+ assertTrue(person.getFollowers().contains(mitch));
+ assertTrue(person.getFollowers().contains(nancy));
+ assertTrue(person.getFollowers().contains(midge));
+ } else if(person.equals(bob)){
+ assertTrue(person.getFollowers().contains(mitch));
+ assertTrue(person.getFollowers().contains(midge));
+ } else if(person.equals(nancy)){
+ assertTrue(person.getFollowers().contains(midge));
+ } else if(person.equals(mitch)){
+ assertTrue(person.getFollowers().contains(nancy));
+ assertTrue(person.getFollowers().contains(midge));
+ assertTrue(person.getFollowers().contains(bob));
+ } else{
+ assertTrue(person.getFollowers().isEmpty());
+ }
+ }
+
+ tearDown();
+ }
}
diff --git a/src/test/java/edu/brown/cs/student/LinkMapperTest.java b/src/test/java/edu/brown/cs/student/LinkMapperTest.java
index 3d4bedc..643f63a 100644
--- a/src/test/java/edu/brown/cs/student/LinkMapperTest.java
+++ b/src/test/java/edu/brown/cs/student/LinkMapperTest.java
@@ -76,7 +76,7 @@ public class LinkMapperTest {
@Test
public void testEmptyDB(){
try{
- db = new DatabaseQuerier("data/empty.sqlite3");
+ db = new DatabaseQuerier("data/testing/empty.sqlite3");
} catch(Exception e){
System.out.println("DBQuerier Test, couldn't connect to db???");
}
diff --git a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
new file mode 100644
index 0000000..99500d0
--- /dev/null
+++ b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java
@@ -0,0 +1,56 @@
+package edu.brown.cs.student;
+
+import edu.brown.cs.student.term.DatabaseQuerier;
+import edu.brown.cs.student.term.hub.Holder;
+import edu.brown.cs.student.term.hub.HubSearch;
+import edu.brown.cs.student.term.hub.LinkMapper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ProfitCalculationTest {
+
+ /** these should span the entire mock dataset */
+ //12 am on 3/11 in UTC
+ private Instant start = Instant.parse("2021-03-11T05:00:00.00Z");
+ //12 am on 3/28 in UTC
+ private Instant end = Instant.parse("2021-03-28T05:00:00.00Z");
+
+ private DatabaseQuerier db;
+
+ @Before
+ public void setUp() {
+ try{
+ db = new DatabaseQuerier("data/lil_mock.sqlite3");
+ } catch(Exception e){
+ System.out.println("DBQuerier Test, couldn't connect to db???");
+ }
+ }
+
+ @After
+ public void tearDown() {
+ db = null;
+ }
+
+ @Test
+ public void testEmptyDB(){
+ try{
+ db = new DatabaseQuerier("data/empty.sqlite3");
+ } catch(Exception e){
+ System.out.println("DBQuerier Test, couldn't connect to db???");
+ }
+ LinkMapper lm = new LinkMapper(db);
+ HubSearch hub = new HubSearch(lm);
+ Map<Holder, Double> hubRanks = hub.runHubSearch(start, end);
+ assertTrue(hubRanks.isEmpty());
+ tearDown();
+ }
+
+
+} \ No newline at end of file
diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java
index d641507..3f8ea3d 100644
--- a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java
+++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java
@@ -10,24 +10,33 @@ import org.junit.Before;
import org.junit.Test;
import java.time.Instant;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class SuspicionRankerTest {
+ // ./cs32-test tests/student/hub/*.test
+
+ //friday april 9th
+ //private Instant start = Instant.ofEpochMilli(1618004180000L);
+
+ //saturday april 10th
+ //private Instant end = Instant.ofEpochMilli(1618019436000L);
+
//12 am on 3/11 in UTC
- //private Instant start = Instant.parse("2021-03-11T05:00:00.00Z");
- private Instant start = Instant.ofEpochMilli(161800418000L);
+ private Instant start = Instant.parse("2021-03-11T05:00:00.00Z");
//12 am on 3/28 in UTC
- //private Instant end = Instant.parse("2021-03-28T05:00:00.00Z");
- private Instant end = Instant.ofEpochMilli(1618019436000L);
+ private Instant end = Instant.parse("2021-03-28T05:00:00.00Z");
private DatabaseQuerier db;
@Before
public void setUp() {
try{
- db = new DatabaseQuerier("data/trades.sqlite3");
+ db = new DatabaseQuerier("data/lil_mock.sqlite3");
} catch(Exception e){
System.out.println("DBQuerier Test, couldn't connect to db???");
}
@@ -39,14 +48,40 @@ public class SuspicionRankerTest {
}
@Test
- public void testMapper(){
+ public void testSuspicionRanksOnMockData(){
setUp();
SuspicionRanker r = new SuspicionRanker(db);
List<Holder> him = r.getSuspicionScoreList(start, end);
- //System.out.println(him);
- for(Holder guy: him){
- System.out.println(guy.getName() + " " + guy.getSuspicionScore());
+ assertEquals(6, him.size());
+ for(Holder p: him){
+ System.out.println(p.toTestString());
+ }
+ tearDown();
+ }
+
+ @Test
+ public void testSusRankEmptyDB(){
+ try{
+ db = new DatabaseQuerier("data/testing/empty.sqlite3");
+ } catch(Exception e){
+ System.out.println("DBQuerier Test, couldn't connect to db???");
}
+ SuspicionRanker r = new SuspicionRanker(db);
+ List<Holder> vals = r.getSuspicionScoreList(start, end);
+ assertTrue(vals.isEmpty());
tearDown();
+
}
+
+ @Test
+ public void testBadDates(){
+ setUp();
+ SuspicionRanker r = new SuspicionRanker(db);
+ List<Holder> vals = r.getSuspicionScoreList(end, start);
+ assertTrue(vals.isEmpty());
+ tearDown();
+
+ }
+
+
}
diff --git a/tests/student/hub/data_not_loaded.test b/tests/student/hub/data_not_loaded.test
new file mode 100644
index 0000000..f89cb51
--- /dev/null
+++ b/tests/student/hub/data_not_loaded.test
@@ -0,0 +1,5 @@
+INPUT
+rank 1618004180000 1618019436000
+OUTPUT
+ERROR:
+END \ No newline at end of file
diff --git a/tests/student/hub/fake_command.test b/tests/student/hub/fake_command.test
new file mode 100644
index 0000000..95c3ee7
--- /dev/null
+++ b/tests/student/hub/fake_command.test
@@ -0,0 +1,5 @@
+INPUT
+fake 1 1
+OUTPUT
+ERROR:
+END \ No newline at end of file
diff --git a/tests/student/hub/load_data.test b/tests/student/hub/load_data.test
new file mode 100644
index 0000000..87b6220
--- /dev/null
+++ b/tests/student/hub/load_data.test
@@ -0,0 +1,5 @@
+INPUT
+setup data/trades.sqlite3
+OUTPUT
+Connected to db at data/trades.sqlite3
+END \ No newline at end of file
diff --git a/tests/student/hub/no_trades_for_dates.test b/tests/student/hub/no_trades_for_dates.test
new file mode 100644
index 0000000..dc142ed
--- /dev/null
+++ b/tests/student/hub/no_trades_for_dates.test
@@ -0,0 +1,6 @@
+INPUT
+setup data/lil_mock.sqlite3
+rank 161462819800 161696099800
+OUTPUT
+Connected to db at data/lil_mock.sqlite3
+END \ No newline at end of file
diff --git a/tests/student/hub/rank_lil_mock.test b/tests/student/hub/rank_lil_mock.test
new file mode 100644
index 0000000..6af2a7e
--- /dev/null
+++ b/tests/student/hub/rank_lil_mock.test
@@ -0,0 +1,12 @@
+INPUT
+setup data/lil_mock.sqlite3
+rank 1614628198000 1616960998000
+OUTPUT
+Connected to db at data/lil_mock.sqlite3
+Mitch
+Bob
+Don
+Jane
+Nancy
+Midge
+END \ No newline at end of file
diff --git a/tests/student/hub/too_many_arguments_setup.test b/tests/student/hub/too_many_arguments_setup.test
new file mode 100644
index 0000000..1ec48fe
--- /dev/null
+++ b/tests/student/hub/too_many_arguments_setup.test
@@ -0,0 +1,5 @@
+INPUT
+setup data/trades.sqlite3 more
+OUTPUT
+ERROR:
+END \ No newline at end of file