Kraken Asset Names

xcvi

Mitglied
Hallo, die sind hier spezifiziert: https://support.kraken.com/hc/en-us/articles/360001185506-How-to-interpret-asset-codes

Code:
Java:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

public class KrakenNames {
    private ArrayList<String> pairs;

    public KrakenNames(MyAccess access) {
        pairs = new MyKraken(access.api).getAssetPairs();
        System.out.println(pairs.size() + " pairs.");
    }

    private List<String> getPairs(String pair) {
        if (pair.startsWith("X")) {
            return pairs.stream().filter(s -> s.startsWith(pair.substring(1, 4))).collect(Collectors.toList());
        }
        return pairs.stream().filter(s -> s.startsWith(pair.substring(0, 3))).collect(Collectors.toList());
    }

    public String[] resolve(String pair, String target) {
        List<String> pairs2 = getPairs(pair);
        String p = pairs2.stream().filter(s -> s.endsWith(target)).findFirst().orElse(null);
        if (p == null) {
            return null;
        }
        if (target.equals("USD")) {
            if (p.startsWith("X")) {
                return new String[] { p, StringUtils.left(p, 4), p.substring(1, 4) };
            } else {
                return new String[] { p, StringUtils.left(p, 3), StringUtils.left(p, 3) };
            }
        } else {
            if (p.startsWith("X")) {
                return new String[] { p, StringUtils.left(p, 4), p.substring(1, 4) };
            } else {
                return new String[] { p, StringUtils.left(p, 3), StringUtils.left(p, 3) };
            }
        }
    }
}

Resolve funktioniert nicht. Könnte mir sagen, wo mein Denkfehler ist?

resolve soll ein beliebiger Asset Name und das Traget Asset übergeben werden. Dann soll das richtige Asset Pair returned werden...
 

mrBrown

Super-Moderator
Mitarbeiter
Deine Beschreibung ist auf der Ebene von "Es wird etwas gemacht und dann soll das richtige passieren".

Für eine sinnvolle Antwort solltest du schon vernünftig formulieren, was genau da passieren soll, dafür wäre zB eine Erklärung nötig, was pair und target sind und was dann das "richtige Asset Pair" ist.
 
K

kneitzel

Gast
Also bezüglich des Codes einfach noch ein paar Anmerkungen:

- Die Bezeichner finde ich relativ "strange" pair / pairs da erwarte ich irgend eine Datenstruktur mit einem Paar oder so. Aber das ist einfach nur ein String ... Da wären zu, einen Informationen gut, was da überhaupt gespeichert ist. Auf Grund des Codes vermutlich irgend welche Daten, die einfach in einem String zusammen gefasst wurden, was i.d.R. zu komplexerem Code führt.
- Dein if (target.equals("USD")) kannst Du vergessen - in beiden Zweigen machst Du genau das Gleiche.

Generell würde ich da ein Refactoring machen um den Code lesbarer zu machen. Das umschließt neben einer Anpassung diverser Bezeichner auch einer weitere Unterteilung in Methoden. Das was Du da innerhalb des oben genannten ifs machst wäre z.B. in einer Methode gut untergebracht. Dann wäre die ganze Methode nur noch ein einfacher Stream (der halt nach dem .filter noch ein .map bekommen würde...)
Ähnliches gilt für getPairs. Da hast Du unter dem Strich fast zwei Mal das Gleiche. Das kann man aus meiner Sicht so auch deutlich lesbarer gestalten. (Und dann lassen sich einzelne Teile auch gut per Unit Test testen. Das gibt einem dann etwas mehr Sicherheit, dass eine Methode eben genau das macht, was sie soll!)

Das konnten so aber nur ganz oberflächliche Informationen sein, da einfach zu viel der von Dir festgelegten Details uns unbekannt sind (Und Du mit Details in keiner Weise hilfst). Aber die allgemeinen Hinweise helfen Dir evtl., da besser voran zu kommen und selbst mögliche Fehler zu finden.
 

xcvi

