Skip to content
This repository was archived by the owner on Apr 2, 2023. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: veighna-global/vnpy_ftx
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2021.9.24
Choose a base ref
...
head repository: veighna-global/vnpy_ftx
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Sep 26, 2021

  1. 修复bug

    Edanflame committed Sep 26, 2021
    Copy the full SHA
    f2be97b View commit details
  2. Merge pull request #6 from Edanflame/main

    修复bug
    vnpy authored Sep 26, 2021
    Copy the full SHA
    55b8e24 View commit details

Commits on Sep 27, 2021

  1. Copy the full SHA
    a04c316 View commit details
  2. Copy the full SHA
    7b592e2 View commit details
  3. Merge pull request #7 from Edanflame/main

    修复持仓为0时仓位不刷新问题
    vnpy authored Sep 27, 2021
    Copy the full SHA
    6ba5444 View commit details

Commits on Sep 29, 2021

  1. Copy the full SHA
    6de7a42 View commit details
  2. Copy the full SHA
    ed78860 View commit details
  3. Merge pull request #8 from Edanflame/main

    增加历史数据查询数量
    vnpy authored Sep 29, 2021
    Copy the full SHA
    0d1ac00 View commit details

Commits on Sep 30, 2021

  1. 修改重连机制

    Edanflame committed Sep 30, 2021
    Copy the full SHA
    8e6376d View commit details
  2. Copy the full SHA
    d180700 View commit details

Commits on Oct 1, 2021

  1. Merge pull request #9 from Edanflame/main

    修改重连机制
    vnpy authored Oct 1, 2021
    Copy the full SHA
    bec72eb View commit details

Commits on Oct 19, 2021

  1. Copy the full SHA
    e49d485 View commit details

Commits on Oct 20, 2021

  1. Merge pull request #11 from cningyuan/fix_FTX_proxy_bug

    修复FTX接口代理连接Bug
    vnpy authored Oct 20, 2021
    Copy the full SHA
    80a8938 View commit details

Commits on Oct 22, 2021

  1. Copy the full SHA
    846641b View commit details
  2. Merge pull request #12 from Edanflame/main

    [Fix] 历史数据下载添加时区
    vnpy authored Oct 22, 2021
    Copy the full SHA
    6160b9b View commit details

Commits on Oct 25, 2021

  1. Copy the full SHA
    27bcf71 View commit details

Commits on Nov 1, 2021

  1. Copy the full SHA
    6aaab9a View commit details
  2. Copy the full SHA
    277e975 View commit details
  3. Merge pull request #13 from Edanflame/main

    修复历史数据下载乱序问题
    vnpy authored Nov 1, 2021
    Copy the full SHA
    cc510f4 View commit details

Commits on Dec 1, 2021

  1. Copy the full SHA
    07dec72 View commit details

Commits on May 10, 2022

  1. [Mod] update README.md

    vn-crypto committed May 10, 2022
    Copy the full SHA
    69d9d45 View commit details
Showing with 133 additions and 62 deletions.
  1. +1 −1 LICENSE
  2. +5 −2 README.md
  3. +3 −3 setup.cfg
  4. +2 −2 vnpy_ftx/__init__.py
  5. +122 −54 vnpy_ftx/ftx_gateway.py
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-present, Xiaoyou Chen
Copyright (c) 2015-present, vn-crypto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -5,12 +5,15 @@
</p>

<p align="center">
<img src ="https://img.shields.io/badge/version-2021.9.24-blueviolet.svg"/>
<img src ="https://img.shields.io/badge/version-2021.10.25-blueviolet.svg"/>
<img src ="https://img.shields.io/badge/platform-windows|linux|macos-yellow.svg"/>
<img src ="https://img.shields.io/badge/python-3.7-blue.svg" />
<img src ="https://img.shields.io/github/license/vnpy/vnpy.svg?color=orange"/>
</p>

