Skip to content

Commit

Permalink
Catch api errors #24 and use api key input form #23
Browse files Browse the repository at this point in the history
  • Loading branch information
ogallagher committed Aug 28, 2021
1 parent c1a824d commit c1f41cd
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 26 deletions.
125 changes: 122 additions & 3 deletions src/ogallagher/marketsense/MarketSense.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.EntityManager;
Expand All @@ -31,6 +32,7 @@
import com.fxgraph.graph.PannableCanvas;

import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
Expand All @@ -46,6 +48,7 @@
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
Expand All @@ -63,11 +66,14 @@
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Callback;

import ogallagher.twelvedata_client_java.TwelvedataClient;
import ogallagher.twelvedata_client_java.TwelvedataInterface.BarInterval;
import ogallagher.twelvedata_client_java.TwelvedataInterface.Failure;
import ogallagher.twelvedata_client_java.TwelvedataInterface.SecuritySet;
import ogallagher.marketsense.persistent.Person;
import ogallagher.marketsense.persistent.Security;
Expand Down Expand Up @@ -325,6 +331,11 @@ public static class MarketSenseGUI extends Application {
private static int MAIN_WINDOW_WIDTH_INIT = 600;
private static int MAIN_WINDOW_HEIGHT_INIT = 500;

/**
* Provide static access to the {@code HostServices} instance of the latest {@code MarketSenseGUI} launched.
*/
private static HostServices hostServices;

public static void main(String[] args) {
launch(args);
}
Expand All @@ -349,6 +360,17 @@ public void start(Stage primaryStage) throws Exception {
);
mainWindow.setScene(mainScene);

// end program on main window close
mainWindow.setOnHidden(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
Platform.exit();
}
});

// host services
hostServices = getHostServices();

// connect db entity manager
dbManager = Persistence
.createEntityManagerFactory(properties.getProperty(PROP_PERSIST_UNIT))
Expand All @@ -363,6 +385,11 @@ public void start(Stage primaryStage) throws Exception {
loadPeople(ShowLogin.class, true);
}

@Override
public void stop() {
// TODO handle program exit
}

/**
* Should be run on javafx thread.
*
Expand Down Expand Up @@ -428,7 +455,7 @@ public void handle(MouseEvent event) {
});
}
catch (IOException e) {
System.out.println("error showing login: " + e.getMessage());
System.out.println("ERROR showing login: " + e.getMessage());
}
}
}
Expand Down Expand Up @@ -973,6 +1000,92 @@ public List<Point2D> call() {
return graph;
}
}

public static class ShowApiKeyForm implements Runnable {
private static final String WINDOW_TITLE = "API Key Form";
private static final int WINDOW_WIDTH = 500;
private static final int WINDOW_HEIGHT = 240;

private Stage apiKeyFormWindow;
private String keyOld;

public ShowApiKeyForm(String keyOld) {
apiKeyFormWindow = new Stage();
apiKeyFormWindow.setTitle(WINDOW_TITLE);
apiKeyFormWindow.setWidth(WINDOW_WIDTH);
apiKeyFormWindow.setHeight(WINDOW_HEIGHT);

this.keyOld = keyOld;
}

@Override
public void run() {
try {
Parent root = (Parent) FXMLLoader.load(MarketSense.class.getResource("resources/APIKeyForm.fxml"));
Scene windowScene = new Scene(root);

apiKeyFormWindow.setScene(windowScene);

enableHyperlinks(root);

// show old key
((Text) root.lookup("#apiKeyOld")).setText(keyOld);

// handle new key
TextField keyField = (TextField) root.lookup("#apiKeyNew");
keyField.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER)) {
String keyNew = keyField.getText();

if (keyNew.length() != 0) {
// set api key of twelvedata client
tdclient.setKey(keyNew);
System.out.println("INFO set api key to " + keyNew);

// close window
apiKeyFormWindow.close();
}
else {
System.out.println("ERROR api key not given");
keyField.setText("");
keyField.setPromptText("blank or invalid key given");
}
}
}
});

apiKeyFormWindow.show();
}
catch (IOException e) {
System.out.println("ERROR showing api key form window: " + e.getMessage());
e.printStackTrace();
}
}

/**
* Enable hyperlinks in the given gui fragment, given that they store their urls in the
* {@link Hyperlink#tooltipProperty() tooltip}.
*
* @param root The gui fragment root node.
*/
private void enableHyperlinks(Node root) {
Set<Node> hyperlinks = root.lookupAll("Hyperlink");
System.out.println("DEBUG found " + hyperlinks.size() + " hyperlinks");

for (Node hln : hyperlinks) {
Hyperlink hl = (Hyperlink) hln;
hl.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
// assumes href is in tooltip
hostServices.showDocument(hl.getTooltip().getText());
}
});
}
}
}
}

