与币安不同的EMA(指数移动平均线)

3
我正在使用Binance JAVA API编写Java程序,以检索交易对的1分钟间隔K线图。使用this Java类,我想计算过去10天的EMA(指数移动平均线)。

Binance JAVA API websocket实现获取最新的深度事件,其中包含当前的收盘价,我使用它来通过调用EMA#update方法更新EMA计算。

然而,我注意到在Binance的图表上显示的EMA与我从代码中获得的不符。此外,我注意到这些值需要一些时间来“稳定”之后才会与Binance上显示的值相比较接近。

Output of the program

Values on Binance

在TradingView上,我找到了一个计算EMA的公式(显示与Binance上相同的EMA值),但这与EMA类中使用的公式不同。然而,即使使用此公式,值与Binance上的值也非常不同。请问有人能帮我找出问题所在并如何获得相同的值吗?
更新1:提供代码。
import java.util.*;
import java.util.stream.Collectors;

import com.binance.api.client.BinanceApiClientFactory;
import com.binance.api.client.BinanceApiRestClient;
import com.binance.api.client.BinanceApiWebSocketClient;
import com.binance.api.client.domain.market.Candlestick;
import com.binance.api.client.domain.market.CandlestickInterval;
import core.util.text.DecimalFormat;
import core.util.text.StringUtil;

public class test_003
{
  private Map<Long, Candlestick> candlesticksCache = new TreeMap<>();

  private EMA EMA_10;
  private EMA EMA_20;

  public static void main(String[] pArgs)
  {
    new test_003();
  }

  private test_003()
  {
    Locale.setDefault(Locale.US);

    candlesticksCacheExample("ADAUSDT", CandlestickInterval.ONE_MINUTE);
  }

  private void candlesticksCacheExample(String symbol, CandlestickInterval interval)
  {
    initializeCandlestickCache(symbol, interval);

    startCandlestickEventStreaming(symbol, interval);
  }

  private void initializeCandlestickCache(String symbol, CandlestickInterval interval)
  {
    BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance();
    BinanceApiRestClient client = factory.newRestClient();

    List<Candlestick> candlestickBars_10 = client.getCandlestickBars(symbol.toUpperCase(), interval, Integer.valueOf(11), null, null);
    List<Candlestick> candlestickBars_20 = client.getCandlestickBars(symbol.toUpperCase(), interval, Integer.valueOf(21), null, null);

    List<Double> closingPriceList_10 = candlestickBars_10.stream().map(c -> Double.valueOf(c.getClose())).collect(Collectors.toList());
    List<Double> closingPriceList_20 = candlestickBars_20.stream().map(c -> Double.valueOf(c.getClose())).collect(Collectors.toList());

    EMA_10 = new EMA(closingPriceList_10, Integer.valueOf(10));
    EMA_20 = new EMA(closingPriceList_20, Integer.valueOf(20));
  }

  private void startCandlestickEventStreaming(String symbol, CandlestickInterval interval)
  {
    BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance();
    BinanceApiWebSocketClient client = factory.newWebSocketClient();

    client.onCandlestickEvent(symbol.toLowerCase(), interval, response -> {
      Long openTime = response.getOpenTime();
      Candlestick updateCandlestick = candlesticksCache.get(openTime);
      if (updateCandlestick == null)
      {
        // new candlestick
        updateCandlestick = new Candlestick();
      }

      // update candlestick with the stream data
      updateCandlestick.setOpenTime(response.getOpenTime());
      updateCandlestick.setOpen(response.getOpen());
      updateCandlestick.setLow(response.getLow());
      updateCandlestick.setHigh(response.getHigh());
      updateCandlestick.setClose(response.getClose());
      updateCandlestick.setCloseTime(response.getCloseTime());
      updateCandlestick.setVolume(response.getVolume());
      updateCandlestick.setNumberOfTrades(response.getNumberOfTrades());
      updateCandlestick.setQuoteAssetVolume(response.getQuoteAssetVolume());
      updateCandlestick.setTakerBuyQuoteAssetVolume(response.getTakerBuyQuoteAssetVolume());
      updateCandlestick.setTakerBuyBaseAssetVolume(response.getTakerBuyQuoteAssetVolume());

      // Store the updated candlestick in the cache
      candlesticksCache.put(openTime, updateCandlestick);

      double closingPrice = Double.valueOf(updateCandlestick.getClose());

      EMA_10.update(closingPrice);
      EMA_20.update(closingPrice);

      System.out.println(StringUtil.replacePlaceholders("Closing price: %1 | EMA(10): %2 - EMA(20): %3", response.getClose(),
                                                        DecimalFormat.format(EMA_10.get(), "#.#####"),
                                                        DecimalFormat.format(EMA_20.get(), "#.#####")));
    });
  }

  public class EMA
  {

    private double currentEMA;
    private final int period;
    private final double multiplier;
    private final List<Double> EMAhistory;
    private final boolean historyNeeded;
    private String fileName;


    public EMA(List<Double> closingPrices, int period)
    {
      this(closingPrices, period, false);
    }

    public EMA(List<Double> closingPrices, int period, boolean historyNeeded)
    {
      currentEMA = 0;
      this.period = period;
      this.historyNeeded = historyNeeded;
      this.multiplier = 2.0 / (double) (period + 1);
      this.EMAhistory = new ArrayList<>();
      init(closingPrices);
    }

    public double get()
    {
      return currentEMA;
    }

    public double getTemp(double newPrice)
    {
      return (newPrice - currentEMA) * multiplier + currentEMA;
    }

