Contributing Guidelines

Thanks for taking the time to contribute.

The following is a set of guidelines for contributing to VietFin. These are mostly guidelines, not rules.

Code style

Guidance on code style

  • Use ruff to format code following the PEP 8 style guide, configured in pyproject.toml. For example:

    • Use 4 spaces per indentation level

    • Use snake_case for variable and function names

    • Use PascalCase for class names

    • Use ALL_CAPS for constants

    • Use double-quoted strings

    • Limit all lines to a maximum of 80 characters

  • Use numpy style docstrings

  • Use Type Hints for functions and classes

Branch strategy

This project uses a two-layer branch model.

  • dev is the main branch. All new features and bug fixes are made on this branch.

  • main is the stable branch. This branch is updated only when a new release is made.

branching-strategy

Credit: yfinance

Contributing to the codebase

Work on an issue

Pick or suggest an issue, by going through the issue tracker, which you would like to work on.

Setup your local development environment. Then start coding.

Setup local development environment

We use the combination of conda to manage virtual environments and poetry to manage dependencies.

  • Clone the dev branch of the VietFin repository. E.g. git clone -b dev https://github.com/vietfin/vietfin.git

  • Install conda

  • Use conda to create a new virtual environment named dev-vietfin with Python 3.10. E.g. conda create -n dev-vietfin python=3.10

  • Activate the environment. E.g. conda activate dev-vietfin

  • Install poetry. E.g. conda install poetry

  • Install dependencies with optional dependency group dev for developement purposes. E.g. poetry install --with dev

Open a Pull Request

When you have resolved your issue, open a pull request (PR) in the VietFin repository. Please adhere to the following guidelines:

Git Process

  • Ensure that your branch is up to date with the dev branch of VietFin repository. E.g. git pull upstream dev

  • Create a new git branch for your feature. E.g. git checkout -b feat/AmazingFeature

  • Check the files you have touched using git status

  • Stage the files you want to commit. E.g. git add src/vietfin/funds/funds.py

  • Write a concise commit message under 50 characters. E.g. git commit -m "feat: add AmazingFeature"

  • Push your changes to the appropriate branch in your fork. E.g. git push origin feat/AmazingFeature

  • Go to your GitHub, then open a PR in the VietFin repository

Contributing to documentation

The documentation is written in reStructuredText syntax, built with Sphinx and Shibuya theme.

If you want to modify/add documentation and see how changes will be rendered, you can setup a local development virtual environment as follows.

  • Clone the dev branch of the VietFin repository. E.g. git clone -b dev https://github.com/vietfin/vietfin.git

  • Install conda

  • Create a new conda environment named docs-vietfin with Python 3.10. E.g. conda create -n docs-vietfin python=3.10

  • Activate the environment. E.g. conda activate docs-vietfin

  • Install poetry. E.g. conda install poetry

  • Install dependencies with optional dependency group docs for writing docs purposes. E.g. poetry install --with docs

You can also suggest to edit a single page of the docs, simply by clicking the Edit this page button on the right side bar of the VietFin docs page. This will open a GitHub website where you can edit the single docs page and submit a PR to the VietFin repository.

Test suite

The ./tests folder contains the main VietFin test suite.

At the moment, the test suite contains only unit tests. These tests assert that all VietFin functionality work as intended with default parameters. Read the Testing Guidelines for more details.

Versioning

VietFin adheres to the semantic versioning specification.

Codebase structure

At high level, the codebase is structured as 3 layers:

The 1st layer, “Facade”, is a recreation of the OpenBB’s hierarchical structure for user-facing commands. It includes a “wrapper” class named VietFin, and its components (i.e. Funds, Equity, EquityPrice, etc.). The word “Façade” coming from Facade Design Pattern.

classDiagram class Funds { +search()$ +historical()$ +holdings()$ } class Equity Equity: +search()$ class EquityPrice { +historical()$ +quote()$ } class EquityFundamental { +management()$ +ratios()$ +dividends()$ +income()$ +balance()$ +cash()$ +multiples()$ } class DerivativesFutures { +historical()$ +quote()$ } class Index Index: +search()$ VietFin <-- Funds VietFin <-- Equity Equity <-- EquityPrice Equity <-- EquityFundamental VietFin <-- Derivatives Derivatives <-- DerivativesFutures VietFin <-- Index

