-
Notifications
You must be signed in to change notification settings - Fork 287
Feature/ohlc download #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MarcDahlem
wants to merge
2
commits into
gazbert:main
Choose a base branch
from
MarcDahlem:feature/ohlc-download
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,12 +35,17 @@ | |
import com.gazbert.bxbot.exchanges.trading.api.impl.BalanceInfoImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.MarketOrderBookImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.MarketOrderImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.OhlcFrameImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.OhlcImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.OpenOrderImpl; | ||
import com.gazbert.bxbot.exchanges.trading.api.impl.TickerImpl; | ||
import com.gazbert.bxbot.trading.api.BalanceInfo; | ||
import com.gazbert.bxbot.trading.api.ExchangeNetworkException; | ||
import com.gazbert.bxbot.trading.api.MarketOrder; | ||
import com.gazbert.bxbot.trading.api.MarketOrderBook; | ||
import com.gazbert.bxbot.trading.api.Ohlc; | ||
import com.gazbert.bxbot.trading.api.OhlcFrame; | ||
import com.gazbert.bxbot.trading.api.OhlcInterval; | ||
import com.gazbert.bxbot.trading.api.OpenOrder; | ||
import com.gazbert.bxbot.trading.api.OrderType; | ||
import com.gazbert.bxbot.trading.api.Ticker; | ||
|
@@ -49,12 +54,14 @@ | |
import com.google.common.base.MoreObjects; | ||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.JsonArray; | ||
import com.google.gson.JsonDeserializationContext; | ||
import com.google.gson.JsonDeserializer; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.annotations.SerializedName; | ||
import com.google.gson.reflect.TypeToken; | ||
import java.io.Serializable; | ||
import java.lang.reflect.Type; | ||
import java.math.BigDecimal; | ||
import java.math.RoundingMode; | ||
|
@@ -68,6 +75,9 @@ | |
import java.security.MessageDigest; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.text.DecimalFormat; | ||
import java.time.Instant; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.ArrayList; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
|
@@ -157,6 +167,8 @@ public final class KrakenExchangeAdapter extends AbstractExchangeAdapter | |
"Failed to get Balance from exchange. Details: "; | ||
private static final String FAILED_TO_GET_TICKER = | ||
"Failed to get Ticker from exchange. Details: "; | ||
private static final String FAILED_TO_GET_OHLC = | ||
"Failed to get OHLC Data from exchange. Details: "; | ||
|
||
private static final String FAILED_TO_GET_OPEN_ORDERS = | ||
"Failed to get Open Orders from exchange. Details: "; | ||
|
@@ -599,6 +611,92 @@ public Ticker getTicker(String marketId) throws TradingApiException, ExchangeNet | |
} | ||
} | ||
|
||
@Override | ||
public Ohlc getOhlc(String marketId, OhlcInterval interval) | ||
throws TradingApiException, ExchangeNetworkException { | ||
return getOhlc(marketId, interval, null); | ||
} | ||
|
||
@Override | ||
public Ohlc getOhlc(String marketId, OhlcInterval interval, Integer resumeID) | ||
throws TradingApiException, ExchangeNetworkException { | ||
ExchangeHttpResponse response; | ||
String intervalInMinutes = calculateMinuteParamFrom(interval); | ||
|
||
try { | ||
final Map<String, String> params = createRequestParamMap(); | ||
params.put("pair", marketId); | ||
params.put("interval", intervalInMinutes); | ||
if (resumeID != null) { | ||
params.put("since", String.valueOf(resumeID)); | ||
} | ||
|
||
response = sendPublicRequestToExchange("OHLC", params); | ||
LOG.debug(() -> "OHLC response: " + response); | ||
|
||
if (response.getStatusCode() == HttpURLConnection.HTTP_OK) { | ||
|
||
final Type resultType = new TypeToken<KrakenResponse<KrakenOhlcResult>>() {}.getType(); | ||
final KrakenResponse<KrakenOhlcResult> krakenResponse = | ||
gson.fromJson(response.getPayload(), resultType); | ||
|
||
final List errors = krakenResponse.error; | ||
if (errors == null || errors.isEmpty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// Assume we'll always get something here if errors array is empty; else blow fast wih NPE | ||
return krakenResponse.result.parseOhlcResult(); | ||
|
||
} else { | ||
if (isExchangeUndergoingMaintenance(response) && keepAliveDuringMaintenance) { | ||
LOG.warn(() -> UNDER_MAINTENANCE_WARNING_MESSAGE); | ||
throw new ExchangeNetworkException(UNDER_MAINTENANCE_WARNING_MESSAGE); | ||
} | ||
|
||
final String errorMsg = FAILED_TO_GET_OHLC + response; | ||
LOG.error(errorMsg); | ||
throw new TradingApiException(errorMsg); | ||
} | ||
|
||
} else { | ||
final String errorMsg = FAILED_TO_GET_OHLC + response; | ||
LOG.error(errorMsg); | ||
throw new TradingApiException(errorMsg); | ||
} | ||
|
||
} catch (ExchangeNetworkException | TradingApiException e) { | ||
throw e; | ||
|
||
} catch (Exception e) { | ||
LOG.error(UNEXPECTED_ERROR_MSG, e); | ||
throw new TradingApiException(UNEXPECTED_ERROR_MSG, e); | ||
} | ||
} | ||
|
||
private String calculateMinuteParamFrom(OhlcInterval interval) { | ||
switch (interval) { | ||
case OneMinute: | ||
return "1"; | ||
case FiveMinutes: | ||
return "5"; | ||
case FifteenMinutes: | ||
return "15"; | ||
case HalfHour: | ||
return "30"; | ||
case OneHour: | ||
return "60"; | ||
case FourHours: | ||
return "240"; | ||
case OneDay: | ||
return "1440"; | ||
case OneWeek: | ||
return "10080"; | ||
case FifteenDays: | ||
return "21600"; | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
// GSON classes for JSON responses. | ||
// See https://www.kraken.com/en-gb/help/api | ||
|
@@ -812,6 +910,46 @@ private static class KrakenMarketOrder extends ArrayList<BigDecimal> { | |
private static final long serialVersionUID = -4959711260742077759L; | ||
} | ||
|
||
private static class KrakenOhlcResult extends HashMap<String, Object> implements Serializable { | ||
private static final long serialVersionUID = 5632663477504483978L; | ||
|
||
Ohlc parseOhlcResult() { | ||
Gson gson = new Gson(); | ||
List<OhlcFrame> frames = new ArrayList<>(); | ||
Integer last = null; | ||
if (this.entrySet().size() != 2) { | ||
throw new IllegalStateException( | ||
"Unknown OHLC result from the API. Maybe the api changed?" | ||
+ "Known are currently only 'last' and the required frames in '<pair>'"); | ||
} | ||
for (Entry<String, Object> entry : this.entrySet()) { | ||
if (entry.getKey().equalsIgnoreCase("last")) { | ||
JsonElement resumeId = gson.toJsonTree(entry).getAsJsonObject().get("value"); | ||
last = resumeId.getAsInt(); | ||
} else { | ||
JsonElement jsonElement = gson.toJsonTree(entry); | ||
for (JsonElement frame : jsonElement.getAsJsonObject().get("value").getAsJsonArray()) { | ||
if (frame.isJsonArray()) { | ||
JsonArray frameAsArray = frame.getAsJsonArray(); | ||
Instant timeInstant = Instant.ofEpochSecond(frameAsArray.get(0).getAsInt()); | ||
ZonedDateTime time = ZonedDateTime.ofInstant(timeInstant, ZoneId.systemDefault()); | ||
BigDecimal open = frameAsArray.get(1).getAsBigDecimal(); | ||
BigDecimal high = frameAsArray.get(2).getAsBigDecimal(); | ||
BigDecimal low = frameAsArray.get(3).getAsBigDecimal(); | ||
BigDecimal close = frameAsArray.get(4).getAsBigDecimal(); | ||
BigDecimal vwap = frameAsArray.get(5).getAsBigDecimal(); | ||
BigDecimal volume = frameAsArray.get(6).getAsBigDecimal(); | ||
Integer count = frameAsArray.get(7).getAsInt(); | ||
|
||
frames.add(new OhlcFrameImpl(time, open, high, low, close, vwap, volume, count)); | ||
} | ||
} | ||
} | ||
} | ||
return new OhlcImpl(last, frames); | ||
} | ||
} | ||
|
||
/** | ||
* Custom GSON Deserializer for Ticker API call result. | ||
* | ||
|
@@ -1091,8 +1229,8 @@ private void loadPairPrecisionConfig() { | |
|
||
if (response.getStatusCode() == HttpURLConnection.HTTP_OK) { | ||
Type type = new TypeToken<KrakenResponse<KrakenAssetPairsConfig>>() {}.getType(); | ||
KrakenResponse<KrakenAssetPairsConfig> krakenResponse = gson.fromJson( | ||
response.getPayload(), type); | ||
KrakenResponse<KrakenAssetPairsConfig> krakenResponse = | ||
gson.fromJson(response.getPayload(), type); | ||
|
||
if (krakenResponse.error != null && !krakenResponse.error.isEmpty()) { | ||
LOG.error( | ||
|
102 changes: 102 additions & 0 deletions
102
...t-exchanges/src/main/java/com/gazbert/bxbot/exchanges/trading/api/impl/OhlcFrameImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package com.gazbert.bxbot.exchanges.trading.api.impl; | ||
|
||
import com.gazbert.bxbot.trading.api.OhlcFrame; | ||
import com.google.common.base.MoreObjects; | ||
import java.math.BigDecimal; | ||
import java.time.ZonedDateTime; | ||
|
||
public class OhlcFrameImpl implements OhlcFrame { | ||
private final ZonedDateTime time; | ||
private final BigDecimal open; | ||
private final BigDecimal high; | ||
private final BigDecimal low; | ||
private final BigDecimal close; | ||
private final BigDecimal vwap; | ||
private final BigDecimal volume; | ||
private final Integer count; | ||
|
||
/** | ||
* Class representing a frame of the OHLC download. | ||
* | ||
* @param time the starttime of the corresponding ohlc frame | ||
* @param open the open price of the corresponding ohl frame | ||
* @param high the high price of the corresponding ohl frame | ||
* @param low the low price of the corresponding ohl frame | ||
* @param close the close price of the corresponding ohl frame | ||
* @param vwap the volume-weighted avaerage price of the corresponding ohl frame | ||
* @param volume the volume traded in the corresponding ohl frame | ||
* @param count the trades cound of the corresponding ohl frame | ||
*/ | ||
public OhlcFrameImpl( | ||
ZonedDateTime time, | ||
BigDecimal open, | ||
BigDecimal high, | ||
BigDecimal low, | ||
BigDecimal close, | ||
BigDecimal vwap, | ||
BigDecimal volume, | ||
Integer count) { | ||
this.time = time; | ||
this.open = open; | ||
this.high = high; | ||
this.low = low; | ||
this.close = close; | ||
this.vwap = vwap; | ||
this.volume = volume; | ||
this.count = count; | ||
} | ||
|
||
@Override | ||
public ZonedDateTime getTime() { | ||
return time; | ||
} | ||
|
||
@Override | ||
public BigDecimal getOpen() { | ||
return open; | ||
} | ||
|
||
@Override | ||
public BigDecimal getHigh() { | ||
return high; | ||
} | ||
|
||
@Override | ||
public BigDecimal getLow() { | ||
return low; | ||
} | ||
|
||
@Override | ||
public BigDecimal getClose() { | ||
return close; | ||
} | ||
|
||
@Override | ||
public BigDecimal getVwap() { | ||
return vwap; | ||
} | ||
|
||
@Override | ||
public BigDecimal getVolume() { | ||
return volume; | ||
} | ||
|
||
@Override | ||
public Integer getCount() { | ||
return count; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return MoreObjects.toStringHelper(this) | ||
.add("time", time) | ||
.add("open", open) | ||
.add("high", high) | ||
.add("low", low) | ||
.add("close", close) | ||
.add("vwap", vwap) | ||
.add("volume", volume) | ||
.add("count", count) | ||
.toString(); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
bxbot-exchanges/src/main/java/com/gazbert/bxbot/exchanges/trading/api/impl/OhlcImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.gazbert.bxbot.exchanges.trading.api.impl; | ||
|
||
import com.gazbert.bxbot.trading.api.Ohlc; | ||
import com.gazbert.bxbot.trading.api.OhlcFrame; | ||
import java.util.List; | ||
|
||
public class OhlcImpl implements Ohlc { | ||
private final Integer resumeID; | ||
private final List<OhlcFrame> frames; | ||
|
||
/** | ||
* This class represents the OHLC result. It contains all frames and an ID from which subsequent | ||
* calls can resume fetching OHLC data | ||
* | ||
* @param resumeID the ID which can be used to resume OHLC download by ignoring all order results | ||
* @param frames the OHLC frames in the requested packaging interval | ||
*/ | ||
public OhlcImpl(Integer resumeID, List<OhlcFrame> frames) { | ||
this.resumeID = resumeID; | ||
this.frames = frames; | ||
} | ||
|
||
@Override | ||
public Integer getResumeID() { | ||
return resumeID; | ||
} | ||
|
||
@Override | ||
public List<OhlcFrame> getFrames() { | ||
return frames; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Objects.nonNull(resumeID)
?