Mitglied
Hab es hinbekommen:
Java:
import java.util.ArrayList;
import org.apache.commons.lang3.StringUtils;

public class KrakenNames {
    private ArrayList<String> pairs;

    public KrakenNames(MyAccess access) {
        pairs = new MyKraken(access.api).getAssetPairs();
        System.out.println(pairs.size() + " pairs.");
    }

    public String[] resolve(String pair, String target) {
        if (pair.startsWith("X")) {
            return pairs.stream()
                    .filter(s -> s.startsWith(pair))
                    .filter(s -> s.endsWith(target)).findFirst()
                    .map(p -> new String[] { p, StringUtils.left(p, p.length() - target.length() - 1), p })
                    .orElse(null);
        }
        return pairs.stream()
                .filter(s -> s.startsWith(pair))
                .filter(s -> s.endsWith(target)).findFirst()
                .map(p -> new String[] { p, StringUtils.left(p, p.length() - target.length()), p })
                .orElse(null);
    }
}

Fragt mich bitte nicht, warum das bei Kraken so seltsam ist...

edit: @kneitzel ich kann doch nichts dafür, wenn dir die Begriffe asset und asset pair kein Begriff sind... Nachhilfe wollte ich eigentlich nicht geben. Ich denke, du bist in der Lage, dich selber zu informieren.
 
K

kneitzel

Gast
Der Kernpunkt ist einfach, dass ein Asset Pair ganz offensichtlich eher nicht ein einfacher String ist. Das mag zwar als JSON in einem String gekommen sein, aber die Daten sind doch eher kein String. Wenn ich mir z.B. https://support.kraken.com/hc/en-us/articles/360000920306-Ticker-pairs ansehe, dann sehe ich da deutlich mehr...

Und alleine schon die Tatsache, dass Du da Teile des Strings auswertest und so deutet auch etwas darauf hin, dass da wohl mehr in einem String drin steckt als einfach nur ein String.

Mir ist das aber relativ egal - Mach Du weiter den Murks, den Du machen willst. Mir ist es egal, ob Du ein sauberes Model hast oder nicht :)
 

xcvi

Mitglied
Ein pair setzt sich einfach aus zwei assets zusammen. Dabei ist Währung auch ein asset. Beginnt ein asset jedoch mit einem X, und ist das zweite asset USD oder eine andere Währung, so wird USD ein Z vorangestellt.

Beispiel Bitcoin:
Basis: XBT
erweiterte Basis: XXBT
Bitcoin nach USD: XXBTZUSD

Gesucht ist hier sowohl XXBTZUSD wie XXBT als aus XBT.

Aber warum erkläre ich dir das überhaupt? Um mich anpöbeln zu lassen? Nein Danke. 😄
 

mihe7

Top Contributor
Ein pair setzt sich einfach aus zwei assets zusammen. Dabei ist Währung auch ein asset. Beginnt ein asset jedoch mit einem X, und ist das zweite asset USD oder eine andere Währung, so wird USD ein Z vorangestellt.
D. h Eintrag in pairs kann entweder pair+target oder "X"+pair+"Z"+target sein? Dann reicht es doch, die Liste auf die Existenz dieser beiden Einträge hin zu prüfen.
 
K

kneitzel

Gast
Um mich anpöbeln zu lassen?
Wo bitte habe ich Dich angepöbelt? Das ist in beiden Threads nur als Reaktion auf Dein Verhalten erfolgt. Hier habe ich lediglich geschrieben, was mir aufgefallen ist. Ob die Punkte gerechtfertigt sind oder nicht kann man ja schauen. In diesem Fall heisst pairs einfach, dass da zwei Asset Codes zusammen gesetzt als eine Art Bezeichner verwendet werden. Das erklärt die List<String> und hätte so gesagt werden können und alles wäre sofort gegessen gewesen.

Die Informationen, die da jetzt gekommen sind, wären übrigens super gewesen auf #2 - dann hätte man sich da weiter drauf konzentrieren können.