The 2nd layer, “Factory”, is an implementation of the data fetching from API providers. It includes the abstract interface (i.e. IFunds, IEquity, etc.) and its real/concrete implementations for each data provider (i.e. FundsFmarket, EquitySsi, etc.). I applied the Factory Design Pattern to develop this layer. I expect that it should be coherent to add new features (e.g. more asset types, groups of commands) and to integrate new data providers.

classDiagram class IFunds { <<interface>> search()* historical()* holdings()* } class FundsFmarket { +search() +historical() +holdings() } IFunds <|.. FundsFmarket class FundsFactory { +get_provider() +Dict funds_providers_implementations } FundsFactory <-- IFunds

The 3rd layer is “Data Standardization”, an accomodation of various data structure, which is a fork of the OpenBB’s Data Standardization Infrastructure. It includes the abstract classes (i.e. VfObject, Data) and the data models for each API provider (i.e. FmarketFundInfoData, SsiEquitySearchData, etc.).

classDiagram class FundsFmarket { +search() +historical() +holdings() } class FmarketFundInfoData { +fund_id: int +short_name: str +name: str +inception_date: ValidatedDatetime | None +management_fee: float | None +nav: float | None +fund_owner: FmarketFundOwnerData | T | None +fund_type: FmarketFundTypesData | T | None } class FmarketFundHoldingsData { +stock_code: str +industry: str +net_asset_percent: float +type_asset: str +update_at: ValidatedDatetime | None } class FmarketFundHistoricalNavData { +date: str +nav_per_share: float +fund_id: int } FundsFmarket <-- FmarketFundInfoData FundsFmarket <-- FmarketFundHoldingsData FundsFmarket <-- FmarketFundHistoricalNavData class Data { <<abstract>> -__alias_dict__: Dict[str, str] -model_config: ConfigDict } Data <-- FmarketFundInfoData Data <-- FmarketFundHoldingsData Data <-- FmarketFundHistoricalNavData

For example, with the component Funds of VietFin package, these 3 layers is interpreted as follows.

  • The logic of creating the FundsFmarket object is encapsulated in the FundsFactory abstract interface.

  • The real/concrete implementation of the data fetching from provider Fmarket is in the FundsFmarket class.

  • The data standardized model is the FmarketFundInfoData class and its peers, which is located in /vietfin/providers/fmarket/models/.

Codebase Structure

I followed the six types of relationships in UML class diagrams and mermaid.js class diagrams syntax to create this class diagram, which is expected to give a good bird-eye view of the codebase.