    public void init(List<Double> closingPrices)
    {
      if (period > closingPrices.size()) return;

      //Initial SMA
      for (int i = 0; i < period; i++)
      {
        currentEMA += closingPrices.get(i);
      }

      currentEMA = currentEMA / (double) period;
      if (historyNeeded) EMAhistory.add(currentEMA);
      //Dont use latest unclosed candle;
      for (int i = period; i < closingPrices.size() - 1; i++)
      {
        update(closingPrices.get(i));
      }
    }

    public void update(double newPrice)
    {
      // EMA = (Close - EMA(previousBar)) * multiplier + EMA(previousBar)
      currentEMA = (newPrice - currentEMA) * multiplier + currentEMA;

      if (historyNeeded) EMAhistory.add(currentEMA);
    }

    public int check(double newPrice)
    {
      return 0;
    }

    public String getExplanation()
    {
      return null;
    }

    public List<Double> getEMAhistory()
    {
      return EMAhistory;
    }

    public int getPeriod()
    {
      return period;
    }
  }
}

更新2

enter image description here


1
我认为你可以通过像https://www.ta-lib.org/hdr_dev.html这样的开源库来交叉检查你的结果。 - Cheok Yan Cheng
谢谢您的建议。然而,我仍然得到EMA值上轻微的差异。您能否通过运行我的代码来帮助我更多? - RazorAlliance192
你在 EMA 计算中使用了足够的历史数据吗?通常需要使用 EMA 回溯期的 3 倍左右才能稳定值。例如,如果 EMA 的回溯期为 10,你会在第 10 个蜡烛上得到第一个值,但只有在第 30 个蜡烛左右,EMA 才被认为是“稳定”的。 - e2e4
@e2e4 我将币安API发送的历史数据最大限度地提高到了1000个1分钟蜡烛图。运行程序时,数值与币安显示的完全相同。但是在30秒后,数值略有不同,在一两分钟后,它们的差异很大(请参见我最后更新中的图像)。 - RazorAlliance192
你找到答案了吗?我已经尝试了第二天了...找到了每一个可用的公式,尝试了不同的权重,但我从币安无法得到EMA。如果我在5分钟后设法将它们匹配起来,一切都会变得不同。 - Lachezar Raychev
2个回答

1
问题在于onCandlestickEvent不仅在蜡烛完成时被调用,而且每分钟实际上会被多次调用(大约每2秒一次)。您在response中收到的数据跨越了从蜡烛开启到响应事件时间的时间,无论蜡烛是否完成。
要了解我的意思,您可以将startCandlestickEventStreaming方法中的System.out()语句替换为以下内容:
System.out.println(response.getOpenTime() + ";" +
    response.getEventTime() + ";" +
    response.getCloseTime());

你会发现蜡烛的关闭时间实际上在未来。
为了正确更新你的EMA,你必须等待蜡烛实际完成。你可以将临时蜡烛的开盘时间存储在成员变量中,检查它是否与上次调用onCandlestickEvent时有所改变,然后使用蜡烛的最终收盘价更新你的EMA:
client.onCandlestickEvent(symbol.toLowerCase(), interval, response -> {
    Long openTime = response.getOpenTime(); 
    Candlestick updateCandlestick = candlesticksCache.get(openTime);
    if (updateCandlestick == null)
    {
        // new candlestick
        updateCandlestick = new Candlestick();
    }

    // update candlestick with the stream data
    ...
    
    // Store the updated candlestick in the cache
    candlesticksCache.put(openTime, updateCandlestick);

    if (openTime > m_LastOpenTime)
    {
        // need to get the close of the PREVIOUS candle
        Candlestick previousCandle = candlesticksCache.get(m_LastOpenTime);
        double closingPrice = Double.valueOf(previousCandle.getClose());
        
        EMA_10.update(closingPrice);
        EMA_20.update(closingPrice);

        System.out.println(StringUtil.replacePlaceholders("Closing price: %1 | EMA(10): %2 - EMA(20): %3", response.getClose(),
                                                        DecimalFormat.format(EMA_10.get(), "#.#####"),
                                                        DecimalFormat.format(EMA_20.get(), "#.#####")));

        m_LastOpenTime = openTime;
    }
});       

第一次响应可能会出现异常,因为栈上还没有蜡烛,我们也没有 m_LastOpenTime。在调用 client.onCandlestickEvent() 之前,您可以获取当前服务器时间:

private void startCandlestickEventStreaming(String symbol, CandlestickInterval interval)
{
    BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance();
    BinanceApiWebSocketClient client = factory.newWebSocketClient();

    BinanceApiRestClient restClient = factory.newRestClient();
    m_LastOpenTime = restClient.getServerTime();

    client.onCandlestickEvent(symbol.toLowerCase(), interval, response -> {
        ...
    }
}

1
我注意到其实有一种比我的另一个答案更简单的方法。然而,我仍然保留那个答案,因为它仍然可能与处理不可靠连接相关,在这种情况下,您不能保证始终获得带有 response 的最终蜡烛图。 response.getBarFinal()) 方法允许测试您收到的响应是否为最终蜡烛图,还是仅为中间蜡烛图。如果按照以下方式更改代码,则您的 EMA 仅会使用蜡烛的最终收盘价进行更新,如应该所示:
if (response.getBarFinal())
{
    double closingPrice = Double.valueOf(updateCandlestick.getClose());
    EMA_10.update(closingPrice);
    EMA_20.update(closingPrice);
    
    System.out.println(StringUtil.replacePlaceholders("Closing price: %1 | EMA(10): %2 - EMA(20): %3", response.getClose(),
            DecimalFormat.format(EMA_10.get(), "#.#####"),
            DecimalFormat.format(EMA_20.get(), "#.#####")));

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接