Und dann einfach weiter genau diese "pairs" betrachtet: Wäre es ggf. sinnvoll, da nicht von Hand irgendwelche Trennungen durchzuführen, sondern sich dies direkt zu holen? Denn die ursprünglichen Regeln scheinen ja nicht konsequent verwendet worden zu sein. ("Asset codes starting with 'X' represent cryptocurrencies, though this convention is no longer followed for newer coin listings." - https://support.kraken.com/hc/en-us/articles/360001185506-How-to-interpret-asset-codes)

Daher wäre halt mein Ansatz (den Du Dir überlegen kannst, du kannst es verwenden aber auch einfach als für Deinen Zweck untauglich):
https://api.kraken.com/0/public/AssetPairs auslesen und dann auswerten.

Für XXBTZUSD bekommst Du dann halt einiges mehr an Informationen:
"altname":"XBTUSD",
"wsname":"XBT\/USD",
"aclass_base":"currency",
"base":"XXBT",
"aclass_quote":"currency"
,"quote":"ZUSD",
...

Da wirst Du dann ggf. deutlich besser filtern können, denn Du kannst dann ja gezielt nach quote oder base filtern. Und da ist dann Bitcoin z.B. immer sauber als XXBT geführt.

Aber ich habe die genaue Aufteilung in das String Array nicht ganz verstanden, daher kann ich auch nicht bewerten, ob alle Informationen, die Du da bereit stellen willst, hier wirklich vorhanden sein. (auf index 0 und 2 ist der gleiche Wert - der ursprüngliche Bezeichner ...).

Und nur um es noch einmal ganz deutlich zu sagen:
Diese Ideen / Anregungen / Hinweise haben doch absolut nichts mit pöbeln zu tun. Daher solltest Du dein Verhalten entsprechend anpassen. Wenn Antworten nicht das enthalten, was Du erwartest, dann solltest Du da nicht anfangen, diejenigen anzugehen, die Dir unter dem Strich nur helfen wollen. Im anderen Thread fand ich das besonders unangemessen, denn auf eine einfache klare Frage hast Du eine klare, eindeutige Antwort bekommen und darauf dann ein total unangemessenes Verhalten, abtun einer Antwort als Spaßantwort u.s.w.
Das will ich nicht ausdiskutieren. Du kannst Das sehen, wie Du willst. Du kannst das auch handhaben, wie Du willst. Wie Du erkennen kannst, mache ich mir weiter meine Gedanken und teile diese in der Hoffnung, dass diese hilfreich sind. Wenn Du in sowas ein "pöbeln" sehen willst, dann ist Dir das durchaus freigestellt, aber dies ist ein ganz klares Missverständnis. (Ein Pöbeln wie "Mach Du weiter den Murks, den Du machen willst." kommt nur auf unangebrachtes pöbeln von einer Seite - vor allem von Unterstellungen wie "ich kann doch nichts dafür, wenn dir die Begriffe asset und asset pair kein Begriff sind..." -> Denn wie Du hoffentlich unklar erkennen kannst habe ich mir durchaus gewisse Dinge angesehen - und das, obwohl mich kraken in absolut keiner Weise interessiert. Wenn ich pöbeln wollte, müsste ich mir so Seiten nicht erst ansehen :)
 

xcvi

Mitglied
Danke für den Hinweis. Hab es mal gerade umgeschrieben (die Rückgabe meiner MyKraken API angepasst):
Java:
import org.json.JSONObject;

public class KrakenNames {
    private JSONObject map;

    public KrakenNames(MyAccess access) {
        map = new MyKraken(access.api).getAssetPairs();
        System.out.println(map.length() + " pairs.");
    }

    public String[] resolve(String base, String target) {
        return map.toMap().entrySet().stream().filter(e -> {
            JSONObject o = map.getJSONObject(e.getKey());
            String alt = o.getString("altname");
            String base2 = o.getString("base");
            String quote = o.getString("quote");
            return (alt.startsWith(base) || base2.equals(base)) && quote.endsWith(target);
        }).findFirst().map(e -> {
            JSONObject o = map.getJSONObject(e.getKey());
            String alt = o.getString("altname");
            String base2 = o.getString("base");
            return new String[] { e.getKey(), base2, alt };
        }).orElse(null);
    }
}

Beispielausgabe:
Code:
ANTUSD ANT ANTUSD
COMPUSD COMP COMPUSD
FILUSD FIL FILUSD
FLOWUSD FLOW FLOWUSD
GRTUSD GRT GRTUSD
ICXUSD ICX ICXUSD
KEEPUSD KEEP KEEPUSD
KNCUSD KNC KNCUSD
MATICUSD MATIC MATICUSD
MINAUSD MINA MINAUSD
NANOUSD NANO NANOUSD
RARIUSD RARI RARIUSD
SUSHIUSD SUSHI SUSHIUSD
UNIUSD UNI UNIUSD
WAVESUSD WAVES WAVESUSD
XETCZUSD XETC ETCUSD
XETHZUSD XETH ETHUSD
XLTCZUSD XLTC LTCUSD
XMLNZUSD XMLN MLNUSD
XXBTZUSD XXBT XBTUSD
XXLMZUSD XXLM XLMUSD
XXRPZUSD XXRP XRPUSD

Stimmt jetzt.
 
K

kneitzel

Gast
Erst einmal freut es mich, dass ich mich verständlicher ausdrücken konnte.

Einen noch etwas weitergehenden Ansatz möchte ich aber noch kurz vorstellen, wie ich mit so einem JSON umgehen würde.

Ich gehe gerne den Objektorientierten Ansatz, d.h. ich würde mir eine Klasse bauen, mit der ich die notwendigen Informationen abbilden könnte. Das könnte dann so aussehen (z.B. mittels gson):

[CODE lang="java" title="Wrapper für Response"]import com.google.gson.JsonArray;

import java.util.Map;

public class AssetResponse {
private JsonArray error;
private Map<String, AssetPair> result;

// ...
}
[/CODE]

[CODE lang="java" title="Klasse für AssetPair Daten"]public class AssetPair {
private String altname;
private String wsname;
private String base;
private String quote;

// ...
}[/CODE]

[CODE lang="java" title="Test um die Response in Entities umzuwandeln"]import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.InputStreamReader;

public class DeserializeTest {
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
AssetResponse resonse = gson.fromJson(new InputStreamReader(DeserializeTest.class.getResourceAsStream("/AssetPairs.json")), AssetResponse.class);
// ...
}
}[/CODE]

