Skip to content

Commit

Permalink
Merge pull request #1 from quant-vn/base
Browse files Browse the repository at this point in the history
Base
  • Loading branch information
dung1t authored Sep 13, 2024
2 parents 8203c3c + 5e2b9d7 commit 7e9a0ab
Show file tree
Hide file tree
Showing 23 changed files with 771 additions and 2 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish vBroker using Poetry

on:
release:
types: [created]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.5.1
virtualenvs-create: true
virtualenvs-in-project: true
- name: Install dependencies
run: poetry install
- name: Build package
run: poetry build
- name: Publish to PyPI
env:
PYPI_USERNAME: __token__
PYPI_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
poetry config pypi-token.pypi $PYPI_PASSWORD
poetry publish
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# vbroker
vBroker
# vBroker
vBroker: A Python wrapper for Viet Nam Broker API

![PyPI - Version](https://img.shields.io/pypi/v/vbroker)
![Python Version](https://img.shields.io/pypi/pyversions/vbroker)
![PyPI - Downloads](https://img.shields.io/pypi/dm/vbroker)
![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)

## Installation
```bash
pip install vbroker
```

## Usage
423 changes: 423 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[tool.poetry]
name = "vbroker"
version = "0.1.0"
license = "MIT"
description = "vBroker: A Python wrapper for Viet Nam Broker API"
repository = "https://github.com/quant-vn/vbroker"
homepage = "https://quant.vn"
authors = ["thync <thync@outlook.com>"]
readme = ["README.md", "LICENSE"]
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Topic :: Software Development :: Libraries :: Python Modules"
]

[tool.poetry.dependencies]
python = "^3.9"
requests = "^2.32.3"
websockets = "^13.0.1"
pydantic = "^2.9.1"
pyjwt = "^2.9.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from vbroker import Broker, Config, EnumBroker

broker = Broker(
broker=EnumBroker.SSI.value,
config=Config(
ssi_broker_id="",
ssi_broker_secret="",
ssi_broker_private_key=""
)
)

print(broker.api.get_token())
4 changes: 4 additions & 0 deletions vbroker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
""" Broker module. """
from .enum_broker import EnumBroker # noqa: F401
from .broker import Broker # noqa: F401
from .config import Config # noqa: F401
23 changes: 23 additions & 0 deletions vbroker/broker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
""" Broker module for broker API and HUB. """
from .config import Config

from .enum_broker import EnumBroker
from .interface_broker_api import IBrokerAPI
from .interface_broker_hub import IBrokerHUB

from .ssi import SSIBrokerAPI, SSIBrokerHUB


class Broker:
def __init__(self, broker: EnumBroker, config: Config) -> None:
if broker == EnumBroker.SSI.value:
self.__api: IBrokerAPI = SSIBrokerAPI(config)
self.__hub: IBrokerHUB = SSIBrokerHUB(self.__api)

@property
def api(self) -> IBrokerAPI:
return self.__api

@property
def hub(self) -> IBrokerHUB:
return self.__hub
19 changes: 19 additions & 0 deletions vbroker/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
""" Configuration module for the broker. """
from typing import Optional

from .utils import BaseModel


class Config(BaseModel):
"""
Configuration class for the broker.
Attributes:
ssi_broker_id (Optional[str]): The SSI broker ID.
ssi_broker_secret (Optional[str]): The SSI broker secret.
ssi_broker_private_key (Optional[str]): The SSI broker private key.
"""
# SSI datafeed information
ssi_broker_id: Optional[str] = None
ssi_broker_secret: Optional[str] = None
ssi_broker_private_key: Optional[str] = None
14 changes: 14 additions & 0 deletions vbroker/enum_broker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
This module contains the EnumDatafeed class which is used for handling datafeed enumerations.
"""
from .utils import EnumHandler


class EnumBroker(EnumHandler):
"""
This module contains the EnumDatafeed class which is used for handling datafeed enumerations.
Attributes:
SSI (str): Represents the SSI datafeed.
"""
SSI = 'ssi'
SSI_PAPER = 'ssi_paper'
12 changes: 12 additions & 0 deletions vbroker/interface_broker_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
""" Interface for Broker API """
from abc import ABC, abstractmethod
from .config import Config


class IBrokerAPI(ABC):
def __init__(self, config: Config):
self.config: Config = config

@abstractmethod
def get_token(self) -> str:
return NotImplemented
33 changes: 33 additions & 0 deletions vbroker/interface_broker_hub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
""" This module defines the interface for a broker HUB. """
from abc import ABC, abstractmethod


class IBrokerHUB(ABC):
def __init__(self, api):
self.api = api
self.__token: str = None

@property
def token(self) -> str:
"""
Returns the token used for authentication.
Returns:
str: The authentication token.
"""
return self.__token

@token.setter
def token(self, value: str):
self.__token = value

@abstractmethod
async def listen(self, args, on_message):
"""
Listens for incoming messages from the broker.
Args:
args: The arguments for the listen method.
on_message: The callback function to be executed when a message is received.
Returns:
NotImplemented
"""
return NotImplemented
2 changes: 2 additions & 0 deletions vbroker/ssi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .api import SSIBrokerAPI # noqa: F401
from .hub import SSIBrokerHUB # noqa: F401
9 changes: 9 additions & 0 deletions vbroker/ssi/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ..interface_broker_api import IBrokerAPI


class SSIBrokerAPI(IBrokerAPI):
def __init__(self, config):
self.config = config

def get_token(self):
return "token"
26 changes: 26 additions & 0 deletions vbroker/ssi/constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
""" Constant for SSI datafeed """
API_URL = "https://fc-tradeapi.ssi.com.vn"
API_PAPER_URL = "https://fc-paperapi.ssi.com.vn"
ENDPOINT_OTP = "api/v2/Trading/GetOTP"
ENDPOINT_AUTH = "api/v2/Trading/AccessToken"

ENDPOINT_EQUITY_NEW_ORDER = "api/v2/Trading/NewOrder"
ENDPOINT_EQUITY_MODIFY_ORDER = "api/v2/Trading/ModifyOrder"
ENDPOINT_EQUITY_CANCEL_ORDER = "api/v2/Trading/CancelOrder"
ENDPOINT_EQUITY_POSITION = "api/v2/Trading/stockPosition"
ENDPOINT_EQUITY_ACCOUNT_BALANCE = "api/v2/Trading/cashAcctBal"

ENDPOINT_DERIVATIVE_NEW_ORDER = "api/v2/Trading/derNewOrder"
ENDPOINT_DERIVATIVE_MODIFY_ORDER = "api/v2/Trading/derModifyOrder"
ENDPOINT_DERIVATIVE_CANCEL_ORDER = "api/v2/Trading/derCancelOrder"
ENDPOINT_DERIVATIVE_POSITION = "api/v2/Trading/derivPosition"
ENDPOINT_DERIVATIVE_ACCOUNT_BALANCE = "api/v2/Trading/derivAcctBal"

ENDPOINT_ORDER_HISTORY = "api/v2/Trading/orderHistory"
ENDPOINT_ORDERBOOK = "api/v2/Trading/orderBook"
ENDPOINT_MAX_BUY_QUANTITY = "api/v2/Trading/maxBuyQty"
ENDPOINT_MAX_SELl_QUANTITY = "api/v2/Trading/maxSellQty"

HUB_URL = "wss://fc-tradehub.ssi.com.vn//v2.0/signalr"
HUB_PAPER_URL = "wss://fc-paperhub.ssi.com.vn//v2.0/signalr"
HUB = "BroadcastHubV2"
9 changes: 9 additions & 0 deletions vbroker/ssi/hub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ..interface_broker_hub import IBrokerHUB


class SSIBrokerHUB(IBrokerHUB):
def __init__(self, api):
super().__init__(api)

async def listen(self, args, on_message):
return NotImplemented
Empty file added vbroker/ssi/model.py
Empty file.
5 changes: 5 additions & 0 deletions vbroker/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .enum_handler import EnumHandler # noqa: F401
from .model_handler import BaseModel, AliasChoices, Field # noqa: F401
from .request_handler import request_handler # noqa: F401
from .socket_handler import SocketListener # noqa: F401
from .jwt_handler import jwt_handler # noqa: F401
18 changes: 18 additions & 0 deletions vbroker/utils/enum_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from enum import Enum


class EnumHandler(Enum):
def __str__(self) -> str:
return str.__str__(self)

@classmethod
def keys(cls):
return cls._member_names_

@classmethod
def values(cls):
return list(cls._value2member_map_.keys())

@classmethod
def items(cls):
return list(cls)
13 changes: 13 additions & 0 deletions vbroker/utils/jwt_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import jwt
import time


class JWTHandler:
def is_expired(self, bearer_token: str) -> bool:
if bearer_token is None:
return True
decoded = jwt.decode(bearer_token.replace("Bearer ", ""), options={"verify_signature": False})
return int(time.time()) > (decoded.get("exp") - 1)


jwt_handler = JWTHandler()
8 changes: 8 additions & 0 deletions vbroker/utils/model_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel as BM, ConfigDict, Field, AliasChoices # noqa: F401


class BaseModel(BM):
model_config: ConfigDict = ConfigDict(
use_enum_values=True,
validate_return=True
)
33 changes: 33 additions & 0 deletions vbroker/utils/request_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import time
import requests


class RequestHandler:
def __init__(self) -> None:
self.__timeout: int = 10

def get(self, url: str, headers: dict, params: dict, limit: int = 0) -> dict:
try:
res = requests.get(url, headers=headers, params=params, timeout=self.__timeout)
if limit:
time.sleep(limit)
res.raise_for_status()
return res.json()
except requests.RequestException as e:
raise e

def post(self, url: str, headers: dict, data: dict = {}, limit: int = 0) -> dict:
try:
if data:
res = requests.post(url, headers=headers, json=data, timeout=self.__timeout)
else:
res = requests.post(url, headers=headers, timeout=self.__timeout)
if limit:
time.sleep(limit)
res.raise_for_status()
return res.json()
except requests.RequestException as e:
raise e


request_handler = RequestHandler()
27 changes: 27 additions & 0 deletions vbroker/utils/socket_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
""" This module is responsible for handling the socket connection to the server. """
import websockets


class SocketListener:
"""
A class that handles socket connections.
Methods:
--------
connect_socket_server(url: str, headers: dict) -> websockets.WebSocketClientProtocol:
Connects to a socket server using the specified URL and headers.
"""
def connect_socket_server(self, url: str, headers: dict) -> websockets.WebSocketClientProtocol:
"""
Connects to a socket server using the specified URL and headers.
Parameters:
-----------
url : str
The URL of the socket server to connect to.
headers : dict
Additional headers to include in the connection request.
Returns:
--------
websockets.WebSocketClientProtocol
The WebSocket client protocol object representing the connection.
"""
return websockets.connect(url, extra_headers=headers)

0 comments on commit 7e9a0ab

Please sign in to comment.