classDiagram class IFunds <<interface>> IFunds IFunds : search()* IFunds : historical()* IFunds : holdings()* class IEquity { <<interface>> search()* profile()* } class IEquityPrice <<interface>> IEquityPrice IEquityPrice : historical()* IEquityPrice : quote()* class IEquityOwnership { <<interface>> insider_trading()* major_holders()* foreign_trading()* prop_trading()* } class IEquityCalendar { <<interface>> events()* } class IEquityFundamental { <<interface>> management()* ratios()* dividends()* income()* balance()* cash()* multiples()* } class IEquityDiscovery { <<interface>> active()* gainers()* losers()* } class IDerivativesFutures { <<interface>> historical()* quote()* search()* } class IDerivativesCoveredWarrant { <<interface>> search()* } class IEtf { <<interface>> historical()* search()* } class IIndex { <<interface>> search()* constituents()* } class IIndexPrice { <<interface>> historical()* } class INews { <<interface>> company()* } class FundsFmarket { +search() +historical() +holdings() } class EquitySsi EquitySsi : +search() class EquityWifeed EquityWifeed : +search() class EquityTcbs { +profile() } class EquityPriceDnse EquityPriceDnse : +historical() class EquityPriceTcbs { +historical() +quote() } class EquityPriceSsi { +historical() +quote() } class EquityOwnershipTcbs { +insider_trading() +major_holders() } class EquityCalendarTcbs { +events() } class EquityFundamentalTcbs { +management() +ratios() +dividends() +income() +balance() +cash() +multiples() } class EquityDiscoverySsi { +active() +gainers() +losers() } class DerivativesFuturesTcbs { +historical() } class DerivativesFuturesSsi { +search() +quote() } class DerivativesCoveredWarrantSsi { +search() } class EtfTcbs { +historical() } class EtfDnse { +historical() } class EtfSsi { +search() } class IndexSsi { +search() +constituents() } class IndexPriceTcbs { +historical() } class IndexPriceDnse { +historical() } class NewsTcbs { +company() } class EquityOwnershipCafef { +foreign_trading() +prop_trading() } IFunds <|.. FundsFmarket IEquity <|.. EquitySsi IEquity <|.. EquityWifeed IEquity <|.. EquityTcbs IEquityPrice <|.. EquityPriceDnse IEquityPrice <|.. EquityPriceTcbs IEquityPrice <|.. EquityPriceSsi IEquityOwnership <|.. EquityOwnershipTcbs IEquityOwnership <|.. EquityOwnershipCafef IEquityCalendar <|.. EquityCalendarTcbs IEquityFundamental <|.. EquityFundamentalTcbs IEquityDiscovery <|.. EquityDiscoverySsi IDerivativesFutures <|.. DerivativesFuturesTcbs IDerivativesFutures <|.. DerivativesFuturesSsi IDerivativesCoveredWarrant <|.. DerivativesCoveredWarrantSsi IEtf <|.. EtfTcbs IEtf <|.. EtfDnse IEtf <|.. EtfSsi IIndex <|.. IndexSsi IIndexPrice <|.. IndexPriceTcbs IIndexPrice <|.. IndexPriceDnse INews <|.. NewsTcbs class FundsFactory { +get_provider() +Dict providers_implementations } class EquityFactory { +get_provider() +Dict providers_implementations } class EquityPriceFactory { +get_provider() +Dict providers_implementations } class EquityOwnershipFactory { +get_provider() +Dict providers_implementations } class EquityCalendarFactory { +get_provider() +Dict providers_implementations } class EquityFundamentalFactory { +get_provider() +Dict providers_implementations } class EquityDiscoveryFactory { +get_provider() +Dict providers_implementations } class DerivativesFuturesFactory { +get_provider() +Dict providers_implementations } class DerivativesCoveredWarrantFactory { +get_provider() +Dict providers_implementations } class EtfFactory { +get_provider() +Dict providers_implementations } class IndexFactory { +get_provider() +Dict providers_implementations } class IndexPriceFactory { +get_provider() +Dict providers_implementations } class NewsFactory { +get_provider() +Dict providers_implementations } FundsFactory <-- IFunds EquityFactory <-- IEquity EquityPriceFactory <-- IEquityPrice EquityOwnershipFactory <-- IEquityOwnership EquityCalendarFactory <-- IEquityCalendar EquityFundamentalFactory <-- IEquityFundamental EquityDiscoveryFactory <-- IEquityDiscovery DerivativesFuturesFactory <-- IDerivativesFutures DerivativesCoveredWarrantFactory <-- IDerivativesCoveredWarrant EtfFactory <-- IEtf IndexFactory <-- IIndex IndexPriceFactory <-- IIndexPrice NewsFactory <-- INews class Funds { +search()$ +historical()$ +holdings()$ } class Equity { +search()$ +profile()$ } class EquityPrice { +historical()$ +quote()$ } class EquityFundamental { +management()$ +ratios()$ +dividends()$ +income()$ +balance()$ +cash()$ +multiples()$ } class EquityOwnership { +insider_trading()$ +major_holders()$ +foreign_trading()$ +prop_trading()$ } class EquityCalendar EquityCalendar: +events()$ class EquityDiscovery { +active()$ +gainers()$ +losers()$ } class DerivativesFutures { +historical()$ +quote()$ +search()$ } class DerivativesCoveredWarrant { +search()$ } class Etf { +search()$ +historical()$ } class Index { +search()$ +constituents()$ } class IndexPrice { +historical()$ } class News { +company()$ } Funds .. FundsFactory Equity .. EquityFactory EquityPrice .. EquityPriceFactory EquityFundamental .. EquityFundamentalFactory EquityOwnership .. EquityOwnershipFactory EquityCalendar .. EquityCalendarFactory EquityFundamental .. EquityFundamentalFactory EquityDiscovery .. EquityDiscoveryFactory DerivativesFutures .. DerivativesFuturesFactory DerivativesCoveredWarrant .. DerivativesCoveredWarrantFactory Etf .. EtfFactory Index .. IndexFactory IndexPrice .. IndexPriceFactory News .. NewsFactory VietFin <-- Funds VietFin <-- Equity VietFin <-- Derivatives VietFin <-- Etf VietFin <-- Index VietFin <-- News Equity <-- EquityPrice Equity <-- EquityFundamental Equity <-- EquityOwnership Equity <-- EquityCalendar Equity <-- EquityDiscovery Derivatives <-- DerivativesFutures Derivatives <-- DerivativesCoveredWarrant Index <-- IndexPrice