关于使用VeighNa框架进行Crypto交易的话题,新开了一个[Github Discussions论坛](https://github.com/vn-crypto/vnpy_crypto/discussions),欢迎通过这里来进行讨论交流。


## 说明

基于FTX交易所的API开发,支持账户下的现货、期货、永续交易。
@@ -19,7 +22,7 @@

## 安装

安装需要基于2.6.0版本以上的[VN Studio](https://www.vnpy.com)
安装需要基于2.7.0版本以上的[VN Studio](https://www.vnpy.com)

直接使用pip命令:

6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[metadata]
name = vnpy_ftx
version = 2021.9.24
version = 2021.10.25
url = https://www.vnpy.com
license = MIT
author = Xiaoyou Chen
author_email = xiaoyou.chen@mail.vnpy.com
author = vn-crypto
author_email = vn.crypto@outlook.com
description = FTX gateway for vn.py quant trading framework.
long_description = file: README.md
long_description_content_type = text/markdown
4 changes: 2 additions & 2 deletions vnpy_ftx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The MIT License (MIT)
#
# Copyright (c) 2015-present, Xiaoyou Chen
# Copyright (c) 2015-present, vn-crypto
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,7 @@

import importlib_metadata

from .ftx_gateway import FtxGateways
from .ftx_gateway import FtxGateway


try:
176 changes: 122 additions & 54 deletions vnpy_ftx/ftx_gateway.py
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@

from vnpy_websocket import WebsocketClient
from vnpy_rest import Request, RestClient
from vnpy_rest.rest_client import Response


# 中国时区
@@ -57,7 +58,8 @@
# 买卖方向映射
DIRECTION_VT2FTX = {
Direction.LONG: "buy",
Direction.SHORT: "sell"
Direction.SHORT: "sell",
Direction.NET: "net"
}

DIRECTION_FTX2VT = {v: k for k, v in DIRECTION_VT2FTX.items()}
@@ -70,15 +72,23 @@

PRODUCTTYPE_FTX2VT = {v: k for k, v in PRODUCTTYPE_VT2FTX.items()}

INTERVAL_VT2FTX = {
# 窗口长度映射
WINDOW_VT2FTX = {
Interval.MINUTE: 60,
Interval.HOUR: 3600,
Interval.DAILY: 86400,
Interval.WEEKLY: 604800
}

INTERVAL_FTX2VT = {v: k for k, v in INTERVAL_VT2FTX.items()}
WINDOW_FTX2VT = {v: k for k, v in WINDOW_VT2FTX.items()}

# 历史数据长度限制映射
LMIMIT_VT2FTX = {
Interval.MINUTE: 950,
Interval.HOUR: 1400,
Interval.DAILY: 1400,
Interval.WEEKLY: 1400
}

# 合约数据全局缓存字典
symbol_contract_map: Dict[str, ContractData] = {}
@@ -206,7 +216,7 @@ def unsubscribe(self, req: SubscribeRequest) -> None:

def send_order(self, req: OrderRequest) -> None:
"""委托下单"""
self.rest_api.send_order(req)
return self.rest_api.send_order(req)

def cancel_order(self, req: CancelRequest) -> None:
"""委托撤单"""
@@ -320,8 +330,8 @@ def connect(
"""连接REST服务器"""
self.key = key
self.secret = secret.encode()
self.proxy_port = proxy_host
self.proxy_host = proxy_port
self.proxy_host = proxy_host
self.proxy_port = proxy_port

self.connect_time = (
int(datetime.now().strftime("%y%m%d%H%M%S")) * self.order_count
@@ -469,19 +479,17 @@ def on_query_account(self, data: dict, request: Request) -> None:
def on_query_position(self, data: dict, request: Request) -> None:
"""持仓查询回报"""
for d in data["result"]:
if d["entryPrice"] is not None:
position: PositionData = PositionData(
symbol=d["future"],
exchange=Exchange.FTX,
direction=DIRECTION_FTX2VT[d["side"]],
volume=float(d["size"]),
price=float(d["entryPrice"]),
pnl=float(d["unrealizedPnl"]),
gateway_name=self.gateway_name,
)
position: PositionData = PositionData(
symbol=d["future"],
exchange=Exchange.FTX,
direction=DIRECTION_FTX2VT["net"],
volume=float(d["netSize"]),
price=0,
pnl=float(d["unrealizedPnl"]),
gateway_name=self.gateway_name,
)

if position.volume:
self.gateway.on_position(position)
self.gateway.on_position(position)

self.gateway.write_log("持仓信息查询成功")

@@ -584,40 +592,88 @@ def on_cancel_failed(self, status_code: str, request: Request):

def query_history(self, req: HistoryRequest) -> List[BarData]:
"""查询历史数据"""
history = []
start = datetime.timestamp(req.start)
end = datetime.timestamp(req.end)
params = {
"resolution": INTERVAL_VT2FTX[req.interval],
"start_time": start,
"end_time": end
}
path = f"/api/markets/{req.symbol}/candles?"
history: List[BarData] = []
limit: int = LMIMIT_VT2FTX[req.interval]

resp = self.request(
"GET",
path,
data={"security": Security.NONE},
params=params
)
start: int = int(datetime.timestamp(req.start))
end: int = int(datetime.timestamp(req.end))

data = resp.json()
if not data:
return 0
for his_data in data["result"]:
bar = BarData(
symbol=req.symbol,
exchange=req.exchange,
datetime=datetime.utcfromtimestamp(his_data["time"] / 1000),
interval=req.interval,
volume=his_data["volume"],
open_price=his_data["open"],
high_price=his_data["high"],
low_price=his_data["low"],
close_price=his_data["close"],
gateway_name=self.gateway_name
tmp_start = max(start, end - WINDOW_VT2FTX[req.interval] * limit)

buf: List[BarData] = None

while True:
params = {
"resolution": WINDOW_VT2FTX[req.interval],
"start_time": tmp_start,
"end_time": end
}

path = f"/api/markets/{req.symbol}/candles?"

resp: Response = self.request(
"GET",
path,
data={"security": Security.NONE},
params=params
)
history.append(bar)

# 如果请求失败则终止循环
if resp.status_code // 100 != 2:
msg: str = f"获取历史数据失败,状态码:{resp.status_code},信息:{resp.text}"
self.gateway.write_log(msg)
break
else:
data: dict = resp.json()
# 整个时间段内都未有数据,终止循环
if (not data["result"]) and (buf is None):
stop_time = datetime.utcfromtimestamp(tmp_start)
msg: str = f"获取历史数据为空,开始时间:{stop_time}"
self.gateway.write_log(msg)
break
# 剩下的时间端内不再有数据,终止循环
elif (not data["result"]) and (buf is not None):
msg: str = "获取历史数据完成"
self.gateway.write_log(msg)
break

buf: List[BarData] = []

for his_data in data["result"]:
bar = BarData(
symbol=req.symbol,
exchange=req.exchange,
datetime=datetime.fromtimestamp(his_data["time"] / 1000, timezone.utc),
interval=req.interval,
volume=his_data["volume"],
open_price=his_data["open"],
high_price=his_data["high"],
low_price=his_data["low"],
close_price=his_data["close"],
gateway_name=self.gateway_name
)
buf.append(bar)

begin: datetime = buf[0].datetime
end: datetime = buf[-1].datetime

buf = list(reversed(buf))
history.extend(buf)

msg: str = f"获取历史数据成功,{req.symbol} - {req.interval.value}{begin} - {end}"
self.gateway.write_log(msg)

# 最后一段查询结束后终止
if tmp_start == start:
msg: str = "获取历史数据完成"
self.gateway.write_log(msg)
break

# 更新下一个时间段
end = tmp_start
tmp_start = max(start, end - WINDOW_VT2FTX[req.interval] * limit)

history = list(reversed(history))
return history

def query_price(self, symbol: str) -> List:
@@ -682,7 +738,9 @@ def on_connected(self) -> None:
self.authenticate(self.api_key, self.api_secret_key)

for req in list(self.subscribed.values()):
self.subscribe(req)
self.resubscribe(req)

self.subscribe_private_channels()

def on_disconnected(self) -> None:
""""""
@@ -723,7 +781,6 @@ def subscribe(self, req: SubscribeRequest) -> None:
self.send_packet(subscribe_ticker)
subscribe_orderbook = {'op': 'subscribe', 'channel': 'orderbook', 'market': req.symbol}
self.send_packet(subscribe_orderbook)
self.subscribe_private_channels()

def unsubscribe(self, req: SubscribeRequest) -> None:
"""取消订阅行情"""
@@ -743,6 +800,18 @@ def unsubscribe(self, req: SubscribeRequest) -> None:
self.orderbook.pop(symbol)
self.holc.pop(symbol)

def resubscribe(self, req: SubscribeRequest) -> None:
"""重连后订阅行情"""
if req.symbol not in symbol_contract_map:
self.gateway.write_log(f"找不到该合约代码{req.symbol}")
return

self.subscribed[req.vt_symbol] = req
subscribe_ticker = {'op': 'subscribe', 'channel': 'ticker', 'market': req.symbol}
self.send_packet(subscribe_ticker)
subscribe_orderbook = {'op': 'subscribe', 'channel': 'orderbook', 'market': req.symbol}
self.send_packet(subscribe_orderbook)

def subscribe_private_channels(self) -> None:
"""订阅个人orders和fills行情"""
self.send_packet({'op': 'subscribe', 'channel': 'fills'})
@@ -762,9 +831,9 @@ def on_packet(self, packet: Any) -> None:
# 更新推送
if packet["type"] == "update":
channel = packet["channel"]
symbol = packet["market"]

if channel == "ticker":
symbol = packet["market"]
self.holc[symbol]["last_price"] = packet["data"]["last"]
tm_mday = time.gmtime(packet["data"]["time"]).tm_mday

@@ -818,8 +887,6 @@ def on_packet(self, packet: Any) -> None:
self.gateway.on_tick(copy(tick))

elif channel == "trades":
# self.last_price = packet["data"][0]["price"]
# self.last_volume = packet["data"][0]["size"]
pass

elif channel == "fills":
@@ -837,6 +904,7 @@ def on_packet(self, packet: Any) -> None:
)
self.gateway.on_trade(trade)
self.gateway.query_position()
self.gateway.query_account()

elif channel == "orders":
d = packet["data"]