/**
Expand Down Expand Up @@ -1201,14 +1314,20 @@ public static boolean newTrainingSession(String symbol, String barWidth, int sam
System.out.println("starting a new training session " + session);

// prepare the database
if (session.collectMarketUniverse(dbManager,tdclient)) {
Failure failure = session.collectMarketUniverse(dbManager,tdclient);

if (failure == null) {
System.out.println("market data universe acquired for lookback of " + session.getMaxLookbackMonths() + " months");

// show training session interface
Platform.runLater(new MarketSenseGUI.ShowTrainingSession(session));
}
else if (failure.code == Failure.ErrorCode.API_KEY) {
// show api key input form
Platform.runLater(new MarketSenseGUI.ShowApiKeyForm(tdclient.getKey()));
}
else {
System.out.println("ERROR failed to creake market data universe for training session");
System.out.println("ERROR failed to creake market data universe for training session: " + failure.message);
}

return true;
Expand Down
75 changes: 53 additions & 22 deletions src/ogallagher/marketsense/persistent/TrainingSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import ogallagher.marketsense.MarketSynth;
import ogallagher.twelvedata_client_java.TwelvedataClient;
import ogallagher.twelvedata_client_java.TwelvedataInterface.BarInterval;
import ogallagher.twelvedata_client_java.TwelvedataInterface.Failure;
import ogallagher.twelvedata_client_java.TwelvedataInterface.TimeSeries;

/**
Expand Down Expand Up @@ -206,14 +207,15 @@ public MarketSample nextSample(EntityManager dbManager, MarketSynth marketSynth)
* so there are never any holes between the start and end datetimes.
*
* Note that ideal universe bounds won't necessarily match valid market calendars and market hours, so in cases where this is
* expected, the universe bounds {@link after} .. {@link before} will be updated to match what the database does have.
* expected, the universe bounds {@link #after} .. {@link #before} will be updated to match what the database does have.
*
* @return {@code true} if the needed market data is now in the database.
* @return The failure, or {@code null} if the needed market data is now in the database.
*/
public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient marketClient) {
boolean result = true,
firstUp = false,
lastDown = false;
public Failure collectMarketUniverse(EntityManager dbManager, TwelvedataClient marketClient) {
Failure result = null;

boolean firstUp = false;
boolean lastDown = false;

TradeBar first = new TradeBar(security, after, barWidth);
TradeBar last = new TradeBar(security, BarInterval.offsetBars(before, barWidth, sampleSize), barWidth);
Expand All @@ -222,7 +224,7 @@ public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient m
TradeBar preLast = null, postFirst = null;

dbManager.getTransaction().begin();
if (!dbManager.contains(first)) {
if (result == null && !dbManager.contains(first)) {
// move forward to find earliest bar after first
String qstr = String.format(
"select t from %5$s t " +
Expand Down Expand Up @@ -259,7 +261,7 @@ public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient m
security.getSymbol(), barWidth,
first.getDatetime(), BarInterval.offsetBars(preLast.getDatetime(), barWidth, -1)
);
if (timeSeries != null) {
if (!timeSeries.isFailure()) {
// convert to db-compat trade bars and persist
List<TradeBar> bars = TradeBar.convertTimeSeries(timeSeries, Comparator.naturalOrder());

Expand All @@ -269,15 +271,29 @@ public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient m
System.out.println("persisted " + bars.size() + " new bars");
}
else {
System.out.println("WARNING failed to fetch first-prelast for universe, perhaps no bars exist");
// update first to be preLast
firstUp = true;
Failure f = (Failure) timeSeries;
switch (f.code) {
case Failure.ErrorCode.API_KEY:
case Failure.ErrorCode.CALL_LIMIT:
case Failure.ErrorCode.NO_COMMS:
case Failure.ErrorCode.NULL_RESPONSE:
result = f;
break;

default:
System.out.println(
"WARNING failed to fetch first-prelast for universe, perhaps no bars exist: " + f.toString()
);
// update first to be preLast
firstUp = true;
break;
}
}
}
dbManager.getTransaction().commit();

dbManager.getTransaction().begin();
if (!dbManager.contains(last)) {
if (result == null && !dbManager.contains(last)) {
// move backward to find latest bar before last
Query query = dbManager.createQuery(
String.format(
Expand Down Expand Up @@ -314,7 +330,7 @@ public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient m
security.getSymbol(), barWidth,
BarInterval.offsetBars(postFirst.getDatetime(), barWidth, 1), last.getDatetime()
);
if (timeSeries != null) {
if (!timeSeries.isFailure()) {
// convert to db-compat trade bars and persist
List<TradeBar> bars = TradeBar.convertTimeSeries(timeSeries, Comparator.naturalOrder());

Expand All @@ -324,20 +340,35 @@ public boolean collectMarketUniverse(EntityManager dbManager, TwelvedataClient m
System.out.println("persisted " + bars.size() + " new bars");
}
else {
System.out.println("WARNING failed to fetch postfirst-last for universe, perhaps no bars exist");
// update last to be postFirst
lastDown = true;
Failure f = (Failure) timeSeries;
switch (f.code) {
case Failure.ErrorCode.API_KEY:
case Failure.ErrorCode.CALL_LIMIT:
case Failure.ErrorCode.NO_COMMS:
case Failure.ErrorCode.NULL_RESPONSE:
result = f;
break;

default:
System.out.println("WARNING failed to fetch postfirst-last for universe, perhaps no bars exist: " + f);
// update last to be postFirst
lastDown = true;
break;
}

}
}
dbManager.getTransaction().commit();

if (firstUp) {
after = preLast.getDatetime();
}
if (lastDown) {
before = BarInterval.offsetBars(postFirst.getDatetime(), barWidth, -sampleSize);
if (result == null) {
if (firstUp) {
after = preLast.getDatetime();
}
if (lastDown) {
before = BarInterval.offsetBars(postFirst.getDatetime(), barWidth, -sampleSize);
}
System.out.println("DEBUG universe trimmed to " + first.getDatetime() + " to " + last.getDatetime());
}
System.out.println("DEBUG universe trimmed to " + first.getDatetime() + " to " + last.getDatetime());

return result;
}
Expand Down
40 changes: 40 additions & 0 deletions src/ogallagher/marketsense/resources/APIKeyForm.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Owen Gallagher
2021-08-28
Market data API Key input form.
*/

.root {
-fx-font-size: 14px;
-fx-font-family: sans-serif;
-fx-background-color: #fff;
-fx-padding: 4 4 4 4;
}

.h1 {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-padding: 2 0 8 0;
}

.h2 {
-fx-font-size: 20px;
-fx-padding: 2 0 4 0;
}

.h3 {
-fx-font-size: 16px;
-fx-padding: 2 0 4 0;
}

.paragraph {
-fx-padding: 4px 0px 4px 0px;
-fx-text-alignment: left;
}

.code {
-fx-font-family: "monospace";
}

0 comments on commit c1f41cd

Please sign in to comment.