Skip to content

Commit

Permalink
4 change endpoint to support multiple coins (#5)
Browse files Browse the repository at this point in the history
* Fix #2 (#3)

Transactions can be written to json again.

* Refactorings

Move iterating over transactions into service.

* Fix #4
  • Loading branch information
kerner1000 authored May 7, 2022
1 parent b358482 commit 3afe55e
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.kerner1000.terra;

import com.github.kerner1000.terra.commons.Coin;
import com.github.kerner1000.terra.transactions.*;

import java.util.Arrays;

public class LunaWeightedMeanCalculatorService extends WeightedMeanCalculatorService {

public LunaWeightedMeanCalculatorService() {
super(Coin.LUNA);
}

protected Iterable<? extends AbstractTransactionVisitor> getVisitors() {
return Arrays.asList(new TerraswapTransactionVisitor(), new AstroportTransactionVisitor(), new MarketTransactionVisitor(), new LoopSwapTransactionVisitor());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.kerner1000.terra.commons.BinnedBuySellMaps;
import com.github.kerner1000.terra.commons.BuySellMaps;
import com.github.kerner1000.terra.commons.Coin;
import com.github.kerner1000.terra.commons.SwapPrices;
import com.github.kerner1000.terra.feign.LcdClient;
import com.github.kerner1000.terra.feign.LcdTransactionsPagination;
Expand All @@ -11,50 +12,58 @@
import lombok.extern.slf4j.Slf4j;
import org.openapitools.api.SwapsApi;
import org.openapitools.model.BuySellSwaps;
import org.openapitools.model.BuySellSwapsPerCoin;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
@RestController
public class TerraAppController implements SwapsApi {

private final LcdClient lcdClient;

private final WeightedMeanCalculatorService callbackService;

private final ObjectMapper objectMapper;

private final TerraConfig terraConfig;

public TerraAppController(LcdClient lcdClient, WeightedMeanCalculatorService callbackService, ObjectMapper objectMapper, TerraConfig terraConfig) {
public TerraAppController(LcdClient lcdClient, ObjectMapper objectMapper, TerraConfig terraConfig) {
this.lcdClient = lcdClient;
this.callbackService = callbackService;
this.objectMapper = objectMapper;
this.terraConfig = terraConfig;
}

@Override
public ResponseEntity<BuySellSwaps> getSwaps(String token, String terraAddress) {
log.info("Calculating average buy/sell for {} and address {}", token, terraAddress);
public ResponseEntity<BuySellSwapsPerCoin> getSwaps(String terraAddress, List<String> hide) {
log.info("Calculating average buy/sell for address {}", terraAddress);
long transactionsCount = 0;
long offset = 0;
BuySellMaps collectedSwaps = new BuySellMaps();
Map<Coin,BuySellMaps> coinToCollectedSwaps = new TreeMap<>();
do {
try {
LcdTransactionsPagination lcdTransactionsPagination = lcdClient.searchTransactions(terraAddress, 100, offset);
List<Transaction> transactions = new ArrayList<>(lcdTransactionsPagination.getTxs());
collectedSwaps.add(callbackService.visit(lcdTransactionsPagination.getTxs()));
var buySellMapsForCoin = new LunaWeightedMeanCalculatorService().visit(lcdTransactionsPagination.getTxs());
var buySellMaps = coinToCollectedSwaps.get(buySellMapsForCoin.coin());
if(buySellMaps != null){
buySellMaps.add(buySellMapsForCoin.buySellMaps());
} else {
buySellMaps = buySellMapsForCoin.buySellMaps();
}
coinToCollectedSwaps.put(buySellMapsForCoin.coin(), buySellMaps);
transactionsCount += transactions.size();
log.info("Collected {} transactions, current offset: {}", transactionsCount, offset);
if(terraConfig.isWriteTransactions()) {
try {
File file = new File("transactions/" + terraAddress + "transaction-" + offset + ".json");
if(file.mkdirs()){
File directory = new File("transactions/"+terraAddress);
File file = new File(directory,"transaction-" + offset + ".json");
if(directory.exists() || directory.mkdirs()){
objectMapper.writeValue(file, transactions);
}
} catch (IOException e) {
Expand All @@ -68,11 +77,10 @@ public ResponseEntity<BuySellSwaps> getSwaps(String token, String terraAddress)
}
} while (offset != 0);

SwapPrices result2 = new Transactions().getWeightedMean(collectedSwaps);
log.info("Collected {} transactions, average swap price is {}", transactionsCount, result2);
BinnedBuySellMaps<Double> binnedBuySellMaps = BinnedBuySellMaps.BinnedBuySellMapsFactory.buildWithFixBinSize(collectedSwaps.getBuyMap(), 5);
log.debug("Buy price distribution:\n{}", binnedBuySellMaps.toAsciiHistogram(false));
org.openapitools.model.BuySellSwaps result = Transformer.transform(collectedSwaps);
BuySellSwapsPerCoin result = new BuySellSwapsPerCoin();
for(Map.Entry<Coin, BuySellMaps> entry : coinToCollectedSwaps.entrySet()){
result.addEntriesItem(Transformer.transform(entry.getKey(), entry.getValue()));
}
return ResponseEntity.ok(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.kerner1000.terra.commons.BuySellMap;
import com.github.kerner1000.terra.commons.BuySellMaps;
import com.github.kerner1000.terra.commons.Coin;
import org.openapitools.model.BuySellSwaps;
import org.openapitools.model.SwapEntry;
import org.openapitools.model.Swaps;
Expand All @@ -11,8 +12,9 @@

public class Transformer {

public static BuySellSwaps transform(BuySellMaps maps){
public static BuySellSwaps transform(Coin coin, BuySellMaps maps){
BuySellSwaps buySellSwaps = new BuySellSwaps();
buySellSwaps.setCoin(coin.toString());
buySellSwaps.setBuy(new Swaps());
buySellSwaps.setSell(new Swaps());
for(Map.Entry<BuySellMap.Key, Number> element : maps.getBuyMap().getMap().entrySet()){
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
package com.github.kerner1000.terra;

import com.github.kerner1000.terra.commons.BuySellMaps;
import com.github.kerner1000.terra.commons.BuySellMapsForCoin;
import com.github.kerner1000.terra.commons.Coin;
import com.github.kerner1000.terra.json.data.Transaction;
import com.github.kerner1000.terra.transactions.Transactions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.context.annotation.RequestScope;
import com.github.kerner1000.terra.transactions.AbstractTransactionVisitor;

import java.util.List;

@Slf4j
@RequestScope
@Service
public class WeightedMeanCalculatorService {

public BuySellMaps visit(List<Transaction> transactions) throws InterruptedException {
BuySellMaps wm = new Transactions().getWeightedMeanSwapMaps(transactions);
// if(log.isDebugEnabled() && wm.getBuyMap().getMap().size() > 1 || wm.getSellMap().getMap().size() > 1) {
// log.debug("swap map:\n{}\n======\nweighted mean: {}", result, new Transactions().getWeightedMean(result));
// }
return wm;
public abstract class WeightedMeanCalculatorService {

private final Coin coin;

protected WeightedMeanCalculatorService(Coin coin) {
this.coin = coin;
}

public BuySellMapsForCoin visit(List<Transaction> transactions) throws InterruptedException {

BuySellMaps result = new BuySellMaps();

for (Transaction transaction : transactions) {
if(Thread.currentThread().isInterrupted()){
break;
}
for (AbstractTransactionVisitor visitor : getVisitors()) {
result.add(visitor.visit(transaction));
}
}
return new BuySellMapsForCoin(coin, result);
}

protected abstract Iterable<? extends AbstractTransactionVisitor> getVisitors();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
import com.github.kerner1000.terra.commons.SwapPrices;
import com.github.kerner1000.terra.json.data.Additional;
import com.github.kerner1000.terra.json.data.Swap;
import com.github.kerner1000.terra.json.data.Transaction;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
Expand All @@ -28,27 +26,6 @@ public class Transactions {

static final Predicate<Additional> ASTROPORT_FILTER = a -> a != null && a.getContract() != null && a.getContract().stream().anyMatch(c -> c.contains(SwapPairs.Astroport.LUNA_UST));

private final List<AbstractTransactionVisitor> visitors = Arrays.asList(new TerraswapTransactionVisitor(), new AstroportTransactionVisitor(), new MarketTransactionVisitor(), new LoopSwapTransactionVisitor());

public BuySellMaps getWeightedMeanSwapMaps(Collection<? extends Transaction> transactionsList) {

BuySellMaps result = new BuySellMaps();

for (Transaction transaction : transactionsList) {
if(Thread.currentThread().isInterrupted()){
break;
}
for (AbstractTransactionVisitor visitor : visitors) {
result.add(visitor.visit(transaction));
}
}
return result;
}

public SwapPrices getWeightedMean(Collection<? extends Transaction> transactionsList) throws InterruptedException {
return getWeightedMean(getWeightedMeanSwapMaps(transactionsList));
}

public SwapPrices getWeightedMean(BuySellMaps swapResult) {
return new SwapPrices(weightedMean(swapResult.getBuyMap()), weightedMean(swapResult.getSellMap()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import discord4j.core.spec.MessageCreateMono;
import lombok.extern.slf4j.Slf4j;
import org.openapitools.model.BuySellSwaps;
import org.openapitools.model.BuySellSwapsPerCoin;
import org.springframework.http.ResponseEntity;
import reactor.core.publisher.Mono;

Expand Down Expand Up @@ -71,28 +72,30 @@ void processResult(String string){

private String processBuyCommand(String buyCommandString) {
try {
if (buyCommandString.startsWith("LUNA")) {
String terraAddress = buyCommandString.substring("LUNA".length()).trim();
ResponseEntity<BuySellSwaps> apiResponse = accountStatsApiClient.getSwaps("Luna", terraAddress);

BuySellSwaps buySellSwaps = apiResponse.getBody();
BuySellMap buyMap = new BuySellMap(buySellSwaps.getBuy());
BinnedBuySellMaps binnedBuyMap = BinnedBuySellMaps.BinnedBuySellMapsFactory.buildWithFixBinSize(buyMap, 5);
BuySellMap sellMap = new BuySellMap(buySellSwaps.getSell());
BinnedBuySellMaps binnedSellMap = BinnedBuySellMaps.BinnedBuySellMapsFactory.buildWithFixBinSize(sellMap, 5);
SwapPrices swapPrices = new SwapPrices(Util.weightedMean(buySellSwaps.getBuy().getSwaps()),Util.weightedMean(buySellSwaps.getSell().getSwaps()));

String terraAddress = buyCommandString.trim();
ResponseEntity<BuySellSwapsPerCoin> apiResponse = accountStatsApiClient.getSwaps(terraAddress,null);
StringBuilder sb = new StringBuilder();
sb.append("Average swap prices are:\n");
sb.append(swapPrices);
sb.append("\n");
sb.append("Your buy price distribution:");
sb.append("\n");
sb.append("```" + binnedBuyMap.toAsciiHistogram(false) + "```");
sb.append("Your sell price distribution:");
sb.append("```" + binnedSellMap.toAsciiHistogram(false) + "```");
for(BuySellSwaps buySellSwaps : apiResponse.getBody().getEntries()){
sb.append(buySellSwaps.getCoin());
sb.append(":\n");
BuySellMap buyMap = new BuySellMap(buySellSwaps.getBuy());
BinnedBuySellMaps binnedBuyMap = BinnedBuySellMaps.BinnedBuySellMapsFactory.buildWithFixBinSize(buyMap, 5);
BuySellMap sellMap = new BuySellMap(buySellSwaps.getSell());
BinnedBuySellMaps binnedSellMap = BinnedBuySellMaps.BinnedBuySellMapsFactory.buildWithFixBinSize(sellMap, 5);
SwapPrices swapPrices = new SwapPrices(Util.weightedMean(buySellSwaps.getBuy().getSwaps()),Util.weightedMean(buySellSwaps.getSell().getSwaps()));
sb.append("Average swap prices are:\n");
sb.append(swapPrices);
sb.append("\n");
sb.append("Your buy price distribution:");
sb.append("\n");
sb.append("```" + binnedBuyMap.toAsciiHistogram(false) + "```");
sb.append("Your sell price distribution:");
sb.append("```" + binnedSellMap.toAsciiHistogram(false) + "```");
sb.append("\n");
}
return sb.toString();
}

} catch(Exception e){
log.error(e.getLocalizedMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.github.kerner1000.terra.commons;

import org.openapitools.model.BuySellSwaps;
import org.openapitools.model.SwapEntry;
import org.openapitools.model.Swaps;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.github.kerner1000.terra.commons;

public record BuySellMapsForCoin(Coin coin, BuySellMaps buySellMaps) {

}
26 changes: 19 additions & 7 deletions terra-stats-contract/src/main/resources/contract.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,34 @@ info:
description: Terra Stats OpenAPI contract
version: 1.0.0
paths:
/swaps/{coin}/{terra-address}:
/swaps/{terra-address}:
get:
summary: get swaps for given coin
description: get swaps for given coin
operationId: getSwaps
tags:
- /swaps
parameters:
- name: coin
in: path
schema:
type: string
required: true
- name: terra-address
in: path
schema:
type: string
required: true
- name: hide
in: query
schema:
type: array
items:
type: string
required: false

responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/BuySellSwaps'
$ref: '#/components/schemas/BuySellSwapsPerCoin'
500:
description: internal server error
content:
Expand Down Expand Up @@ -56,9 +59,18 @@ components:
BuySellSwaps:
type: object
properties:
coin:
type: string
buy:
$ref: '#/components/schemas/Swaps'
sell:
$ref: '#/components/schemas/Swaps'
BuySellSwapsPerCoin:
type: object
properties:
entries:
type: array
items:
$ref: '#/components/schemas/BuySellSwaps'
Error:
type: object

0 comments on commit 3afe55e

Please sign in to comment.