Directory structure

.
├── abstract                                    # abstract classes and interfaces
│   ├── __init__.py
│   ├── data.py                                 # Data class, the heart of Data Standardization layer of VietFin
│   ├── factory.py                              # Factory classes for each menu of commands
│   ├── interface.py                            # Abstract interfaces for each menu of commands
│   └── vfobject.py                             # VfObject class, every command will return this class as the command output.
├── components                                  # The 1st layer, “Facade” of the VietFin package
│   ├── __init__.py
│   ├── derivatives.py                          # Menu of commands related to Derivatives
│   └── ...
├── providers                                   # The functions and data model which crawl data from each data provider
│   ├── cafef                                   # Provider CafeF
│   │   ├── models                              # Data models used in the provider CafeF
│   │   │   ├── __init__.py
│   │   │   ├── equity_ownership_foreign.py
│   │   │   └── ...
│   │   ├── utils                               # Functions used to crawl data from the provider CafeF
│   │   │   ├── equity_ownership_foreign.py
│   │   │   ├── ...
│   │   │   └── helpers.py
│   │   ├── __init__.py
│   │   └── provider.py                         # The concrete implementation of the provider CafeF
│   ├── dnse                                    # Provider DNSE
│   ├── fmarket                                 # Provider Fmarket
│   ├── ssi                                     # Provider SSI
│   ├── tcbs                                    # Provider TCBS
│   ├── vdsc                                    # Provider VDSC Rong Viet
│   ├── wifeed                                  # Provider WiFeed
│   └── __init__.py
├── utils                                       # Utility functions used in the package
│   ├── __init__.py
│   ├── errors.py                               # Custom exceptions
│   └── helpers.py                              # Helpers functions
├── __init__.py
└── py.typed                                    # Dummy file to enable static type hints

Based on this codebase’s structure, when I want to add a new asset type (e.g. Etf), I need to:

  • Create a new abstract interface for the new asset type. E.g. class IEtf in /abstract/interface.py

  • Create a new Factory class to represent the concrete implementation of the new asset type. E.g. class EtfFactory in /abstract/factory.py

  • Create a new concrete implementation of the new asset type and its data provider. E.g. class EtfProvider in the appropriate /providers/provider_name/provider.py

  • Create client code linked to the new Factory class. E.g. class Etf in /components/etf.py

  • Initialize the new asset class in the VietFin class. E.g. self.etf = Etf() in /__init__.py

Similarly, when I want to add a new command to an existing asset type, I need to:

  • Create a new method in the abtract interface of the asset type. E.g. new_method() in IEtf class in /abstract/interface.py

  • Implement this new method in the concrete implementation of the asset type. E.g. new_method() in class EtfProvider in the appropriate /providers/provider_name/provider.py

  • Create a new function and new data model to be called by the new command. E.g. get_data_new_method() in /providers/provider_name/utils/new_method.py and new_data_model in /providers/provider_name/utils/new_data_model.py

  • Add this new method into all existing provider concrete class, which share the abstract interface. E.g. I also add new_method() into the provider.py of each existing data provider which implements IEtf

When I want to add a new data provider for existing asset type, I need to:

  • Create a new concrete implementation of the new data provider. E.g. class EtfProvider in the appropriate /providers/provider_name/provider.py

  • Add this new concrete implementation to the Factory class. E.g. providers_implementations in class EtfFactory in /abstract/factory.py

NOTE: This approach is not DRY. I’m open to suggestions to improve the codebase.