Skip to content

add trade stream for Bitflyer #740

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

Merged
merged 1 commit into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions src/ExchangeSharp/API/Exchanges/Bitflyer/ExchangeBitflyerApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using Newtonsoft.Json.Linq;
using SocketIOClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ExchangeSharp
{
public sealed partial class ExchangeBitflyerApi : ExchangeAPI
{
public override string BaseUrl { get; set; } = "https://api.bitflyer.com";
public override string BaseUrlWebSocket { get; set; } = "https://io.lightstream.bitflyer.com";

public ExchangeBitflyerApi()
{
//NonceStyle = new guid
//NonceOffset not needed
// WebSocketOrderBookType = not implemented
MarketSymbolSeparator = "_";
MarketSymbolIsUppercase = true;
// ExchangeGlobalCurrencyReplacements[] not implemented
}

protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
{
/*
[
{
"product_code": "BTC_JPY",
"market_type": "Spot"
},
{
"product_code": "XRP_JPY",
"market_type": "Spot"
},
{
"product_code": "ETH_JPY",
"market_type": "Spot"
},
{
"product_code": "XLM_JPY",
"market_type": "Spot"
},
{
"product_code": "MONA_JPY",
"market_type": "Spot"
},
{
"product_code": "ETH_BTC",
"market_type": "Spot"
},
{
"product_code": "BCH_BTC",
"market_type": "Spot"
},
{
"product_code": "FX_BTC_JPY",
"market_type": "FX"
},
{
"product_code": "BTCJPY12MAR2021",
"alias": "BTCJPY_MAT1WK",
"market_type": "Futures"
},
{
"product_code": "BTCJPY19MAR2021",
"alias": "BTCJPY_MAT2WK",
"market_type": "Futures"
},
{
"product_code": "BTCJPY26MAR2021",
"alias": "BTCJPY_MAT3M",
"market_type": "Futures"
}
]
*/
JToken instruments = await MakeJsonRequestAsync<JToken>("v1/getmarkets");
var markets = new List<ExchangeMarket>();
foreach (JToken instrument in instruments)
{
markets.Add(new ExchangeMarket
{
MarketSymbol = instrument["product_code"].ToStringUpperInvariant(),
AltMarketSymbol = instrument["alias"].ToStringInvariant(),
AltMarketSymbol2 = instrument["market_type"].ToStringInvariant(),
});
}
return markets.Select(m => m.MarketSymbol);
}
protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
{
if (marketSymbols == null || marketSymbols.Length == 0)
{
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
}
var client = new SocketIOWrapper(BaseUrlWebSocket);

foreach (var marketSymbol in marketSymbols)
{ // {product_code} can be obtained from the market list. It cannot be an alias.
// BTC/JPY (Spot): lightning_executions_BTC_JPY
client.socketIO.On($"lightning_executions_{marketSymbol}", response =>
{ /* [[ {
"id": 39361,
"side": "SELL",
"price": 35100,
"size": 0.01,
"exec_date": "2015-07-07T10:44:33.547Z",
"buy_child_order_acceptance_id": "JRF20150707-014356-184990",
"sell_child_order_acceptance_id": "JRF20150707-104433-186048"
} ]] */
var token = JToken.Parse(response.ToStringInvariant());
foreach (var tradeToken in token[0])
{
var trade = tradeToken.ParseTradeBitflyer("size", "price", "side", "exec_date", TimestampType.Iso8601UTC, "id");

// If it is executed during an Itayose, it will be an empty string.
if (string.IsNullOrWhiteSpace(tradeToken["side"].ToStringInvariant()))
trade.Flags |= ExchangeTradeFlags.HasNoSide;

callback(new KeyValuePair<string, ExchangeTrade>(marketSymbol, trade)).Wait();
}
});
}

client.socketIO.OnConnected += async (sender, e) =>
{
foreach (var marketSymbol in marketSymbols)
{ // {product_code} can be obtained from the market list. It cannot be an alias.
// BTC/JPY (Spot): lightning_executions_BTC_JPY
await client.socketIO.EmitAsync("subscribe", $"lightning_executions_{marketSymbol}");
}
};
await client.socketIO.ConnectAsync();
return client;
}
}

class SocketIOWrapper : IWebSocket
{
public SocketIO socketIO;
public SocketIOWrapper(string url)
{
socketIO = new SocketIO(url);
socketIO.Options.Transport = SocketIOClient.Transport.TransportProtocol.WebSocket;
socketIO.OnConnected += (s, e) => Connected?.Invoke(this);
socketIO.OnDisconnected += (s, e) => Disconnected?.Invoke(this);
}

public TimeSpan ConnectInterval
{ get => socketIO.Options.ConnectionTimeout; set => socketIO.Options.ConnectionTimeout = value; }

public TimeSpan KeepAlive
{ get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

public event WebSocketConnectionDelegate Connected;
public event WebSocketConnectionDelegate Disconnected;

public Task<bool> SendMessageAsync(object message) => throw new NotImplementedException();

public void Dispose() => socketIO.Dispose();
}

public partial class ExchangeName { public const string Bitflyer = "Bitflyer"; }
}
17 changes: 17 additions & 0 deletions src/ExchangeSharp/API/Exchanges/Bitflyer/Models/BitflyerTrade.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace ExchangeSharp.Bitflyer
{
public class BitflyerTrade : ExchangeTrade
{
public string BuyChildOrderAcceptanceId { get; set; }
public string SellChildOrderAcceptanceId { get; set; }

public override string ToString()
{
return string.Format("{0},{1}, {2}", base.ToString(), BuyChildOrderAcceptanceId, SellChildOrderAcceptanceId);
}
}
}
11 changes: 11 additions & 0 deletions src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The above copyright notice and this permission notice shall be included in all c
using ExchangeSharp.NDAX;
using ExchangeSharp.API.Exchanges.FTX.Models;
using ExchangeSharp.Bybit;
using ExchangeSharp.Bitflyer;

namespace ExchangeSharp
{
Expand Down Expand Up @@ -541,6 +542,16 @@ internal static ExchangeTrade ParseTradeBinance(this JToken token, object amount
return trade;
}

internal static ExchangeTrade ParseTradeBitflyer(this JToken token, object amountKey, object priceKey, object typeKey,
object timestampKey, TimestampType timestampType, object idKey, string typeKeyIsBuyValue = "buy")
{
var trade = ParseTradeComponents<BitflyerTrade>(token, amountKey, priceKey, typeKey,
timestampKey, timestampType, idKey, typeKeyIsBuyValue);
trade.BuyChildOrderAcceptanceId = token["buy_child_order_acceptance_id"].ConvertInvariant<string>();
trade.SellChildOrderAcceptanceId = token["sell_child_order_acceptance_id"].ConvertInvariant<string>();
return trade;
}

internal static ExchangeTrade ParseTradeBybit(this JToken token, object amountKey, object priceKey, object typeKey,
object timestampKey, TimestampType timestampType, object idKey, string typeKeyIsBuyValue = "buy")
{
Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/ExchangeSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.7" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NLog" Version="4.5.10" />
<PackageReference Include="SocketIOClient" Version="3.0.5" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0" />
</ItemGroup>

Expand Down
9 changes: 7 additions & 2 deletions src/ExchangeSharp/Model/ExchangeTrade.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand Down Expand Up @@ -120,6 +120,11 @@ public enum ExchangeTradeFlags
/// <summary>
/// Whether the trade is the last trade from a snapshot
/// </summary>
IsLastFromSnapshot = 4
IsLastFromSnapshot = 4,

/// <summary>
/// Is neither buy nor sell
/// </summary>
HasNoSide = 8,
}
}