Bei dem Test habe ich jetzt eifnach keinen Request an den Server abgesetzt sondern hatte einfach das Ergebnis der oben genannten URL in AssetPairs.json gespeichert.

Das Ergebnis ist dann, dass ich in response.result eine Map habe mit allen Daten, die mich interessieren (Hier im Beispiel habe ich einfach mal 4 Felder genommen).

Das muss man in der Logik nicht mehr mit JsonObject und co arbeiten sondern hat Entities, die man direkt nutzen kann. Also den Map Inhalt streamen um dann da direkt auf die Properties zuzugreifen.

Das nur um kurz mein Vorgehen zu skizzieren. Das ist natürlich mit ausbaubar - ein private JsonArray error; ist natürlich nicht wirklich zielführend. Da wäre natürlich interessant, was die API da im Detail liefert um das auszuwerten. Ich habe es nur erst einmal so eingebaut, da ich ungerne Fehler / Exceptions erst einmal "weglasse" um dann später darauf zurück zu kommen.
 

xcvi

Mitglied
Nochmal etwas weitergemacht und ein paar Zeichen eingespart...
Java:
import java.util.List;
import java.util.stream.Collectors;

import org.json.JSONObject;

public class KrakenNames {
    private JSONObject map;

    public KrakenNames(MyAccess access) {
        map = new MyKraken(access.api).getAssetPairs();
        System.out.println(map.length() + " pairs.");
    }

