diff options
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 Binary files differindex 3d10907..e69de29 100644 --- a/data/empty.sqlite3 +++ b/data/empty.sqlite3 diff --git a/data/testing/empty.sqlite3 b/data/testing/empty.sqlite3 Binary files differnew file mode 100644 index 0000000..3d10907 --- /dev/null +++ b/data/testing/empty.sqlite3 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 |