├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── example1.png ├── example2.png ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src └── JesseTradingViewLightReport │ ├── __init__.py │ └── generateReport.py ├── tests ├── __init__.py └── test_simple.py └── tox.ini /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to 2 | # help make automated releases for this project 3 | 4 | name: Release 5 | 6 | on: 7 | push: 8 | branches: 9 | - release 10 | 11 | jobs: 12 | build-and-publish: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v1 19 | with: 20 | python-version: '3.x' 21 | - name: Install build dependencies 22 | run: python -m pip install -U setuptools wheel build 23 | - name: Build 24 | run: python -m build . 25 | - name: Publish 26 | uses: pypa/gh-action-pypi-publish@master 27 | with: 28 | password: ${{ secrets.pypi_password }} 29 | skip_existing: true 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to 2 | # help test this project 3 | 4 | name: Test 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | python: ['3.8', '3.9', '3.10'] 13 | platform: [ubuntu-latest] #, macos-latest, windows-latest] 14 | runs-on: ${{ matrix.platform }} 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python }} 19 | uses: actions/setup-python@v3 20 | with: 21 | python-version: ${{ matrix.python }} 22 | - name: Install ta-lib 23 | run: | 24 | if ([ "$RUNNER_OS" = "macOS" ]); then 25 | brew install ta-lib 26 | fi 27 | if ([ "$RUNNER_OS" = "Linux" ]); then 28 | if [ ! -f "$GITHUB_WORKSPACE/ta-lib/src" ]; then wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz -q && tar -xzf ta-lib-0.4.0-src.tar.gz; fi 29 | cd ta-lib/ 30 | ./configure --prefix=/usr 31 | if [ ! -f "$HOME/ta-lib/src" ]; then make; fi 32 | sudo make install 33 | cd 34 | fi 35 | if ([ "$RUNNER_OS" = "Windows" ]); then 36 | curl -sL http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-msvc.zip -o $GITHUB_WORKSPACE/ta-lib.zip --create-dirs && 7z x $GITHUB_WORKSPACE/ta-lib.zip -o/c/ta-lib && mv /c/ta-lib/ta-lib/* /c/ta-lib/ && rm -rf /c/ta-lib/ta-lib && cd /c/ta-lib/c/make/cdr/win32/msvc && nmake 37 | fi 38 | - name: Install test dependencies 39 | run: python -m pip install -U tox 40 | - name: Test 41 | run: python -m tox -e py 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # general things to ignore 2 | build/ 3 | dist/ 4 | *.egg-info/ 5 | *.egg 6 | *.py[cod] 7 | __pycache__/ 8 | *.so 9 | *~ 10 | 11 | # due to using tox and pytest 12 | .tox 13 | .cache 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jesse.Trade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jesse tradingview light reporting library 2 | 3 | Generate an html document containing all of the scripts and data to load tradingview and review the results. This can be generated within a strategy at regular intervals to review the results live. 4 | 5 | The library is published to pypi: https://pypi.org/project/JesseTradingViewLightReport/ 6 | 7 | So to install, from a command prompt where you would be running python: 8 | ``` 9 | pip install JesseTradingViewLightReport 10 | ``` 11 | 12 | Upgrade: 13 | ``` 14 | pip install JesseTradingViewLightReport --upgrade 15 | ``` 16 | 17 | To generate just the candlestick, volume, and order report - Add the following to your strategy: 18 | ```python 19 | import JesseTradingViewLightReport 20 | 21 | def terminate(self): 22 | JesseTradingViewLightReport.generateReport() 23 | ``` 24 | 25 | But you can also add custom data for example: 26 | 27 | ```python 28 | JesseTradingViewLightReport.generateReport( 29 | customData={ 30 | "atr":{"data":self.atr, "options":{"pane":1, "color":'rgba(251, 192, 45, 1)'}}, 31 | 'two':{"data":self.candles[:,1]-5, "options":{"pane":2}, "type":"HistogramSeries"}, 32 | 'three':{"data":self.candles[:,1]+5, "options":{"pane":2, "color":'purple'}} 33 | } 34 | ) 35 | ``` 36 | 37 | ![demo](https://github.com/qwpto/JesseTradingViewLightReport/blob/release/example1.png?raw=true) 38 | 39 | Available types are: 40 | - LineSeries 41 | - HistogramSeries 42 | - AreaSeries 43 | - BaselineSeries 44 | 45 | You may be able to use: 46 | - BarSeries 47 | - CandlestickSeries 48 | 49 | For more information on plot types and options see: 50 | - https://tradingview.github.io/lightweight-charts/docs/series-types 51 | - https://tradingview.github.io/lightweight-charts/docs/api 52 | - https://www.tradingview.com/lightweight-charts/ 53 | 54 | For the moment, the data will need to be the same length as the number of candles you would receive from self.candles 55 | 56 | The different panes can be resized by dragging them with the cursor. 57 | 58 | It is also possible to change the candle colors to PVSRA candles with the option: 59 | ```python 60 | JesseTradingViewLightReport.generateReport(chartConfig={'isPvsra':True}) 61 | ``` 62 | However, at the moment it is not possible to use multiple panes with this option. This restiction will be removed in a future release. 63 | ![demo2](https://github.com/qwpto/JesseTradingViewLightReport/blob/release/example2.png?raw=true) 64 | 65 | It is possible to also plot the profit and loss with for example: 66 | ```python 67 | JesseTradingViewLightReport.generateReport(chartConfig={'pnl':True}) 68 | ``` 69 | 70 | The generateReport function returns the relative location of the file. You can also find it inside where you're running the jesse strategy from there will be a folder called storage, inside that this plugin creates a folder called JesseTradingViewLightReport. Then each time you run a strategy with different parameters it will create a unique file called something like 77cbda27-6eec-48b6-90fb-621656d9e9d8.html 71 | 72 | So in this example it'll be: 73 | c:/whereveryourunjesse/storage/JesseTradingViewLightReport/77cbda27-6eec-48b6-90fb-621656d9e9d8.html 74 | 75 | CHANGELOG: 76 | 1.1.0 - added support for jesse 0.39.+, and added PNL calculation for all orders. With accumulated PNL plotting. 77 | -------------------------------------------------------------------------------- /example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qwpto/JesseTradingViewLightReport/1d5d5d4bd67b4096c99fcd923610bef5a4594049/example1.png -------------------------------------------------------------------------------- /example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qwpto/JesseTradingViewLightReport/1d5d5d4bd67b4096c99fcd923610bef5a4594049/example2.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # These are the assumed default build requirements from pip: 3 | # https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support 4 | requires = ["setuptools>=43.0.0", "wheel"] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | # This includes the license file(s) in the wheel. 3 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 4 | license_files = LICENSE.txt 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/guides/distributing-packages-using-setuptools/ 5 | https://github.com/pypa/sampleproject 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | import pathlib 11 | 12 | here = pathlib.Path(__file__).parent.resolve() 13 | 14 | # Get the long description from the README file 15 | long_description = (here / "README.md").read_text(encoding="utf-8") 16 | 17 | # Arguments marked as "Required" below must be included for upload to PyPI. 18 | # Fields marked as "Optional" may be commented out. 19 | 20 | setup( 21 | # This is the name of your project. The first time you publish this 22 | # package, this name will be registered for you. It will determine how 23 | # users can install this project, e.g.: 24 | # 25 | # $ pip install JesseTradingViewLightReport 26 | # 27 | # And where it will live on PyPI: https://pypi.org/project/JesseTradingViewLightReport/ 28 | # 29 | # There are some restrictions on what makes a valid project name 30 | # specification here: 31 | # https://packaging.python.org/specifications/core-metadata/#name 32 | name="JesseTradingViewLightReport", # Required 33 | # Versions should comply with PEP 440: 34 | # https://www.python.org/dev/peps/pep-0440/ 35 | # 36 | # For a discussion on single-sourcing the version across setup.py and the 37 | # project code, see 38 | # https://packaging.python.org/guides/single-sourcing-package-version/ 39 | version="1.1.0", # Required 40 | # This is a one-line description or tagline of what your project does. This 41 | # corresponds to the "Summary" metadata field: 42 | # https://packaging.python.org/specifications/core-metadata/#summary 43 | description="Generate an html document containing all of the scripts and data to load tradingview and review the results", # Optional 44 | # This is an optional longer description of your project that represents 45 | # the body of text which users will see when they visit PyPI. 46 | # 47 | # Often, this is the same as your README, so you can just read it in from 48 | # that file directly (as we have already done above) 49 | # 50 | # This field corresponds to the "Description" metadata field: 51 | # https://packaging.python.org/specifications/core-metadata/#description-optional 52 | long_description=long_description, # Optional 53 | # Denotes that our long_description is in Markdown; valid values are 54 | # text/plain, text/x-rst, and text/markdown 55 | # 56 | # Optional if long_description is written in reStructuredText (rst) but 57 | # required for plain-text or Markdown; if unspecified, "applications should 58 | # attempt to render [the long_description] as text/x-rst; charset=UTF-8 and 59 | # fall back to text/plain if it is not valid rst" (see link below) 60 | # 61 | # This field corresponds to the "Description-Content-Type" metadata field: 62 | # https://packaging.python.org/specifications/core-metadata/#description-content-type-optional 63 | long_description_content_type="text/markdown", # Optional (see note above) 64 | # This should be a valid link to your project's main homepage. 65 | # 66 | # This field corresponds to the "Home-Page" metadata field: 67 | # https://packaging.python.org/specifications/core-metadata/#home-page-optional 68 | url="https://github.com/qwpto/JesseTradingViewLightReport", # Optional 69 | # This should be your name or the name of the organization which owns the 70 | # project. 71 | author="qwpto", # Optional 72 | # This should be a valid email address corresponding to the author listed 73 | # above. 74 | author_email="109690977+qwpto@users.noreply.github.com", # Optional 75 | # Classifiers help users find your project by categorizing it. 76 | # 77 | # For a list of valid classifiers, see https://pypi.org/classifiers/ 78 | classifiers=[ # Optional 79 | # How mature is this project? Common values are 80 | # 3 - Alpha 81 | # 4 - Beta 82 | # 5 - Production/Stable 83 | "Development Status :: 3 - Alpha", 84 | # Indicate who your project is intended for 85 | "Intended Audience :: Developers", 86 | "Topic :: Software Development :: Build Tools", 87 | # Pick your license as you wish 88 | "License :: OSI Approved :: MIT License", 89 | # Specify the Python versions you support here. In particular, ensure 90 | # that you indicate you support Python 3. These classifiers are *not* 91 | # checked by 'pip install'. See instead 'python_requires' below. 92 | "Programming Language :: Python :: 3", 93 | "Programming Language :: Python :: 3.7", 94 | "Programming Language :: Python :: 3.8", 95 | "Programming Language :: Python :: 3.9", 96 | "Programming Language :: Python :: 3.10", 97 | "Programming Language :: Python :: 3 :: Only", 98 | ], 99 | # This field adds keywords for your project which will appear on the 100 | # project page. What does your project relate to? 101 | # 102 | # Note that this is a list of additional keywords, separated 103 | # by commas, to be used to assist searching for the distribution in a 104 | # larger catalog. 105 | keywords="jesse, trade, tradingview, report", # Optional 106 | # When your source code is in a subdirectory under the project root, e.g. 107 | # `src/`, it is necessary to specify the `package_dir` argument. 108 | package_dir={"": "src"}, # Optional 109 | # You can just specify package directories manually here if your project is 110 | # simple. Or you can use find_packages(). 111 | # 112 | # Alternatively, if you just want to distribute a single Python file, use 113 | # the `py_modules` argument instead as follows, which will expect a file 114 | # called `my_module.py` to exist: 115 | # 116 | # py_modules=["my_module"], 117 | # 118 | packages=find_packages(where="src"), # Required 119 | # Specify which Python versions you support. In contrast to the 120 | # 'Programming Language' classifiers above, 'pip install' will check this 121 | # and refuse to install the project if the version does not match. See 122 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires 123 | python_requires=">=3.7, <4", 124 | # This field lists other packages that your project depends on to run. 125 | # Any package you put here will be installed by pip when your project is 126 | # installed, so they must be valid existing projects. 127 | # 128 | # For an analysis of "install_requires" vs pip's requirements files see: 129 | # https://packaging.python.org/discussions/install-requires-vs-requirements/ 130 | install_requires=["jesse", "pandas", "bottle"], # Optional 131 | # List additional groups of dependencies here (e.g. development 132 | # dependencies). Users will be able to install these using the "extras" 133 | # syntax, for example: 134 | # 135 | # $ pip install sampleproject[dev] 136 | # 137 | # Similar to `install_requires` above, these must be valid existing 138 | # projects. 139 | extras_require={ # Optional 140 | "dev": ["check-manifest"], 141 | "test": ["coverage"], 142 | }, 143 | # If there are data files included in your packages that need to be 144 | # installed, specify them here. 145 | # package_data={ # Optional 146 | # # "sample": ["package_data.dat"], 147 | # }, 148 | # Although 'package_data' is the preferred approach, in some case you may 149 | # need to place data files outside of your packages. See: 150 | # http://docs.python.org/distutils/setupscript.html#installing-additional-files 151 | # 152 | # In this case, 'data_file' will be installed into '/my_data' 153 | # data_files=[("my_data", ["data/data_file"])], # Optional 154 | # To provide executable scripts, use entry points in preference to the 155 | # "scripts" keyword. Entry points provide cross-platform support and allow 156 | # `pip` to create the appropriate form of executable for the target 157 | # platform. 158 | # 159 | # For example, the following would provide a command called `sample` which 160 | # executes the function `main` from this package when invoked: 161 | # entry_points={ # Optional 162 | # # "console_scripts": [ 163 | # # "sample=sample:main", 164 | # # ], 165 | # }, 166 | # List additional URLs that are relevant to your project as a dict. 167 | # 168 | # This field corresponds to the "Project-URL" metadata fields: 169 | # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use 170 | # 171 | # Examples listed include a pattern for specifying where the package tracks 172 | # issues, where the source is hosted, where to say thanks to the package 173 | # maintainers, and where to support the project financially. The key is 174 | # what's used to render the link text on PyPI. 175 | project_urls={ # Optional 176 | "Bug Reports": "https://github.com/qwpto/JesseTradingViewLightReport/issues", 177 | # "Funding": "https://donate.pypi.org", 178 | # "Say Thanks!": "http://saythanks.io/to/example", 179 | "Source": "https://github.com/qwpto/JesseTradingViewLightReport/", 180 | }, 181 | ) 182 | -------------------------------------------------------------------------------- /src/JesseTradingViewLightReport/__init__.py: -------------------------------------------------------------------------------- 1 | from .generateReport import generateReport -------------------------------------------------------------------------------- /src/JesseTradingViewLightReport/generateReport.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from jesse.services.file import store_logs 3 | import jesse.helpers as jh 4 | from jesse.modes import backtest_mode 5 | from jesse.config import config 6 | from jesse.services import charts 7 | from jesse.services import report 8 | from jesse.routes import router 9 | from datetime import datetime, timedelta 10 | from jesse.store import store 11 | import jesse.services.metrics as stats 12 | from jesse.enums import trade_types 13 | 14 | 15 | import pandas as pd 16 | import numpy as np 17 | 18 | from jesse.utils import numpy_candles_to_dataframe 19 | from typing import Union 20 | 21 | import codecs 22 | 23 | from bottle import template 24 | import os 25 | 26 | # from jesse.strategies import Strategy 27 | 28 | from enum import IntEnum 29 | class CD(IntEnum): 30 | date = 0 31 | open = 1 32 | close = 2 33 | high = 3 34 | low = 4 35 | volume = 5 36 | 37 | def pvsra(candles: np.ndarray, sequential = False) -> Union[float, np.ndarray]: 38 | df = numpy_candles_to_dataframe(candles) 39 | df["averageVolume"] = df["volume"].rolling(10).mean() 40 | df["climax"] = df["volume"] * ( df["high"] - df["low"]) 41 | df["highestClimax10"] = df["climax"].rolling(window=10).max() 42 | df.loc[((df['volume'] >= 2 * df["averageVolume"]) | (df["climax"] >= df["highestClimax10"])), 'climaxVolume'] = 1 43 | df.loc[((df['volume'] >= 1.5 * df["averageVolume"]) & (df['climaxVolume'] != 1)), 'risingVolume'] = 1 44 | df.loc[(df["close"] > df["open"]), 'isBull'] = 1 45 | 46 | df.loc[((df["risingVolume"] == 1) & (df["isBull"] == 1)), 'risingBullVolume'] = 1 47 | df.loc[((df["risingVolume"] == 1) & (df["isBull"] != 1)), 'risingBearVolume'] = 1 48 | 49 | df.loc[((df["climaxVolume"] == 1) & (df["isBull"] == 1)), 'climaxBullVolume'] = 1 50 | df.loc[((df["climaxVolume"] == 1) & (df["isBull"] != 1)), 'climaxBearVolume'] = 1 51 | 52 | if sequential: 53 | return df 54 | else: 55 | return df.iloc[-1] 56 | 57 | 58 | def generateReport(customData={}, chartConfig={}): 59 | if(config["app"]["trading_mode"] == 'backtest'): 60 | 61 | cstLineTpl = r""" 62 | 63 | chart.add{{type}}({{!options}}).setData(await getCustomData({{offset}})) 64 | 65 | """ 66 | 67 | tpl = r""" 68 | 69 | 70 | 71 | 72 | 73 | 74 | {{title}} 75 | 92 | 93 | 94 |
95 | 96 | """ 97 | if('isPvsra' in chartConfig and chartConfig['isPvsra'] or len(customData)==0): 98 | tpl += r""" 99 | 100 | """ 101 | else: 102 | tpl += r""" 103 | 113 | """ 114 | tpl += r""" 115 | 304 | 305 | """ 306 | 307 | file_name = jh.get_session_id() 308 | studyname = backtest_mode._get_study_name() 309 | #start_date = datetime.fromtimestamp(store.app.starting_time / 1000) # could optimise this if the generated report already contains data, just start from where it was up to and append. 310 | #date_list = [start_date + timedelta(days=x) for x in range(len(store.app.daily_balance))] 311 | #fullCandles = backtest_mode.load_candles(date_list[0].strftime('%Y-%m-%d'), date_list[-1].strftime('%Y-%m-%d')) 312 | #candles = fullCandles[jh.key(router.routes[0].exchange, router.routes[0].symbol)]['candles'] 313 | candles = store.candles.get_candles(router.routes[0].exchange, router.routes[0].symbol, router.routes[0].timeframe) 314 | 315 | # if('mainChartLines' in customData and len(customData['mainChartLines'])>0): 316 | # for key, value in customData['mainChartLines'].items(): 317 | # # candles += [value] 318 | # for idx, item in enumerate(candles): 319 | # item = np.append(item, value[idx]) 320 | 321 | if('isPvsra' in chartConfig and chartConfig['isPvsra']): 322 | pvsraData = pvsra(candles, True) 323 | file_name += '-PVSRA' 324 | 325 | 326 | candleData = 'const candleData = `' 327 | for idx, candle in enumerate(candles): 328 | candleData += str(candle[CD.date]/1000) + ',' 329 | candleData += str(candle[CD.open]) + ',' 330 | candleData += str(candle[CD.high]) + ',' 331 | candleData += str(candle[CD.low]) + ',' 332 | candleData += str(candle[CD.close]) + ',' 333 | candleData += str(candle[CD.volume]) 334 | if('isPvsra' in chartConfig and chartConfig['isPvsra']): 335 | #color, borderColor,wickColor 336 | if(pvsraData['climaxBullVolume'].iloc[idx] == 1): 337 | candleData += ',lime,lime,white' 338 | elif(pvsraData['climaxBearVolume'].iloc[idx] == 1): 339 | candleData += ',red,red,gray' 340 | elif(pvsraData['risingBullVolume'].iloc[idx] == 1): 341 | candleData += ',blue,blue,white' 342 | elif(pvsraData['risingBearVolume'].iloc[idx] == 1): 343 | candleData += ',fuchsia,fuchsia,gray' 344 | elif(pvsraData['isBull'].iloc[idx] == 1): 345 | candleData += ',silver,silver,gray' 346 | else: 347 | candleData += ',gray,gray,gray' 348 | 349 | else: 350 | candleData += ', , , ' 351 | 352 | 353 | if(len(customData)>0): 354 | for key, value in customData.items(): 355 | candleData += ',' 356 | candleData += str(value['data'][idx]) 357 | 358 | candleData += '\n' 359 | if(candleData[-1]=='\n'): 360 | candleData = candleData.rstrip(candleData[-1]) # remove last new line 361 | candleData += '`;' 362 | 363 | pnl_accumulated = 0 364 | orderData = 'const orderData = `' 365 | for trade in store.completed_trades.trades: 366 | trading_fee = jh.get_config(f'env.exchanges.{trade.exchange}.fee') 367 | average_entry_price = 0 368 | average_entry_size = 0 369 | side_factor = 1 370 | if(trade.type == trade_types.SHORT): 371 | side_factor = -1 372 | for order in trade.orders: 373 | if(order.is_executed): 374 | fee = abs(order.qty) * order.price * trading_fee 375 | if(((trade.type == trade_types.LONG) and (order.side == 'buy')) or ((trade.type == trade_types.SHORT) and (order.side == 'sell'))): 376 | #pnl is just fees as increasing size 377 | pnl_order = -fee 378 | average_entry_price = (average_entry_price*average_entry_size + order.price*abs(order.qty))/(average_entry_size+abs(order.qty)) 379 | average_entry_size += abs(order.qty) 380 | else: 381 | #closing some position 382 | pnl_order = (order.price - average_entry_price)*abs(order.qty)*side_factor - fee 383 | average_entry_size -= abs(order.qty) 384 | 385 | pnl_accumulated+=pnl_order 386 | 387 | mode = '' 388 | if(order.is_stop_loss): 389 | mode = ' (SL)' 390 | elif(order.is_take_profit): 391 | mode = ' (TP)' 392 | orderData += str(order.executed_at/1000) + ',' 393 | orderData += mode + ',' 394 | orderData += order.side + ',' 395 | orderData += order.type + ',' 396 | orderData += str(order.qty) + ',' 397 | orderData += str(order.price) + ',' 398 | orderData += str(pnl_order) + ',' 399 | orderData += str(pnl_accumulated) + '\n' 400 | if(orderData[-1] == '\n'): 401 | orderData = orderData.rstrip(orderData[-1]) # remove last new line 402 | orderData += '`;' 403 | 404 | customCharts='' 405 | if(len(customData)>0): 406 | idx = 0 407 | for key, value in customData.items(): 408 | if(not 'options' in value): 409 | value['options'] = {} 410 | value['options']['title'] = key 411 | if(not 'type' in value): 412 | value['type']='LineSeries' 413 | customCharts += template(cstLineTpl, {'options':str(value['options']), 'offset':idx, 'type':value['type']}) 414 | idx += 1 415 | 416 | pnlCharts = '' 417 | priceScale = '' 418 | if('pnl' in chartConfig and chartConfig['pnl']): 419 | pnlCharts = 'chart.addLineSeries({color: \'rgba(4, 111, 232, 1)\', lineWidth: 1, priceScaleId: \'left\',}).setData(await getPnlData())' 420 | priceScale = ' rightPriceScale: { visible: true, borderColor: \'rgba(197, 203, 206, 1)\' }, leftPriceScale: { visible: true, borderColor: \'rgba(197, 203, 206, 1)\' },' 421 | 422 | info = {'title': studyname, 423 | 'candleData': candleData, 424 | 'orderData': orderData, 425 | 'customCharts':customCharts, 426 | 'pnlCharts':pnlCharts, 427 | 'priceScale': priceScale 428 | } 429 | 430 | result = template(tpl, info) 431 | 432 | filename = "storage/JesseTradingViewLightReport/" + file_name + '.html' 433 | os.makedirs(os.path.dirname(filename), exist_ok=True) 434 | with codecs.open(filename, "w", "utf-8") as f: 435 | f.write(result) 436 | 437 | return filename 438 | 439 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | -------------------------------------------------------------------------------- /tests/test_simple.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | 5 | import unittest 6 | 7 | # from JesseTradingViewLightReport import generateReport 8 | 9 | 10 | class TestSimple(unittest.TestCase): 11 | 12 | def test_generateReport(self): 13 | self.assertEqual(6, 6) 14 | 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # this file is *not* meant to cover or endorse the use of tox or pytest or 2 | # testing in general, 3 | # 4 | # It's meant to show the use of: 5 | # 6 | # - check-manifest 7 | # confirm items checked into vcs are in your sdist 8 | # - python setup.py check 9 | # confirm required package meta-data in setup.py 10 | # - readme_renderer (when using a ReStructuredText README) 11 | # confirms your long_description will render correctly on PyPI. 12 | # 13 | # and also to help confirm pull requests to this project. 14 | 15 | [tox] 16 | envlist = py{37,38,39,310} 17 | 18 | # Define the minimal tox version required to run; 19 | # if the host tox is less than this the tool with create an environment and 20 | # provision it with a tox that satisfies it under provision_tox_env. 21 | # At least this version is needed for PEP 517/518 support. 22 | minversion = 3.3.0 23 | 24 | # Activate isolated build environment. tox will use a virtual environment 25 | # to build a source distribution from the source tree. For build tools and 26 | # arguments use the pyproject.toml file as specified in PEP-517 and PEP-518. 27 | isolated_build = true 28 | 29 | [testenv] 30 | deps = 31 | check-manifest >= 0.42 32 | # If your project uses README.rst, uncomment the following: 33 | # readme_renderer 34 | flake8 35 | pytest 36 | commands = 37 | check-manifest --ignore 'tox.ini,tests/**' 38 | # This repository uses a Markdown long_description, so the -r flag to 39 | # `setup.py check` is not needed. If your project contains a README.rst, 40 | # use `python setup.py check -m -r -s` instead. 41 | python setup.py check -m -s 42 | flake8 . 43 | py.test tests {posargs} 44 | 45 | [flake8] 46 | exclude = .tox,*.egg,build,data 47 | select = E,W,F 48 | ignore = E501,E701,F841,F401,W292 49 | --------------------------------------------------------------------------------