    public String[] resolve(String base, String target) {
        return map.toMap().keySet().stream().filter(k -> {
            JSONObject o = map.getJSONObject(k);
            String alt = o.getString("altname");
            String base2 = o.getString("base");
            String quote = o.getString("quote");
            return (alt.startsWith(base) || base2.equals(base)) && quote.endsWith(target);
        }).findFirst().map(k -> {
            JSONObject o = map.getJSONObject(k);
            String alt = o.getString("altname");
            String base2 = o.getString("base");
            return new String[] { k, base2, alt };
        }).orElse(null);
    }

    public List<List<String>> getUSDAssets() {
        return map.toMap().keySet().stream().filter(k -> {
            return !k.contains(".") && k.endsWith("USD");
        }).map(k -> {
            JSONObject o = map.getJSONObject(k);
            String alt = o.getString("altname");
            String base = o.getString("base");
            return List.of(k, base, alt);
        }).collect(Collectors.toList());
    }

    public List<List<String>> getETHAssets() {
        return map.toMap().keySet().stream().filter(k -> {
            return !k.contains(".") && k.endsWith("ETH");
        }).map(k -> {
            JSONObject o = map.getJSONObject(k);
            String alt = o.getString("altname");
            String base = o.getString("base");
            return List.of(k, base, alt);
        }).collect(Collectors.toList());
    }
}

Die möglichen asset pairs lese ich ganz einfach einmal zu Programmstart direkt von Kraken ein (KrakenNames ist ein Singleton), mithilfe einer dritten API:
Java:
    public JSONObject getAssetPairs() {
        try {
            String s = api.queryPublic(Method.ASSET_PAIRS);
            JSONObject o = new JSONObject(s);
            JSONObject r = o.getJSONObject("result");
            return r;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

Aber GSON braucht man hier nicht unbedingt IMHO.

public String[] resolve vielleicht sollte ich hier auch List<String> zurückgeben, damit es einheitlich wäre...
 
K

kneitzel

Gast
Wenn Dir das so zusagt, ist es ja ok. Ich möchte nur einmal kurz erläutern, was ich bevorzugen würde incl. einem Abriss, wieso ich dies bevorzugen würde.

Ich selbst halte Daten in entsprechenden Model-Klassen vor und JSON dient nur der Serialisierung. Ich würde die Daten meines Models in der Applikation zur Laufzeit nicht als JSON vorhalten und das tust Du ja in der aktuellen Variante was dann zur Folge hat, dass da entsprechende Zugriffe über die Applikation verteilt sind und eben nicht gekapselt sind.

Man könnte das noch entsprechend kapseln um dann einen zentralen Zugriff zu haben (um dann z.B. auch JSONEceptions zu fangen falls ein Mapping nicht existiert oder statt dem Fangen der Exception eine Prüfung, dass der Key existiert. Oder eine Validierung beim Einlesen oder oder oder ...)

Aber sobald Du sowas baust, baust Du Dinge nach, die es eben schon fertig gibt. Welche Implementation man da bevorzugt ist erst einmal egal. gson, jackson, ... Und du arbeitest ja jetzt mit org.json:json wenn ich das richtig gesehen habe - eine Abhängigkeit hast Du also so oder so mit drin.

Daher ist halt mein Vorschlag, dass man sich eben einfach auf sein Model konzentrieren kann um dann mit wenig Code (2 Zeilen) die Konvertierung durchzuführen.

Hat dann auch mit den Vorteil, dass man sich da auch nicht festlegt. Wenn das morgen nicht ehr als JSON vom webservice sondern aus der Datenbank kommt, dann ist es kein Thema. Model bleibt unverändert und man schreibt einfach einen weiteren Adapter. (Wenn man das so bezeichnen möchte. Man kann hier Zuständigkeiten also komplett trennen. Und DI / IoC kannst Du sogar das Model / ein Adapter beliebig nutzen ohne wirklich einen konkreten Adapter zu kennen. Aber damit sind wir beim Design nun schon weit vom eigentlichen Punkt weg. Aber es verdeutlicht vielleicht, wieso ich so eine andere Vorgehensweise bevorzugen würde.)
 

Neue Themen


Oben