diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..2bdc722 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,82 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..af36f7e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f26a588 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/python-tradingview-ta.iml b/.idea/python-tradingview-ta.iml new file mode 100644 index 0000000..269e1a8 --- /dev/null +++ b/.idea/python-tradingview-ta.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test.py b/test.py index b5b513c..851db77 100644 --- a/test.py +++ b/test.py @@ -1,5 +1,5 @@ from colorama import Fore, Style -from tradingview_ta import TA_Handler, Interval, get_multiple_analysis +from tradingview_ta import TA_Handler, Interval, get_multiple_analysis, get_multiple_analysis_with_multiple_intervals import tradingview_ta, requests, argparse arg_parser = argparse.ArgumentParser() @@ -152,6 +152,29 @@ except Exception as e: print("{}#6{} Get indicators test {}failed{}. An exception occured: {}".format(Fore.BLUE, Style.RESET_ALL, Fore.RED, Style.RESET_ALL, e)) +try: + analysis = get_multiple_analysis_with_multiple_intervals(screener="crypto", + intervals=[Interval.INTERVAL_1_DAY, + Interval.INTERVAL_1_HOUR], + symbols=["BINANCE:SOLUSDT", "BINANCE:BTCUSDT"]) + for key, value in analysis.items(): + print( + "{}#7{} Please compare with {}https://www.tradingview.com/symbols/{}/technicals/{}. (Switch to 1 hour tab)".format( + Fore.BLUE, Style.RESET_ALL, Fore.LIGHTMAGENTA_EX, key, Style.RESET_ALL)) + print("{}#7{} (Summary) Rec: {}, Sell: {}, Neutral: {}, Buy: {}".format(Fore.BLUE, Style.RESET_ALL, + value.summary["RECOMMENDATION"], + value.summary["SELL"], + value.summary["NEUTRAL"], + value.summary["BUY"])) + if input("{}#7{} Are the results the same? (Y/N) ".format(Fore.BLUE, Style.RESET_ALL)).lower() == "y": + print("{}#7{} Multiple analysis test {}success{}.".format(Fore.BLUE, Style.RESET_ALL, Fore.GREEN, + Style.RESET_ALL)) + success += 1 + else: + print("{}#7{} Multiple analysis test {}failed{}".format(Fore.BLUE, Style.RESET_ALL, Fore.RED, Style.RESET_ALL)) +except Exception as e: + print("{}#7{} Multiple analysis test {}failed{}. An exception occured: {}".format(Fore.BLUE, Style.RESET_ALL, + Fore.RED, Style.RESET_ALL, e)) print("------------------------------------------------") print("Test finished. Result: {}{}/{}{}.".format(Fore.LIGHTWHITE_EX, success, COUNT, Style.RESET_ALL)) diff --git a/tradingview_ta/__init__.py b/tradingview_ta/__init__.py index 8ce1275..0397793 100644 --- a/tradingview_ta/__init__.py +++ b/tradingview_ta/__init__.py @@ -1,2 +1,3 @@ -from .main import TA_Handler, TradingView, Analysis, Interval, Exchange, get_multiple_analysis, __version__ +from .main import TA_Handler, TradingView, Analysis, Interval, Exchange, get_multiple_analysis, \ + get_multiple_analysis_with_multiple_intervals, __version__ from .technicals import Recommendation, Compute diff --git a/tradingview_ta/main.py b/tradingview_ta/main.py index 0c8638b..029e39f 100644 --- a/tradingview_ta/main.py +++ b/tradingview_ta/main.py @@ -2,10 +2,12 @@ # Author: deathlyface (https://github.com/deathlyface) # License: MIT -import requests -import json import datetime +import json import warnings + +import requests + from .technicals import Compute __version__ = "3.3.0" @@ -43,8 +45,21 @@ class Exchange: class TradingView: # Note: Please DO NOT modify the order or DELETE existing indicators, it will break the technical analysis. You may APPEND custom indicator to the END of the list. - indicators = ["Recommend.Other", "Recommend.All", "Recommend.MA", "RSI", "RSI[1]", "Stoch.K", "Stoch.D", "Stoch.K[1]", "Stoch.D[1]", "CCI20", "CCI20[1]", "ADX", "ADX+DI", "ADX-DI", "ADX+DI[1]", "ADX-DI[1]", "AO", "AO[1]", "Mom", "Mom[1]", "MACD.macd", "MACD.signal", "Rec.Stoch.RSI", "Stoch.RSI.K", "Rec.WR", "W.R", "Rec.BBPower", "BBPower", "Rec.UO", "UO", "close", "EMA5", "SMA5", "EMA10", "SMA10", "EMA20", "SMA20", "EMA30", "SMA30", "EMA50", "SMA50", "EMA100", "SMA100", "EMA200", "SMA200", "Rec.Ichimoku", "Ichimoku.BLine", "Rec.VWMA", "VWMA", "Rec.HullMA9", "HullMA9", "Pivot.M.Classic.S3", "Pivot.M.Classic.S2", "Pivot.M.Classic.S1", "Pivot.M.Classic.Middle", "Pivot.M.Classic.R1", - "Pivot.M.Classic.R2", "Pivot.M.Classic.R3", "Pivot.M.Fibonacci.S3", "Pivot.M.Fibonacci.S2", "Pivot.M.Fibonacci.S1", "Pivot.M.Fibonacci.Middle", "Pivot.M.Fibonacci.R1", "Pivot.M.Fibonacci.R2", "Pivot.M.Fibonacci.R3", "Pivot.M.Camarilla.S3", "Pivot.M.Camarilla.S2", "Pivot.M.Camarilla.S1", "Pivot.M.Camarilla.Middle", "Pivot.M.Camarilla.R1", "Pivot.M.Camarilla.R2", "Pivot.M.Camarilla.R3", "Pivot.M.Woodie.S3", "Pivot.M.Woodie.S2", "Pivot.M.Woodie.S1", "Pivot.M.Woodie.Middle", "Pivot.M.Woodie.R1", "Pivot.M.Woodie.R2", "Pivot.M.Woodie.R3", "Pivot.M.Demark.S1", "Pivot.M.Demark.Middle", "Pivot.M.Demark.R1", "open", "P.SAR", "BB.lower", "BB.upper", "AO[2]", "volume", "change", "low", "high"] + indicators = ["Recommend.Other", "Recommend.All", "Recommend.MA", "RSI", "RSI[1]", "Stoch.K", "Stoch.D", + "Stoch.K[1]", "Stoch.D[1]", "CCI20", "CCI20[1]", "ADX", "ADX+DI", "ADX-DI", "ADX+DI[1]", "ADX-DI[1]", + "AO", "AO[1]", "Mom", "Mom[1]", "MACD.macd", "MACD.signal", "Rec.Stoch.RSI", "Stoch.RSI.K", "Rec.WR", + "W.R", "Rec.BBPower", "BBPower", "Rec.UO", "UO", "close", "EMA5", "SMA5", "EMA10", "SMA10", "EMA20", + "SMA20", "EMA30", "SMA30", "EMA50", "SMA50", "EMA100", "SMA100", "EMA200", "SMA200", "Rec.Ichimoku", + "Ichimoku.BLine", "Rec.VWMA", "VWMA", "Rec.HullMA9", "HullMA9", "Pivot.M.Classic.S3", + "Pivot.M.Classic.S2", "Pivot.M.Classic.S1", "Pivot.M.Classic.Middle", "Pivot.M.Classic.R1", + "Pivot.M.Classic.R2", "Pivot.M.Classic.R3", "Pivot.M.Fibonacci.S3", "Pivot.M.Fibonacci.S2", + "Pivot.M.Fibonacci.S1", "Pivot.M.Fibonacci.Middle", "Pivot.M.Fibonacci.R1", "Pivot.M.Fibonacci.R2", + "Pivot.M.Fibonacci.R3", "Pivot.M.Camarilla.S3", "Pivot.M.Camarilla.S2", "Pivot.M.Camarilla.S1", + "Pivot.M.Camarilla.Middle", "Pivot.M.Camarilla.R1", "Pivot.M.Camarilla.R2", "Pivot.M.Camarilla.R3", + "Pivot.M.Woodie.S3", "Pivot.M.Woodie.S2", "Pivot.M.Woodie.S1", "Pivot.M.Woodie.Middle", + "Pivot.M.Woodie.R1", "Pivot.M.Woodie.R2", "Pivot.M.Woodie.R3", "Pivot.M.Demark.S1", + "Pivot.M.Demark.Middle", "Pivot.M.Demark.R1", "open", "P.SAR", "BB.lower", "BB.upper", "AO[2]", + "volume", "change", "low", "high"] scan_url = "https://scanner.tradingview.com/" @@ -97,6 +112,75 @@ def data(symbols, interval, indicators): return data_json + def data_multi_intervals(symbols, intervals, indicators): + """Format TradingView's Scanner Post Data + + Args: + symbols (list): List of EXCHANGE:SYMBOL (ex: ["NASDAQ:AAPL"] or ["BINANCE:BTCUSDT"]) + interval (list): Time Interval (ex: 1m, 5m, 15m, 1h, 4h, 1d, 1W, 1M) + + Returns: + string: JSON object as a string. + """ + output_intervals = [] + for interval in intervals: + if interval == "1m": + # 1 Minute + data_interval = "|1" + output_intervals.append(data_interval) + elif interval == "5m": + # 5 Minutes + data_interval = "|5" + output_intervals.append(data_interval) + + elif interval == "15m": + # 15 Minutes + data_interval = "|15" + output_intervals.append(data_interval) + + elif interval == "30m": + # 30 Minutes + data_interval = "|30" + output_intervals.append(data_interval) + + elif interval == "1h": + # 1 Hour + data_interval = "|60" + output_intervals.append(data_interval) + + elif interval == "2h": + # 2 Hours + data_interval = "|120" + output_intervals.append(data_interval) + + elif interval == "4h": + # 4 Hour + data_interval = "|240" + output_intervals.append(data_interval) + + elif interval == "1W": + # 1 Week + data_interval = "|1W" + output_intervals.append(data_interval) + + elif interval == "1M": + # 1 Month + data_interval = "|1M" + output_intervals.append(data_interval) + + else: + if interval != '1d': + warnings.warn( + "Interval is empty or not valid, defaulting to 1 day.") + # Default, 1 Day + data_interval = "" + output_intervals.append(data_interval) + + data_json = {"symbols": {"tickers": [symbol.upper() for symbol in symbols], "query": { + "types": []}}, "columns": [i + j for j in output_intervals for i in indicators]} + + return data_json + def search(text, type=None): """Search for assets on TradingView. Returns the symbol, exchange, type, and description of the asset. @@ -228,11 +312,14 @@ def calculate(indicators, indicators_key, screener, symbol, exchange, interval): analysis.indicators = analysis.indicators.copy() analysis.oscillators = {"RECOMMENDATION": recommend_oscillators, - "BUY": oscillators_counter["BUY"], "SELL": oscillators_counter["SELL"], "NEUTRAL": oscillators_counter["NEUTRAL"], "COMPUTE": computed_oscillators} + "BUY": oscillators_counter["BUY"], "SELL": oscillators_counter["SELL"], + "NEUTRAL": oscillators_counter["NEUTRAL"], "COMPUTE": computed_oscillators} analysis.moving_averages = {"RECOMMENDATION": recommend_moving_averages, - "BUY": ma_counter["BUY"], "SELL": ma_counter["SELL"], "NEUTRAL": ma_counter["NEUTRAL"], "COMPUTE": computed_ma} + "BUY": ma_counter["BUY"], "SELL": ma_counter["SELL"], "NEUTRAL": ma_counter["NEUTRAL"], + "COMPUTE": computed_ma} analysis.summary = {"RECOMMENDATION": recommend_summary, "BUY": oscillators_counter["BUY"] + ma_counter["BUY"], - "SELL": oscillators_counter["SELL"] + ma_counter["SELL"], "NEUTRAL": oscillators_counter["NEUTRAL"] + ma_counter["NEUTRAL"]} + "SELL": oscillators_counter["SELL"] + ma_counter["SELL"], + "NEUTRAL": oscillators_counter["NEUTRAL"] + ma_counter["NEUTRAL"]} return analysis @@ -355,8 +442,9 @@ def get_indicators(self, indicators=[]): # Return False if can't get data if response.status_code != 200: - raise Exception("Can't access TradingView's API. HTTP status code: {}. Check for invalid symbol, exchange, or indicators.".format( - response.status_code)) + raise Exception( + "Can't access TradingView's API. HTTP status code: {}. Check for invalid symbol, exchange, or indicators.".format( + response.status_code)) result = json.loads(response.text)["data"] if result != []: @@ -384,7 +472,8 @@ def get_analysis(self): Analysis: Contains information about the analysis. """ - return calculate(indicators=self.get_indicators(), indicators_key=self.indicators, screener=self.screener, symbol=self.symbol, exchange=self.exchange, interval=self.interval) + return calculate(indicators=self.get_indicators(), indicators_key=self.indicators, screener=self.screener, + symbol=self.symbol, exchange=self.exchange, interval=self.interval) def get_multiple_analysis(screener, interval, symbols, additional_indicators=[], timeout=None, proxies=None): @@ -430,8 +519,9 @@ def get_multiple_analysis(screener, interval, symbols, additional_indicators=[], for x in range(len(analysis["d"])): indicators[indicators_key[x]] = analysis["d"][x] - final[analysis["s"]] = calculate(indicators=indicators, indicators_key=indicators_key, screener=screener, symbol=analysis["s"].split( - ":")[1], exchange=analysis["s"].split(":")[0], interval=interval) + final[analysis["s"]] = calculate(indicators=indicators, indicators_key=indicators_key, screener=screener, + symbol=analysis["s"].split( + ":")[1], exchange=analysis["s"].split(":")[0], interval=interval) for symbol in symbols: # Add None if there is no analysis for symbol @@ -439,3 +529,110 @@ def get_multiple_analysis(screener, interval, symbols, additional_indicators=[], final[symbol.upper()] = None return final + + +def get_multiple_analysis_with_multiple_intervals(screener, intervals, symbols, additional_indicators=[], timeout=None, + proxies=None): + """Retrieve multiple technical analysis at once. Note: You can't mix different screener and interval + + Args: + screener (str, required): Screener (see documentation and tradingview's site). + interval (str, optional): See the interval class and the documentation. Defaults to 1 day. + symbols (list, required): List of exchange and ticker symbol separated by a colon. Example: ["NASDAQ:TSLA", "NYSE:DOCN"] or ["BINANCE:BTCUSDT", "BITSTAMP:ETHUSD"]. + additional_indicators (list, optional): List of additional indicators to be requested from TradingView, see valid indicators on https://pastebin.com/1DjWv2Hd. + timeout (float, optional): Timeout for requests (in seconds). Defaults to None. + proxies (dict, optional): Proxies to be used for requests. Defaults to None (disabled). + + Returns: + dict: dictionary with a format of {"EXCHANGE:SYMBOL": Analysis}. + """ + if not screener or not isinstance(screener, str): + raise Exception("Screener is empty or not valid.") + if not symbols or not isinstance(symbols, list): + raise Exception("Symbols is empty or not valid.") + for symbol in symbols: + if len(symbol.split(":")) != 2 or "" in symbol.split(":"): + raise Exception( + "One or more symbols are invalid. Symbols should be a list of exchange and ticker symbol separated by a colon. Example: [\"NASDAQ:TSLA\", \"NYSE:DOCN\"] or [\"BINANCE:BTCUSDT\", \"BITSTAMP:ETHUSD\"].") + + indicators_key = TradingView.indicators.copy() + + if additional_indicators: + indicators_key += additional_indicators + + data = TradingView.data_multi_intervals(symbols, intervals, indicators_key) + indicators_key = data["columns"] + scan_url = f"{TradingView.scan_url}{screener.lower()}/scan" + headers = {"User-Agent": "tradingview_ta/{}".format(__version__)} + response = requests.post(scan_url, json=data, headers=headers, timeout=timeout, proxies=proxies) + + result = json.loads(response.text)["data"] + final = {} + + for analysis in result: + # Convert list to dict + indicators = {indicators_key[x]: analysis["d"][x] for x in range(len(analysis["d"]))} + + final[analysis["s"]] = calculate(indicators=indicators, indicators_key=indicators_key, screener=screener, + symbol=analysis["s"].split(":")[1], exchange=analysis["s"].split(":")[0], + interval=intervals) + + for symbol in symbols: + # Add None if there is no analysis for symbol + if symbol.upper() not in final: + final[symbol.upper()] = None + + combain_dict = {} + for i in symbols: + if Interval.INTERVAL_1_MINUTE in intervals: + dict_1m = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|1')} + combain_dict[Interval.INTERVAL_1_MINUTE] = dict_1m + + if Interval.INTERVAL_5_MINUTES in intervals: + dict_5m = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|5')} + combain_dict[Interval.INTERVAL_5_MINUTES] = dict_5m + + if Interval.INTERVAL_15_MINUTES in intervals: + dict_15m = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|15')} + combain_dict[Interval.INTERVAL_15_MINUTES] = dict_15m + + if Interval.INTERVAL_30_MINUTES in intervals: + dict_30m = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|30')} + combain_dict[Interval.INTERVAL_30_MINUTES] = dict_30m + + if Interval.INTERVAL_1_HOUR in intervals: + dict_1h = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|60')} + combain_dict[Interval.INTERVAL_1_HOUR] = dict_1h + + if Interval.INTERVAL_2_HOURS in intervals: + dict_2h = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|120')} + combain_dict[Interval.INTERVAL_2_HOURS] = dict_2h + + if Interval.INTERVAL_4_HOURS in intervals: + dict_4h = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|240')} + combain_dict[Interval.INTERVAL_4_HOURS] = dict_4h + + if Interval.INTERVAL_1_WEEK in intervals: + dict_1W = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|1W')} + combain_dict[Interval.INTERVAL_1_WEEK] = dict_1W + + if Interval.INTERVAL_1_MONTH in intervals: + dict_1M = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if + key.endswith('|1M')} + combain_dict[Interval.INTERVAL_1_MONTH] = dict_1M + + if Interval.INTERVAL_1_DAY in intervals: + dict_1D = {key.split("|")[0]: value for key, value in final[i.upper()].indicators.items() if '|' not in key} + combain_dict[Interval.INTERVAL_1_DAY] = dict_1D + + final[i.upper()].indicators = combain_dict + + return final