├── .gitignore ├── README.md ├── api-app.py ├── capability.py ├── chart_creator.py ├── config.py ├── generate-compat-table.py ├── log.py ├── ohlcv-proxy.py ├── pine ├── .gitignore ├── __init__.py ├── base.py ├── broker │ ├── __init__.py │ ├── base.py │ └── mirror.py ├── lexer.py ├── market │ ├── __init__.py │ ├── base.py │ ├── bitflyer.py │ ├── bitmex.py │ └── mirror.py ├── parser.py ├── parsetab.py ├── preprocess.py ├── runner.py ├── sample │ ├── a.pine │ ├── divergence.pine │ ├── dotenkun.pine │ ├── elize-moef.pine │ ├── elize.pine │ ├── elliott.pine │ ├── highlow_linreg.pine │ ├── input.pine │ ├── ma.pine │ ├── moef.pine │ ├── mtf_stochrsi.pine │ ├── offset.pine │ ├── offset_kai.pine │ ├── offsetenv.pine │ ├── piano_delta.pine │ ├── ppo_bullbear.pine │ ├── stoch.pine │ └── stochrsi.pine └── vm │ ├── __init__.py │ ├── builtin_function.py │ ├── builtin_variable.py │ ├── compile.py │ ├── helper.py │ ├── node.py │ ├── plot.py │ ├── step.py │ └── vm.py ├── repl-app.py ├── static ├── cryptowatch-support.json └── exchange-support.json └── templates ├── evaluate_error.html ├── evaluate_error_exception.html ├── exchange-support.html ├── input_forms.html └── landing.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # vim 107 | *.sw? 108 | ccxt/ 109 | log/ 110 | pine-codes/ 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pine-bot-server 2 | 3 | This is a server which works with pine-bot-client. 4 | 5 | I started this project just for fun to implement Pine script parser/evaluator and 6 | I make this repository public because I have no interest to maintain this project especially after TradingView supports Webhook alert. 7 | 8 | Now I'm busy on other project and please allow me not to respond you. 🙇 9 | -------------------------------------------------------------------------------- /api-app.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import log 4 | 5 | from logging import getLogger 6 | logger = getLogger() 7 | 8 | from datetime import datetime 9 | 10 | from flask import Flask 11 | from flask import render_template 12 | from flask import request 13 | from flask import jsonify 14 | 15 | app = Flask(__name__) 16 | app.config['TEMPLATES_AUTO_RELOAD'] = True 17 | app.config['JSON_SORT_KEYS'] = False 18 | 19 | from pine.base import PineError 20 | from pine.vm.vm import InputScanVM 21 | from pine.vm.step import StepVM 22 | from pine.vm.compile import compile_pine 23 | from pine.market.base import Market 24 | from pine.market.mirror import MirrorMarket 25 | from pine.broker.mirror import MirrorBroker 26 | 27 | import traceback 28 | from collections import OrderedDict 29 | 30 | from datetime import datetime, timezone 31 | def utctimestamp (): 32 | return datetime.now(timezone.utc).timestamp() 33 | 34 | # exchange information 35 | import json 36 | with open('static/exchange-support.json') as f: 37 | exchanges = json.loads(f.read(), object_pairs_hook=OrderedDict) 38 | 39 | @app.route('/exchange-support', methods=['POST']) 40 | def exchange_support (): 41 | try: 42 | exchange = request.json.get('exchange', None) 43 | if not exchange: 44 | return jsonify(exchanges=tuple(exchanges.keys())) 45 | xchg = exchanges.get(exchange.lower(), None) 46 | if xchg is None: 47 | return jsonify(markets=[]) 48 | 49 | market = request.json.get('market', None) 50 | if market is None: 51 | return jsonify(markets=xchg['markets']) 52 | 53 | market = market.lower() 54 | markets = [] 55 | for name, m in xchg['markets'].items(): 56 | if market == name: 57 | markets.append(m) 58 | else: 59 | for mi in m['ids']: 60 | if mi.lower() == market: 61 | markets.append(m) 62 | break 63 | return jsonify(markets=markets) 64 | 65 | except Exception as e: 66 | logger.exception(f'request={request.path}: {request.data}') 67 | return jsonify(error=traceback.format_exc()) 68 | 69 | from log import record_pine, current_maxrss 70 | 71 | @app.route('/scan-input', methods=['POST']) 72 | def scan_input (): 73 | try: 74 | code = request.json['code'] 75 | node = compile_pine(code) 76 | if node is None: 77 | raise PineError("pine script is empty") 78 | 79 | # Exract input 80 | vm = InputScanVM(Market()) 81 | vm.load_node(node) 82 | inputs = vm.run() 83 | 84 | record_pine(code, vm) 85 | 86 | default_qty_value = vm.meta.get('default_qty_value', 1.0) 87 | pyramiding = vm.meta.get('pyramiding ', 0) 88 | max_bars_back = vm.meta.get('max_bars_back', 0) 89 | 90 | params = OrderedDict( 91 | exchange='BITMEX', 92 | symbol='XBTUSD', 93 | resolution=30, 94 | strategy=OrderedDict( 95 | default_qty_value=default_qty_value, 96 | pyramiding=pyramiding, 97 | max_bars_back=max_bars_back, 98 | ) 99 | ) 100 | params['inputs'] = inputs = OrderedDict() 101 | for i in inputs: 102 | inputs[i['title']] = i['defval'] 103 | 104 | return jsonify(params=params) 105 | 106 | except Exception as e: 107 | logger.exception(f'request={request.path}: {request.data}') 108 | return jsonify(error=traceback.format_exc()) 109 | 110 | 111 | import threading 112 | vm_cache = OrderedDict() 113 | vm_cache_lock = threading.Lock() 114 | MAX_VM_CACHE_COUNT = 256 115 | def register_vm_to_cache (vm): 116 | with vm_cache_lock: 117 | vm_cache[vm.ident] = vm 118 | while len(vm_cache) > MAX_VM_CACHE_COUNT: 119 | vm_cache.pop_item(False) # From most oldest 120 | logger.info("VM cache count=%s, RSS=%s", len(vm_cache), current_maxrss()) 121 | 122 | def get_vm_from_cache (vmid): 123 | with vm_cache_lock: 124 | vm = vm_cache[vmid] 125 | vm_cache.move_to_end(vmid) 126 | return vm 127 | 128 | import threading 129 | def vm_cache_reporter (): 130 | from log import notify 131 | import time 132 | while True: 133 | time.sleep(3600) 134 | notify(logger, "VM #={}, RSS={}".format(len(vm_cache), current_maxrss())) 135 | threading.Thread(target=vm_cache_reporter, daemon=True).start() 136 | 137 | @app.route('/install-vm', methods=['POST']) 138 | def install_vm (): 139 | try: 140 | code = request.json['code'] 141 | inputs = request.json['inputs'] 142 | market = request.json['market'] 143 | 144 | # compile PINE 145 | node = compile_pine(code) 146 | if node is None: 147 | raise PineError("pine script is empty") 148 | 149 | # VM 150 | vm = StepVM(MirrorMarket(*market), code) 151 | 152 | # Set up and run 153 | vm.load_node(node) 154 | vm.set_user_inputs(inputs) 155 | markets = vm.scan_market() 156 | 157 | # Register VM to store 158 | register_vm_to_cache(vm) 159 | 160 | record_pine(code, vm) 161 | return jsonify(vm=vm.ident, markets=markets, server_clock=utctimestamp()) 162 | 163 | except Exception as e: 164 | logger.exception(f'request={request.path}: {request.data}') 165 | return jsonify(error=traceback.format_exc()) 166 | 167 | @app.route('/touch-vm', methods=['POST']) 168 | def touch_vm (): 169 | try: 170 | vmid = request.json['vmid'] 171 | try: 172 | get_vm_from_cache(vmid) 173 | return jsonify(server_clock=utctimestamp()) 174 | except KeyError: 175 | return jsonify(error='Not found in cache'), 205 176 | except Exception as e: 177 | logger.exception(f'request={request.path}: {request.data}') 178 | return jsonify(error=traceback.format_exc()), 500 179 | 180 | @app.route('/boot-vm', methods=['POST']) 181 | def boot_vm (): 182 | try: 183 | vmid = request.json['vmid'] 184 | ohlcv = request.json['ohlcv'] 185 | 186 | try: 187 | vm = get_vm_from_cache(vmid) 188 | except KeyError: 189 | return jsonify(error='Not found in cache'), 205 190 | 191 | vm.set_ohlcv(ohlcv) 192 | vm.run() 193 | vm.set_broker(MirrorBroker()) 194 | 195 | return jsonify(server_clock=utctimestamp()) 196 | 197 | except Exception as e: 198 | logger.exception(f'request={request.path}: {request.data}') 199 | return jsonify(error=traceback.format_exc()), 500 200 | 201 | @app.route('/step-vm', methods=['POST']) 202 | def step_vm (): 203 | try: 204 | vmid = request.json['vmid'] 205 | broker = request.json['broker'] 206 | ohlcv2 = request.json['ohlcv2'] 207 | 208 | try: 209 | vm = get_vm_from_cache(vmid) 210 | except KeyError: 211 | return jsonify(error='Not found in cache'), 205 212 | 213 | vm.broker.update(**broker) 214 | vm.market.update_ohlcv2(ohlcv2) 215 | 216 | actions = vm.step_new() 217 | return jsonify(actions=actions, 218 | server_clock=utctimestamp()) 219 | 220 | except Exception as e: 221 | logger.exception(f'request={request.path}: {request.data}') 222 | return jsonify(error=traceback.format_exc()), 500 223 | 224 | if __name__ == '__main__': 225 | import os 226 | port = int(os.environ.get('PORT', 5000)) 227 | app.run(port=port) 228 | -------------------------------------------------------------------------------- /capability.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import ccxt 3 | from ccxt.base.errors import NotSupported 4 | 5 | for e in ccxt.exchanges: 6 | try: 7 | xchg = getattr(ccxt, e)() 8 | if xchg.has[sys.argv[1]]: 9 | print(e) 10 | print(xchg.has) 11 | except NotSupported: 12 | pass 13 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | LOG_FILENAME = 'log/pine-api-app.log' 4 | #LOG_FILENAME = '' 5 | 6 | DISCORD_URL = '' 7 | DISCORD_NAME = 'PINE Server' 8 | DISCORD_AVATAR_URL = '' 9 | -------------------------------------------------------------------------------- /generate-compat-table.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import ccxt 4 | from collections import OrderedDict 5 | import requests 6 | import json 7 | 8 | exchanges = OrderedDict() 9 | 10 | # cryptowatch 11 | 12 | ## special handlers 13 | def __bitmex (symbols, pair): 14 | if pair.endswith('-perpetual-futures'): 15 | symbols[pair.split('-')[0]] = pair 16 | if pair == 'btcusd-perpetual-futures': 17 | symbols['xbtusd'] = pair 18 | def __bitflyer (symbols, pair): 19 | if pair == 'btcfxjpy': 20 | symbols['fxbtcjpy'] = pair 21 | 22 | cryptowatch = OrderedDict() 23 | API_SERVER_URL = 'https://api.cryptowat.ch' 24 | res = requests.get(API_SERVER_URL+'/markets') 25 | for m in res.json(object_pairs_hook=OrderedDict)['result']: 26 | exchange = m['exchange'] 27 | pair = m['pair'] 28 | symbols = cryptowatch.setdefault(exchange, OrderedDict()) 29 | symbols[pair] = pair 30 | func = globals().get(f'__{exchange}', None) 31 | if func: 32 | func(symbols, pair) 33 | 34 | with open('static/cryptowatch-support.json', 'w') as f: 35 | f.write(json.dumps(cryptowatch, indent=2)) 36 | 37 | def resolution_to_str (*args): 38 | r = [] 39 | for a in args: 40 | if a >= 1440 * 7: 41 | r.append('{}w'.format(int(a / 1440 / 7))) 42 | elif a >= 1440: 43 | r.append('{}d'.format(int(a / 1440))) 44 | elif a >= 60: 45 | r.append('{}h'.format(int(a / 60))) 46 | else: 47 | r.append('{}m'.format(a % 60)) 48 | return r 49 | 50 | def make_ids (*args): 51 | ids = [] 52 | for a in args: 53 | if a not in ids: 54 | ids.append(a) 55 | a_ = ''.join([c for c in a if c.isalnum()]) 56 | if a_ not in ids: 57 | ids.append(a_) 58 | return ids 59 | 60 | exchanges = OrderedDict(exchanges) 61 | for xchg_name in ccxt.exchanges: 62 | try: 63 | xchg_obj = getattr(ccxt, xchg_name)() 64 | xchg_id = xchg_obj.id 65 | xchg = OrderedDict( 66 | name=xchg_obj.name, 67 | ids=(xchg_id,), 68 | ) 69 | 70 | xchg['cryptowatch'] = xchg_cw = xchg_id in cryptowatch 71 | markets = xchg.setdefault('markets', OrderedDict()) 72 | for name, m in xchg_obj.load_markets().items(): 73 | m_ = markets.setdefault(name, OrderedDict()) 74 | m_['ids'] = make_ids(name, m['id'], m['symbol']) 75 | cw = False 76 | for i in m_['ids']: 77 | if xchg_id in cryptowatch and (i in cryptowatch[xchg_id] or i.lower() in cryptowatch[xchg_id] or i.upper() in cryptowatch[xchg_id]): 78 | cw = True 79 | break 80 | m_['cryptowatch'] = cw 81 | if cw: 82 | resolutions = resolution_to_str( 83 | 1, 3, 5, 15, 30, 84 | 60, 60*2, 60*4, 60*6, 60*12, 85 | 1440, 1440*3, 1440*7 86 | ) 87 | else: 88 | ohlcv_support = xchg_obj.has['fetchOHLCV'] 89 | if ohlcv_support and ohlcv_support != 'emulated': 90 | resolutions = list(xchg_obj.timeframes.keys()) 91 | else: 92 | resolutions = [] 93 | m_['resolutions'] = resolutions 94 | exchanges[xchg_name] = xchg 95 | print(xchg_name) 96 | except Exception as e: 97 | import traceback 98 | print(f'error: {xchg_name}: {e}') 99 | traceback.print_exc() 100 | 101 | with open('static/exchange-support.json', 'w') as f: 102 | f.write(json.dumps(exchanges, indent=2)) 103 | -------------------------------------------------------------------------------- /log.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import logging.handlers 5 | 6 | from config import * 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | # Helpers 11 | console_handler = logging.StreamHandler() 12 | 13 | # Formatters 14 | shortest_formatter = logging.Formatter('%(message)s') 15 | short_formatter = logging.Formatter('%(asctime)s: %(levelname)s: %(message)s') 16 | long_formatter = logging.Formatter('%(asctime)s: %(name)s: %(levelname)s: %(message)s') 17 | 18 | # Handlers 19 | # console 20 | console_handler = logging.StreamHandler() 21 | console_handler.setFormatter(short_formatter) 22 | console_handler.setLevel(logging.INFO) 23 | 24 | # File 25 | file_handler = logging.handlers.TimedRotatingFileHandler(LOG_FILENAME, 26 | when='D', backupCount=99, delay=True) 27 | file_handler.setFormatter(long_formatter) 28 | file_handler.setLevel(logging.DEBUG) 29 | 30 | # Discord 31 | import threading 32 | import queue 33 | discord_thread = None 34 | discord_queue = queue.Queue() 35 | discord_handler = logging.handlers.QueueHandler(discord_queue) 36 | discord_handler.setFormatter(shortest_formatter) 37 | discord_handler.setLevel(logging.CRITICAL) 38 | import requests 39 | def discord_sender (): 40 | while True: 41 | msg = discord_queue.get() 42 | if msg is None: 43 | break 44 | if msg.startswith('fail to send to Disocrd'): 45 | continue 46 | data = { 47 | "content": msg, 48 | "username": DISCORD_NAME, 49 | "avatar_url": DISCORD_AVATAR_URL, 50 | } 51 | try: 52 | r = requests.post(DISCORD_URL, data=data) 53 | if r.status_code != 204: 54 | logger.error(f'fail to send to Discord: {r.status_code}') 55 | except Exception as e: 56 | logger.error(f'fail to send to Discord: {e}') 57 | 58 | 59 | ## App logger 60 | app_logger = logging.getLogger() 61 | app_logger.addHandler(console_handler) 62 | app_logger.setLevel(logging.DEBUG) 63 | 64 | # load logging conf 65 | import os 66 | if os.path.exists('./logging.conf'): 67 | from logging import config 68 | config.fileConfig('./logging.conf', disable_existing_loggers=False) 69 | 70 | # Turn on additional handler 71 | if LOG_FILENAME: 72 | app_logger.addHandler(file_handler) 73 | if DISCORD_URL: 74 | discord_thread = threading.Thread(target=discord_sender, daemon=True) 75 | discord_thread.start() 76 | app_logger.addHandler(discord_handler) 77 | 78 | ## Report incl. discord 79 | def notify (logger, msg): 80 | logger.info(msg) 81 | if discord_thread: 82 | discord_queue.put(msg) 83 | 84 | ## Preserve PINE scripts 85 | import hashlib 86 | def record_pine (code, vm=None): 87 | if not os.path.exists('pine-codes'): 88 | return 89 | if vm: 90 | basename = vm.title 91 | else: 92 | basename = 'NoTitle' 93 | if vm and vm.ident: 94 | basename += '@' + vm.ident 95 | else: 96 | basename += '@' + hashlib.sha1(code.encode('utf-8')).hexdigest() 97 | with open(os.path.join('pine-codes', basename+'.pine'), 'w') as f: 98 | f.write(code) 99 | 100 | import resource 101 | def current_maxrss (): 102 | return '{:.1f}'.format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0) 103 | 104 | -------------------------------------------------------------------------------- /ohlcv-proxy.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from mprpc import RPCServer 4 | import msgpackrpc 5 | #class OhlcvProxyServer (RPCServer): 6 | class OhlcvProxyServer (object): 7 | 8 | def __init__ (self): 9 | super().__init__() 10 | self.adaptors = {} 11 | 12 | def register_adaptor (self, adaptor): 13 | self.adaptors[adaptor.tickerid] = adaptor 14 | 15 | def ohlcv (self, tickerid, resolution, count): 16 | return self.adaptors[tickerid].ohlcv(resolution, count) 17 | 18 | def step_ohlcv (self, tickerid, resolution, next_clock): 19 | return self.adaptors[tickerid].step_ohlcv(resolution, next_clock) 20 | 21 | if __name__ == '__main__': 22 | import os 23 | from gevent.server import StreamServer 24 | from pine.market.bitmex import BitMexOhlcAdaptor 25 | from pine.market.bitflyer import BitFlyerOhlcAdaptor 26 | from pine.market.base import PROXY_PORT 27 | 28 | server = OhlcvProxyServer() 29 | server.register_adaptor(BitMexOhlcAdaptor()) 30 | server.register_adaptor(BitFlyerOhlcAdaptor()) 31 | 32 | port = int(os.environ.get('PORT', PROXY_PORT)) 33 | #server = StreamServer(('127.0.0.1', port), server) 34 | #server.serve_forever() 35 | server = msgpackrpc.Server(server, unpack_encoding='utf-8') 36 | server.listen(msgpackrpc.Address('127.0.0.1', port)) 37 | server.start() 38 | -------------------------------------------------------------------------------- /pine/.gitignore: -------------------------------------------------------------------------------- 1 | parser.out 2 | -------------------------------------------------------------------------------- /pine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzh-dev/pine-bot-server/d36107328876adb3cd473587a5a31169fb6a5a98/pine/__init__.py -------------------------------------------------------------------------------- /pine/base.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | class PineError (Exception): 4 | pass 5 | -------------------------------------------------------------------------------- /pine/broker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzh-dev/pine-bot-server/d36107328876adb3cd473587a5a31169fb6a5a98/pine/broker/__init__.py -------------------------------------------------------------------------------- /pine/broker/base.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import math 4 | 5 | from ..vm.helper import NaN 6 | from ..base import PineError 7 | 8 | class BaseBroker (object): 9 | 10 | def __init__ (self): 11 | self.pyramiding = 0 12 | self.calc_on_order_fills = False 13 | self.calc_on_every_tick = True 14 | self.backtest_fill_limits_assumption = 0.0 15 | self.default_qty_type = 'fixed' 16 | self.default_qty_value = 1.0 17 | self.currency = 'NONE' 18 | self.slippage = 0 19 | self.commission_type = 'percent' 20 | self.commission_value = 'commission_value' 21 | 22 | self.actions = [] 23 | 24 | def setup (self, kws): 25 | v = kws.get('pyramiding', None) 26 | if v is not None: 27 | self.pyramiding = v 28 | v = kws.get('calc_on_order_fills', None) 29 | if v is not None: 30 | self.calc_on_order_fills = v 31 | v = kws.get('calc_on_every_tick', None) 32 | if v is not None: 33 | self.calc_on_every_tick = v 34 | v = kws.get('backtest_fill_limits_assumption', None) 35 | if v is not None: 36 | self.backtest_fill_limits_assumption = v 37 | v = kws.get('default_qty_type', None) 38 | if v is not None: 39 | self.default_qty_type = v 40 | v = kws.get('default_qty_value', None) 41 | if v is not None: 42 | self.default_qty_value = v 43 | v = kws.get('currency', None) 44 | if v is not None: 45 | self.currency = v 46 | v = kws.get('slippage', None) 47 | if v is not None: 48 | self.commission_type = 0.0 49 | 50 | if self.calc_on_order_fills: 51 | raise PineError('calc_on_order_fills is not supported') 52 | 53 | def entry (self, kws): 54 | #print('entry', kws) 55 | #oid = kws['id'] 56 | #is_long = kws['long'] 57 | #qty = kws.get('qty', self.default_qty_value) 58 | limit = kws.get('limit', None) 59 | stop = kws.get('stop', None) 60 | oca_name = kws.get('oca_name', '') 61 | oca_type = kws.get('oca_type', 'none') 62 | #comment = kws.get('comment', None) 63 | 64 | if limit is not None: 65 | raise PineError("limit order is not supported") 66 | if stop is not None: 67 | raise PineError("stop-limit order is not supported") 68 | if oca_name or oca_type: 69 | raise PineError("OCA order is not supported") 70 | 71 | kws['action'] = 'entry' 72 | self.add_action(kws) 73 | 74 | def close (self, kws): 75 | #print('close', kws) 76 | kws['action'] = 'close' 77 | self.add_action(kws) 78 | 79 | def close_all (self, kws): 80 | #print('close_all', kws) 81 | kws['action'] = 'close_all' 82 | self.add_action(kws) 83 | 84 | def add_action (self, action): 85 | self.actions.append(action) 86 | def clear_actions (self): 87 | self.actions = [] 88 | 89 | def position_size (self): 90 | raise NotImplementedError 91 | 92 | def step (self): 93 | raise NotImplementedError 94 | 95 | 96 | class Broker (BaseBroker): 97 | 98 | def __init__ (self): 99 | super().__init__() 100 | self.clear_positions() 101 | # TODO self.active_entry_orders[id], self.active_exit_orders[id] 102 | self.order_history = [] 103 | 104 | def clear_positions (self): 105 | self.positions = {} 106 | 107 | def position_size (self): 108 | s = 0.0 109 | for p in self.positions.values(): 110 | s += p['qty'] 111 | return s 112 | 113 | def step (self): 114 | # TODO need to separate order and execution for limit order. 115 | # We should have no pending orders here. 116 | # Don't touch self.positions during order making. 117 | orders = [] 118 | for a in self.actions: 119 | if a['action'] == 'entry': 120 | qty = a['qty'] 121 | if qty is None or math.isnan(qty): 122 | a['qty'] = self.default_qty_value 123 | if a['long']: 124 | orders += self.close_positions(False) 125 | orders += self.open_position(a) 126 | else: 127 | orders += self.close_positions(True) 128 | orders += self.open_position(a) 129 | elif a['action'] == 'close': 130 | orders += self.close_position(a['id']) 131 | elif a['action'] == 'close_all': 132 | orders += self.close_positions() 133 | else: 134 | raise PineError("Invalid strategy action type: {}".format(a)) 135 | 136 | self.clear_actions() 137 | 138 | # Apply orders 139 | self.apply_orders(orders) 140 | 141 | self.order_history.append(orders) 142 | return orders 143 | 144 | def close_position (self, oid): 145 | p = self.positions.get(oid, None) 146 | if p is None: 147 | return [] 148 | else: 149 | return [{'id': oid, 'qty': -p['qty']}] 150 | 151 | def close_positions (self, d=None): 152 | if d is None: 153 | ids = self.positions.keys() 154 | elif d: 155 | ids = [i for i,p in self.positions.items() if p['qty'] > 0] 156 | else: 157 | ids = [i for i,p in self.positions.items() if p['qty'] < 0] 158 | 159 | orders = [] 160 | for i in ids: 161 | orders += self.close_position(i) 162 | return orders 163 | 164 | def open_position (self, a): 165 | if a['long']: 166 | long_qty = sum([p['qty'] for p in self.positions.values() if p['qty'] > 0]) 167 | qty = a['qty'] - long_qty 168 | if qty <= 0: 169 | return [] 170 | else: 171 | short_qty = sum([p['qty'] for p in self.positions.values() if p['qty'] < 0]) 172 | qty = -a['qty'] - short_qty 173 | if qty >= 0: 174 | return [] 175 | 176 | p = self.positions.get(a['id'], None) 177 | if p: 178 | oqty = qty - p['qty'] 179 | if a['long'] and oqty <= 0: 180 | return [] 181 | if (not a['long']) and oqty >= 0: 182 | return [] 183 | else: 184 | oqty = qty 185 | return [{'id': a['id'], 'qty': oqty}] 186 | 187 | def apply_orders (self, orders): 188 | for o in orders: 189 | i = o['id'] 190 | p = self.positions.get(i, None) 191 | if p: 192 | qty = p['qty'] + o['qty'] 193 | if qty == 0: 194 | del self.positions[i] 195 | else: 196 | p['qty'] = qty 197 | else: 198 | self.positions[i] = o 199 | -------------------------------------------------------------------------------- /pine/broker/mirror.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from .base import BaseBroker 4 | 5 | class MirrorBroker (BaseBroker): 6 | 7 | def __init__ (self): 8 | super().__init__() 9 | self._position_size = 0 10 | 11 | def position_size (self): 12 | return self._position_size 13 | 14 | def update (self, **kws): 15 | self._position_size = kws['position_size'] 16 | 17 | def step (self): 18 | self.next_actions = self.actions 19 | self.clear_actions() 20 | -------------------------------------------------------------------------------- /pine/lexer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # see https://www.tradingview.com/wiki/Appendix_B._Pine_Script_v2_lexer_grammar 3 | 4 | import ply.lex as lex 5 | from ply.lex import TOKEN 6 | from .base import PineError 7 | 8 | # List of token names. This is always required 9 | tokens = ( 10 | 'COND', 'COND_ELSE', 11 | 'OR', 'AND', 'NOT', 12 | 'EQ', 'NEQ', 13 | 'GT', 'GE', 'LT', 'LE', 14 | 'PLUS', 'MINUS', 'MUL', 'DIV', 'MOD', 15 | 'COMMA', 16 | 'ARROW', 17 | 'LPAR', 'RPAR', 18 | 'LSQBR', 'RSQBR', 19 | 'DEFINE', 20 | 'IF_COND', 'IF_COND_ELSE', 21 | 'ASSIGN', 22 | 'FOR_STMT', 23 | 'FOR_STMT_TO', 24 | 'FOR_STMT_BY', 25 | 'BREAK', 26 | 'CONTINUE', 27 | 28 | 'BEGIN', 'END', 'DELIM', 29 | 30 | 'INT_LITERAL', 31 | 'FLOAT_LITERAL', 32 | 'BOOL_LITERAL', 33 | 'STR_LITERAL', 34 | 'COLOR_LITERAL', 35 | 36 | 'ID', 37 | #'ID_EX', 38 | ) 39 | 40 | def _surround (r): 41 | return r'(?:' + r + r')' 42 | 43 | def Lexer (): 44 | 45 | def r (t): 46 | t.lexer.last_token = t 47 | t.lexer.line_breakable = False 48 | return t 49 | def b (t): 50 | t.lexer.last_token = t 51 | t.lexer.line_breakable = True 52 | return t 53 | 54 | ### Grammer definitiion 55 | 56 | # Simple tokens 57 | def t_ASSIGN (t): 58 | r':=' 59 | return b(t) 60 | def t_COND (t): 61 | r'\?' 62 | return b(t) 63 | def t_COND_ELSE (t): 64 | r':' 65 | return b(t) 66 | def t_EQ (t): 67 | r'==' 68 | return b(t) 69 | def t_NEQ (t): 70 | r'!=' 71 | return b(t) 72 | def t_GE (t): 73 | r'>=' 74 | return b(t) 75 | def t_ARROW (t): 76 | r'=>' 77 | return r(t) 78 | def t_GT (t): 79 | r'>' 80 | return b(t) 81 | def t_LE (t): 82 | r'<=' 83 | return b(t) 84 | def t_LT (t): 85 | r'<' 86 | return b(t) 87 | def t_PLUS (t): 88 | r'\+' 89 | return b(t) 90 | def t_MINUS (t): 91 | r'-' 92 | return b(t) 93 | def t_MUL (t): 94 | r'\*' 95 | return b(t) 96 | def t_DIV (t): 97 | r'/' 98 | return b(t) 99 | def t_MOD (t): 100 | r'%' 101 | return b(t) 102 | def t_COMMA (t): 103 | r',' 104 | return b(t) 105 | def t_LSQBR (t): 106 | r'\[' 107 | t.lexer.in_braket += 1 108 | return r(t) 109 | def t_RSQBR (t): 110 | r'\]' 111 | t.lexer.in_braket -= 1 112 | return r(t) 113 | def t_DEFINE (t): 114 | r'=' 115 | return b(t) 116 | 117 | # reserved keywords (Needs to define as a method before hand ID to prevent from being swallowed. 118 | def t_IF_COND (t): 119 | r'\bif\b' 120 | return r(t) 121 | def t_IF_COND_ELSE (t): 122 | r'\belse\b' 123 | return r(t) 124 | def t_OR (t): 125 | r'\bor\b' 126 | return b(t) 127 | def t_AND (t): 128 | r'\band\b' 129 | return b(t) 130 | def t_NOT (t): 131 | r'\bnot\b' 132 | return r(t) 133 | def t_FOR_STMT (t): 134 | r'\bfor\b' 135 | return r(t) 136 | def t_FOR_STMT_TO (t): 137 | r'\bto\b' 138 | return r(t) 139 | def t_FOR_STMT_BY (t): 140 | r'\bby\b' 141 | return r(t) 142 | def t_BREAK (t): 143 | r'\bbreak\b' 144 | return r(t) 145 | def t_CONTINUE (t): 146 | r'\bcontinue\b' 147 | return r(t) 148 | 149 | # Parenthsis 150 | def t_LPAR (t): 151 | r'\(' 152 | t.lexer.in_paren += 1 153 | return r(t) 154 | def t_RPAR (t): 155 | r'\)' 156 | t.lexer.in_paren -= 1 157 | return r(t) 158 | 159 | # Pseudo tokens 160 | def t_BEGIN (t): 161 | r'\|BGN\|' 162 | if not t.lexer.in_paren and not t.lexer.in_braket and not t.lexer.line_breakable: 163 | return r(t) 164 | else: 165 | t.lexer.dummy_indent += 1 166 | def t_END (t): 167 | r'\|END\|' 168 | if not t.lexer.in_paren and not t.lexer.in_braket: 169 | if not t.lexer.dummy_indent: 170 | return r(t) 171 | t.lexer.dummy_indent -= 1 172 | def t_DELIM (t): 173 | r'\|DLM\|' 174 | if not t.lexer.in_paren and not t.lexer.in_braket and not t.lexer.line_breakable: 175 | return r(t) 176 | 177 | ## Literals 178 | 179 | # fragment DIGIT : ( '0' .. '9' ) ; 180 | # fragment DIGITS : ( '0' .. '9' )+ ; 181 | # fragment HEX_DIGIT : ( '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' ) ; 182 | # fragment EXP : ( 'e' | 'E' ) ( '+' | '-' )? DIGITS ; 183 | digit = _surround(r'\d') 184 | digits = _surround(r'\d+') 185 | hex_digit = _surround(r'[\dA-Fa-f]') 186 | exp = _surround(r'[eE][-+]?' + digits) 187 | 188 | # FLOAT_LITERAL : ( '.' DIGITS ( EXP )? | DIGITS ( '.' ( DIGITS ( EXP )? )? | EXP ) ); 189 | float1 = _surround(r'\.' + digits + exp + r'?') 190 | float2 = _surround(digits + r'\.' + _surround(digits + _surround(exp + r'?')) + r'?') 191 | float3 = exp 192 | float_ = _surround(float1 + '|' + float2 + '|' + float3) 193 | @TOKEN(float_) 194 | def t_FLOAT_LITERAL (t): 195 | t.value = float(t.value) 196 | return r(t) 197 | 198 | @TOKEN(digits) 199 | def t_INT_LITERAL (t): 200 | t.value = int(t.value, 10) 201 | return r(t) 202 | 203 | # BOOL_LITERAL : ( 'true' | 'false' ); 204 | def t_BOOL_LITERAL (t): 205 | r'true|false' 206 | t.value = (t.value == 'true') 207 | return r(t) 208 | 209 | # fragment ESC : '\\' . ; 210 | # STR_LITERAL : ( '"' ( ESC | ~ ( '\\' | '\n' | '"' ) )* '"' | '\'' ( ESC | ~ ( '\\' | '\n' | '\'' ) )* '\'' ); 211 | esc = r'\\' 212 | dqbody = _surround(esc + '|' + r'[^\\\n"]') + '*' 213 | dqstr = r'"' + dqbody + r'"' 214 | sqbody = _surround(esc + '|' + r"[^\\\n']") + '*' 215 | sqstr = r"'" + sqbody + r"'" 216 | str_ = dqstr + '|' + sqstr 217 | @TOKEN(str_) 218 | def t_STR_LITERAL (t): 219 | t.value = t.value[1:-1] 220 | return r(t) 221 | 222 | # COLOR_LITERAL : ( '#' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT | '#' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT ); 223 | color_literal_ = '\#' + hex_digit + '{6}' + _surround(hex_digit + '{2}') + '?' 224 | @TOKEN(color_literal_) 225 | def t_COLOR_LITERAL (t): 226 | return r(t) 227 | 228 | # fragment ID_BODY : ( ID_LETTER | DIGIT )+ ; 229 | # fragment ID_BODY_EX : ( ID_LETTER_EX | DIGIT )+ ; 230 | # fragment ID_LETTER : ( 'a' .. 'z' | 'A' .. 'Z' | '_' ) ; 231 | # fragment ID_LETTER_EX : ( 'a' .. 'z' | 'A' .. 'Z' | '_' | '#' ) ; 232 | # ID : ( ID_LETTER ) ( ( '\.' )? ( ID_BODY '\.' )* ID_BODY )? ; 233 | # ID_EX : ( ID_LETTER_EX ) ( ( '\.' )? ( ID_BODY_EX '\.' )* ID_BODY_EX )? ; 234 | id_letter = r'[A-Za-z_]' 235 | id_letter_ex = r'[A-Za-z_#]' 236 | id_body = r'\w+' 237 | id_body_ex = r'[\w#]+' 238 | id_ = id_letter + _surround(_surround(r'\.?' + id_body + r'\.') + '*' + id_body) + '?' 239 | #id_ex_ = id_letter_ex + _surround(_surround(r'\.?' + id_body_ex + r'\.') + '*' + id_body_ex) + '?' 240 | @TOKEN(id_) 241 | def t_ID (t): 242 | return r(t) 243 | #@TOKEN(id_ex_) 244 | #def t_ID_EX (t): 245 | # return r(t) 246 | 247 | ### Utilities 248 | t_ignore = ' \t' 249 | def t_newline(t): 250 | r'\n+' 251 | t.lexer.lineno += len(t.value) 252 | def t_error (t): 253 | raise PineError("invalid character: {}".format(t.value[0])) 254 | #t.lexer.skip(1) 255 | 256 | ### Make lexer 257 | lexer = lex.lex() 258 | lexer.in_paren = 0 259 | lexer.in_braket = 0 260 | lexer.dummy_indent = 0 261 | lexer.last_token = None 262 | lexer.line_breakable = False 263 | return lexer 264 | 265 | 266 | if __name__ == '__main__': 267 | import sys 268 | from .preprocess import preprocess 269 | with open(sys.argv[1]) as f: 270 | data = preprocess(f.read()) 271 | lexer = Lexer() 272 | lexer.input(data) 273 | 274 | # Tokenize 275 | while True: 276 | tok = lexer.token() 277 | if not tok: 278 | break # No more input 279 | print(tok.type, tok.value, tok.lineno, tok.lexpos) 280 | -------------------------------------------------------------------------------- /pine/market/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | -------------------------------------------------------------------------------- /pine/market/base.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | MARKETS = {} 4 | def register_market (market, cls): 5 | MARKETS[market] = cls 6 | 7 | class MarketError (Exception): 8 | pass 9 | 10 | class L(list): 11 | 12 | def __setitem__(self, index, value): 13 | if index >= len(self): 14 | self.extend([None]*(index + 1 - len(self))) 15 | list.__setitem__(self, index, value) 16 | 17 | def rindex (self, v): 18 | i = len(self) - 1 19 | while i >= 0: 20 | if self[i] == v: 21 | return i 22 | i -= 1 23 | return i 24 | 25 | def rindex_next (self, v): 26 | i = imax = len(self) - 1 27 | while i >= 0: 28 | if self[i] <= v: 29 | if i == imax: 30 | return -1 31 | return i + 1 32 | i -= 1 33 | return i 34 | 35 | def empty_udf (): 36 | return {'t':L(), 'o':L(), 'c':L(), 'h':L(), 'l':L(), 'v':L()} 37 | 38 | def rows_to_udf (rows): 39 | import numpy 40 | cols = numpy.array(rows).T 41 | udf = empty_udf() 42 | udf['t'] += [int(t) for t in cols[0]] 43 | udf['o'] += [t for t in cols[1]] 44 | udf['h'] += [t for t in cols[2]] 45 | udf['l'] += [t for t in cols[3]] 46 | udf['c'] += [t for t in cols[4]] 47 | udf['v'] += [t for t in cols[5]] 48 | return udf 49 | 50 | import calendar 51 | from datetime import datetime 52 | def utcunixtime (): 53 | now = datetime.utcnow() 54 | return calendar.timegm(now.utctimetuple()) 55 | 56 | def resolution_to_str (resolution): 57 | if resolution < 60: 58 | return str(resolution) 59 | if resolution < 60*24: 60 | n, u = int(resolution / 60), 'H' 61 | else: 62 | n, u = int(resolution / 60 / 24), 'D' 63 | if n == 1: 64 | n = '' 65 | return str(n)+u 66 | 67 | def str_to_resolution (string): 68 | try: 69 | return int(string) 70 | except: 71 | pass 72 | if string[-1] == 'H': 73 | n = string[0:-1] 74 | u = 60 75 | elif string[-1] == 'D': 76 | n = string[0:-1] 77 | u = 60 * 24 78 | else: 79 | raise PineError("invalid resolution: {}".format(string)) 80 | 81 | if not bool(n): 82 | n = 1 83 | return n * u 84 | 85 | class MarketBase (object): 86 | 87 | RESOLUTIONS = ( 88 | 1, 89 | 3, 90 | 5, 91 | 15, 92 | 30, 93 | 60 * 1, 94 | 60 * 2, 95 | 60 * 4, 96 | 60 * 6, 97 | 60 * 12, 98 | 60 * 24, 99 | ) 100 | 101 | def __init__ (self, market='MARKET', symbol='SYMBOL', resolution=240): 102 | self.market = market 103 | self.symbol = symbol 104 | self.resolution = resolution 105 | self.data = empty_udf() 106 | 107 | def ohlcv_df (self): 108 | import pandas as pd 109 | from collections import OrderedDict 110 | data = self.data 111 | return pd.DataFrame(OrderedDict( 112 | {"unixtime":data["t"], "open":data["o"], "high":data["h"], 113 | "low":data["l"], "close":data["c"], "volume":data["v"]})) 114 | 115 | def size (self): 116 | return len(self.data['t']) 117 | def timestamp (self, count=0): 118 | if count: 119 | return self.data['t'][-count] 120 | return self.data['t'] 121 | 122 | def open (self, count=0): 123 | if count: 124 | return self.data['o'][-count] 125 | return self.data['o'] 126 | def high (self, count=0): 127 | if count: 128 | return self.data['h'][-count] 129 | return self.data['h'] 130 | def low (self, count=0): 131 | if count: 132 | return self.data['l'][-count] 133 | return self.data['l'] 134 | def close (self, count=0): 135 | if count: 136 | return self.data['c'][-count] 137 | return self.data['c'] 138 | def volume (self, count=0): 139 | if count: 140 | return self.data['v'][-count] 141 | return self.data['v'] 142 | 143 | def mintick (self): 144 | raise NotImplementedError 145 | 146 | def period (self): 147 | return str_to_resolution(self.resolution) 148 | def tickerid (self): 149 | return ':'.join((self.market, self.symbol)) 150 | def mintick (self): 151 | raise NotImplementedError 152 | 153 | # float(ms) 154 | def bartimestamp (self): 155 | import datetime 156 | import math 157 | unixtime = utcunixtime 158 | step = 60 * self.resolution 159 | ts = math.floor(unixtime / step) * step 160 | return ts * 1000.0 161 | 162 | 163 | class Market (MarketBase): 164 | 165 | def __init__ (self, market='MARKET', symbol='SYMBOL', resolution=240): 166 | super().__init__(market, symbol, resolution) 167 | for k in self.data.keys(): 168 | self.data[k].append(0) 169 | 170 | def mintick (self): 171 | return 0.0 172 | 173 | 174 | PROXY_PORT = 7000 175 | 176 | import threading 177 | import fasteners 178 | import queue 179 | import time 180 | class MarketOhlcvAdapter (object): 181 | 182 | MIN_COUNT = 500 183 | MAX_COUNT = 10000 184 | 185 | UDF_RESOLUTIONS = ( 1, 5, 60, 60*24 ) 186 | 187 | def __init__ (self, tickerid='MARKET:SYMBOL'): 188 | super().__init__() 189 | self.tickerid = tickerid 190 | self.candles = {} 191 | self.lock = fasteners.ReaderWriterLock() 192 | self.queues = {} 193 | self.start_threads() 194 | 195 | def ohlcv (self, resolution, count): 196 | candles = self.candles[resolution] 197 | with self.lock.read_lock(): 198 | return dict( 199 | t=candles['t'][-count:], 200 | o=candles['o'][-count:], 201 | h=candles['h'][-count:], 202 | l=candles['l'][-count:], 203 | c=candles['c'][-count:], 204 | v=candles['v'][-count:], 205 | ) 206 | 207 | def step_ohlcv (self, resolution, next_clock): 208 | candles = self.candles[resolution] 209 | with self.lock.read_lock(): 210 | i = candles['t'].rindex(next_clock) 211 | if i <= 0: 212 | return (None, None) 213 | else: 214 | return ( 215 | dict( 216 | t=candles['t'][i-1], 217 | o=candles['o'][i-1], 218 | h=candles['h'][i-1], 219 | l=candles['l'][i-1], 220 | c=candles['c'][i-1], 221 | v=candles['v'][i-1], 222 | ), 223 | dict( 224 | t=candles['t'][i], 225 | o=candles['o'][i], 226 | h=candles['h'][i], 227 | l=candles['l'][i], 228 | c=candles['c'][i], 229 | v=candles['v'][i], 230 | ), 231 | ) 232 | 233 | def start_threads (self): 234 | resolutions = list(MarketBase.RESOLUTIONS) 235 | res_groups = [] 236 | for udf_res in reversed(self.UDF_RESOLUTIONS): 237 | child_res = [] 238 | while True: 239 | res = resolutions.pop(-1) 240 | if udf_res < res: 241 | child_res.insert(0, res) 242 | else: 243 | res_groups.append((udf_res, child_res)) 244 | break 245 | 246 | # maintainers 247 | for res, children in res_groups: 248 | self.queues[res] = queue.Queue() 249 | t = threading.Thread(target=self.candle_maintainer, 250 | args=(res, children), daemon=True) 251 | t.start() 252 | 253 | # loader 254 | t = threading.Thread(target=self.candle_loader, args=(res_groups,), daemon=True) 255 | t.start() 256 | 257 | def fetch_candles (self, resolution, from_, to): 258 | raise NotImplementedError 259 | 260 | def candle_loader (self, resolution_groups): 261 | next_timestamps = {} 262 | now = utcunixtime() 263 | for res, children in resolution_groups: 264 | maxres = res 265 | if children: 266 | maxres = children[-1] 267 | next_timestamps[res] = now - maxres * 60 * (self.MIN_COUNT - 1) 268 | 269 | while True: 270 | now = utcunixtime() 271 | # Fetch if necessary 272 | for res, next_ts in next_timestamps.items(): 273 | if now < next_ts: 274 | continue 275 | # try to get new candles 276 | candles = self.fetch_candles(res, next_ts - res * 60, now) 277 | ts = candles['t'][-1] 278 | if ts < next_ts: 279 | continue 280 | # notify & schedule 281 | self.queues[res].put(candles) 282 | next_timestamps[res] = ts + res * 60 283 | #print(candles) 284 | 285 | # Schedule 286 | earlist = min(next_timestamps.values()) 287 | interval = earlist - utcunixtime() 288 | if interval < 1: 289 | interval = 1 290 | interval = 3 291 | time.sleep(interval) 292 | 293 | def downsample_candle (self, source_res, target_res): 294 | source = self.candles[source_res] 295 | target = self.candles.get(target_res, None) 296 | slen = len(source['t']) 297 | tres = target_res * 60 298 | 299 | # Find last ts of target 300 | if target: 301 | ti = len(target['t']) - 1 302 | target_ts = target['t'][ti] 303 | else: 304 | ti = 0 305 | target_ts = 0 306 | target = empty_udf() 307 | self.candles[target_res] = target 308 | 309 | # Find corresponding source ts 310 | if target_ts: 311 | si = slen - 1 312 | while si: 313 | source_ts = source['t'][si] 314 | if source_ts <= target_ts and source_ts % tres == 0: 315 | break 316 | si -= 1 317 | else: 318 | for i in range(0, slen): 319 | if source['t'][i] % tres == 0: 320 | si = i 321 | break 322 | 323 | # Down sample 324 | o = c = h = l = v = 0 325 | for i in range(si, slen): 326 | sts = source['t'][i] 327 | if sts % tres == 0: 328 | if o: 329 | target['t'][ti] = sts - tres 330 | target['o'][ti] = o 331 | target['c'][ti] = c 332 | target['h'][ti] = h 333 | target['l'][ti] = l 334 | target['v'][ti] = v 335 | ti += 1 336 | o = source['o'][i] 337 | c = source['c'][i] 338 | h = source['h'][i] 339 | l = source['l'][i] 340 | v = source['v'][i] 341 | else: 342 | c = source['c'][i] 343 | h = max((h, source['h'][i])) 344 | l = min((l, source['l'][i])) 345 | v += source['v'][i] 346 | if o: 347 | target['t'][ti] = target['t'][ti-1] + tres 348 | target['o'][ti] = o 349 | target['c'][ti] = c 350 | target['h'][ti] = h 351 | target['l'][ti] = l 352 | target['v'][ti] = v 353 | 354 | if len(target) > self.MAX_COUNT: 355 | del target[0:self.MAX_COUNT/2] 356 | 357 | def update_candles (self, res, candles): 358 | target = self.candles.get(res, None) 359 | if target is None: 360 | target = empty_udf() 361 | self.candles[res] = target 362 | 363 | ti = target['t'].rindex(candles['t'][0]) 364 | if ti < 0: 365 | ti = 0 366 | 367 | for i in range(0, len(candles['t'])): 368 | target['t'][ti+i] = candles['t'][i] 369 | target['o'][ti+i] = candles['o'][i] 370 | target['h'][ti+i] = candles['h'][i] 371 | target['l'][ti+i] = candles['l'][i] 372 | target['c'][ti+i] = candles['c'][i] 373 | target['v'][ti+i] = candles['v'][i] 374 | 375 | if len(target) > self.MAX_COUNT: 376 | del target[0:self.MAX_COUNT/2] 377 | 378 | def candle_maintainer (self, myres, children): 379 | maxres = myres 380 | if children: 381 | maxres = children[-1] 382 | 383 | while True: 384 | candles = self.queues[myres].get() 385 | 386 | with self.lock.write_lock(): 387 | self.update_candles(myres, candles) 388 | 389 | # downsample 390 | for dres in children: 391 | self.downsample_candle(myres, dres) 392 | 393 | # debug 394 | from datetime import datetime 395 | now = datetime.now() 396 | for res in [myres] + children: 397 | udf = self.candles[res] 398 | print("{}: {}: {}: #={}: t={} c={} v={}".format(now, self.tickerid, res, len(udf['t']), int(udf['t'][-1] % 3600 / 60), udf['c'][-1], udf['v'][-1])) 399 | -------------------------------------------------------------------------------- /pine/market/bitflyer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import requests 4 | 5 | from .base import MarketBase, MarketError, empty_udf, utcunixtime, register_market, rows_to_udf 6 | 7 | URL_TMPL = "https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?" + \ 8 | "periods={resolution}&after={f}&before={t}" 9 | 10 | MARKET = 'BITFLYER' 11 | SYMBOL = 'FXBTCJPY' 12 | TICKERID = ':'.join((MARKET, SYMBOL)) 13 | 14 | class BitFlyerMarketBase (MarketBase): 15 | 16 | SYMBOLS = (SYMBOL,) 17 | 18 | def __init__ (self, symbol=SYMBOL, resolution=60): 19 | super().__init__(MARKET, symbol, resolution) 20 | 21 | def mintick (self): 22 | return 1 23 | 24 | 25 | class BitFlyerMarketDirect (BitFlyerMarketBase): 26 | 27 | def __init__ (self, symbol=SYMBOL, resolution=60): 28 | super().__init__(symbol, resolution) 29 | 30 | resolution *= 60 31 | unixtime = utcunixtime() 32 | since = unixtime - resolution * 256 33 | 34 | url = URL_TMPL.format(resolution=resolution, f=since-1, t=unixtime+1) 35 | res = requests.get(url).json().get('result', None) 36 | if res: 37 | rows = res.get(str(resolution), None) 38 | if rows: 39 | self.data = rows_to_udf(rows) 40 | 41 | # CandleProxyClient 42 | from .base import PROXY_PORT 43 | from mprpc import RPCClient 44 | class BitFlyerMarket (BitFlyerMarketBase): 45 | 46 | def __init__ (self, symbol=SYMBOL, resolution=60, port=PROXY_PORT): 47 | super().__init__(symbol, resolution) 48 | 49 | self.client = RPCClient('127.0.0.1', port) 50 | self.data = self.client.call('ohlcv', TICKERID, self.resolution, 256) 51 | 52 | def step_ohlcv (self, next_clock): 53 | d1, d0 = self.client.call('step_ohlcv', TICKERID, self.resolution, next_clock) 54 | if d1 is None: 55 | return None 56 | if d0['t'] <= self.data['t'][-1]: 57 | return None 58 | for k,v in d0.items(): 59 | self.data[k].pop(0) 60 | self.data[k][-1] = d1[k] 61 | self.data[k].append(v) 62 | return d0['t'] 63 | 64 | register_market(MARKET, BitFlyerMarket) 65 | #register_market(MARKET, BitFlyerMarketDirect) 66 | 67 | # CandleProxyServer 68 | from .base import MarketOhlcvAdapter 69 | class BitFlyerOhlcAdaptor (MarketOhlcvAdapter): 70 | 71 | def __init__ (self): 72 | super().__init__(TICKERID) 73 | 74 | def fetch_candles (self, resolution, from_, to): 75 | resolution *= 60 76 | url = URL_TMPL.format(resolution=resolution, f=from_, t=to) 77 | intvl = 1.0 78 | while True: 79 | try: 80 | j = requests.get(url).json() 81 | res = j.get('result', None) 82 | if res is None: 83 | raise MarketError('missing result: {}'.format(j)) 84 | rows = res.get(str(resolution), None) 85 | if rows is None: 86 | raise MarketError('invalid result: {}'.format(res)) 87 | return rows_to_udf(rows) 88 | except Exception as e: 89 | print(e) 90 | 91 | #if __name__ == '__main__': 92 | # BitFlyerMarketDirect() 93 | 94 | if __name__ == '__main__': 95 | import os 96 | from gevent.server import StreamServer 97 | port = int(os.environ.get('PORT', PROXY_PORT)) 98 | cli = BitFlyerMarket(port=port) 99 | print(cli.data) 100 | -------------------------------------------------------------------------------- /pine/market/bitmex.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import requests 4 | 5 | from .base import MarketBase, MarketError, empty_udf, utcunixtime, register_market 6 | 7 | URL_TMPL = "https://www.bitmex.com/api/udf/history?symbol=XBTUSD&" + \ 8 | "resolution={resolution}&from={f}&to={t}" 9 | 10 | MARKET = 'BITMEX' 11 | SYMBOL = 'XBTUSD' 12 | TICKERID = 'BITMEX:XBTUSD' 13 | 14 | class BitMexMarketBase (MarketBase): 15 | 16 | SYMBOLS = (SYMBOL,) 17 | 18 | def __init__ (self, symbol=SYMBOL, resolution=60): 19 | super().__init__(MARKET, symbol, resolution) 20 | 21 | def mintick (self): 22 | return 0.5 23 | 24 | 25 | class BitMexMarketDirect (BitMexMarketBase): 26 | 27 | def __init__ (self, symbol='XBTUSD', resolution=60): 28 | super().__init__(symbol, resolution) 29 | 30 | unixtime = utcunixtime() 31 | since = unixtime - resolution * 60 * 256 32 | if resolution >= 1440: 33 | resolution = "{}D".format(resolution / 1440) 34 | 35 | url = URL_TMPL.format(resolution=resolution, f=since, t=unixtime) 36 | self.data = requests.get(url).json() 37 | 38 | 39 | # CandleProxyClient 40 | from .base import PROXY_PORT 41 | from mprpc import RPCClient 42 | import msgpackrpc 43 | 44 | class BitMexMarket (BitMexMarketBase): 45 | 46 | def __init__ (self, symbol=SYMBOL, resolution=60, port=PROXY_PORT): 47 | super().__init__(symbol, resolution) 48 | 49 | #self.client = RPCClient('127.0.0.1', port) 50 | self.client = msgpackrpc.Client(msgpackrpc.Address('127.0.0.1', port), unpack_encoding='utf-8') 51 | data = self.client.call('ohlcv', TICKERID, self.resolution, 256) 52 | self.data = dict( 53 | t=list(data['t']), 54 | o=list(data['o']), 55 | h=list(data['h']), 56 | l=list(data['l']), 57 | c=list(data['c']), 58 | v=list(data['v']), 59 | ) 60 | 61 | def step_ohlcv (self, next_clock): 62 | d1, d0 = self.client.call('step_ohlcv', TICKERID, self.resolution, next_clock) 63 | if d1 is None: 64 | return None 65 | if d0['t'] <= self.data['t'][-1]: 66 | return None 67 | for k,v in d0.items(): 68 | self.data[k].pop(0) 69 | self.data[k][-1] = d1[k] 70 | self.data[k].append(v) 71 | return d0['t'] 72 | 73 | register_market(MARKET, BitMexMarket) 74 | 75 | # CandleProxyServer 76 | from .base import MarketOhlcvAdapter 77 | class BitMexOhlcAdaptor (MarketOhlcvAdapter): 78 | 79 | def __init__ (self): 80 | super().__init__(TICKERID) 81 | 82 | def fetch_candles (self, resolution, from_, to): 83 | if resolution >= 60 * 24: 84 | resolution = "{}D".format(int(resolution / 60 / 24)) 85 | url = URL_TMPL.format(resolution=resolution, f=from_, t=to) 86 | intvl = 1.0 87 | while True: 88 | try: 89 | j = requests.get(url).json() 90 | if j['s'] != 'ok': 91 | raise MarketError("UDF refused: {}".format(j)) 92 | return j 93 | except Exception as e: 94 | print(e) 95 | 96 | if __name__ == '__main__': 97 | import os 98 | from gevent.server import StreamServer 99 | port = int(os.environ.get('PORT', PROXY_PORT)) 100 | cli = BitMexMarket(port=port) 101 | print(cli.data) 102 | -------------------------------------------------------------------------------- /pine/market/mirror.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from logging import getLogger 4 | logger = getLogger(__name__) 5 | 6 | from .base import MarketBase 7 | 8 | class MirrorMarket (MarketBase): 9 | 10 | def __init__ (self, *args): 11 | super().__init__(*args) 12 | 13 | def set_ohlcv (self, ohlcv): 14 | for col, candles in ohlcv.items(): 15 | self.data[col].clear() 16 | self.data[col] += candles 17 | 18 | def update_ohlcv2 (self, ohlcv2): 19 | for col, candles in ohlcv2.items(): 20 | self.data[col].pop(0) 21 | self.data[col][-2] = candles[0] 22 | self.data[col][-1] = candles[1] 23 | -------------------------------------------------------------------------------- /pine/parser.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # see https://www.tradingview.com/wiki/Appendix_B._Pine_Script_v2_lexer_gramma://www.tradingview.com/wiki/Appendix_C._Pine_Script_v2_parser_grammar 3 | 4 | import ply.yacc as yacc 5 | from .lexer import tokens 6 | from .base import PineError 7 | 8 | from .vm import node as vm 9 | 10 | ## helper 11 | def make_list2 (p, cls, lidx): 12 | if len(p) == 2: 13 | return p[1] 14 | else: 15 | l = p[1] 16 | if not isinstance(l, cls): 17 | l = cls().append(p[1]).lineno(p.lineno(lidx)) 18 | l.append(p[3]) 19 | return l 20 | 21 | def make_binop (p): 22 | if len(p) == 2: 23 | return p[1] 24 | else: 25 | return vm.BinOpNode(p[2], p[1], p[3]).lineno(p.lineno(2)) 26 | 27 | 28 | ## grammer definition 29 | def p_statement_list (p): 30 | '''statement_list : 31 | | statement 32 | | statement_list statement''' 33 | if len(p) == 1: 34 | p[0] == None 35 | elif len(p) == 2: 36 | p[0] = vm.Node().append(p[1]) 37 | else: 38 | p[0] = p[1].append(p[2]) 39 | 40 | def p_statement (p): 41 | '''statement : fun_def_stmt 42 | | expression_stmt 43 | | loop_break_stmt 44 | | loop_continue_stmt''' 45 | p[0] = p[1] 46 | 47 | # simple statements 48 | def p_loop_break_stmt (p): 49 | 'loop_break_stmt : BREAK DELIM' 50 | raise NotImplementedError 51 | 52 | def p_loop_continue_stmt (p): 53 | 'loop_continue_stmt : CONTINUE DELIM' 54 | raise NotImplementedError 55 | 56 | # expression statements 57 | def p_expression_stmt (p): 58 | '''expression_stmt : simple_expression DELIM 59 | | complex_expression 60 | | var_def_stmt 61 | | var_defs_stmt 62 | | var_assign_stmt''' 63 | p[0] = p[1] 64 | 65 | def p_var_assign_stmt (p): 66 | 'var_assign_stmt : ID ASSIGN expression DELIM' 67 | p[0] = vm.VarAssignNode(p[1], p[3]).lineno(p.lineno(1)) 68 | 69 | def p_var_def_stmt1 (p): 70 | '''var_def_stmt : var_def DELIM 71 | | var_def COMMA 72 | | var_def2''' 73 | p[0] = p[1] 74 | 75 | def p_var_def (p): 76 | 'var_def : ID DEFINE simple_expression' 77 | p[0] = vm.VarDefNode(p[1], p[3]).lineno(p.lineno(1)) 78 | 79 | def p_var_def2 (p): 80 | 'var_def2 : ID DEFINE complex_expression' 81 | p[0] = vm.VarDefNode(p[1], p[3]).lineno(p.lineno(1)) 82 | 83 | def p_var_defs_stmt (p): 84 | 'var_defs_stmt : LSQBR id_list RSQBR DEFINE LSQBR simple_expr_list RSQBR DELIM' 85 | raise NotImplementedError 86 | 87 | def p_id_list (p): 88 | '''id_list : ID 89 | | id_list COMMA ID''' 90 | p[0] = make_list2(p, vm.Node, 1) 91 | 92 | # expression 93 | def p_expression (p): 94 | '''expression : simple_expression 95 | | complex_expression''' 96 | p[0] = p[1] 97 | 98 | # simple_expression 99 | def p_simple_expr_list (p): 100 | '''simple_expr_list : simple_expression 101 | | simple_expr_list COMMA simple_expression 102 | | id_list 103 | | id_list COMMA simple_expression''' 104 | #print(p[1]) 105 | converted = False 106 | # Convert id_list to list of VarRefNode 107 | if isinstance(p[1], str): 108 | n = vm.Node().append(vm.VarRefNode(p[1])) 109 | converted = True 110 | elif len(p[1].children) > 0 and isinstance(p[1].children[0], str): 111 | n = vm.Node() 112 | for s in p[1].children: 113 | n.append(vm.VarRefNode(s)) 114 | converted = True 115 | else: 116 | n = p[1] 117 | 118 | if len(p) == 2: 119 | if not converted: 120 | n = vm.Node().append(n) 121 | else: 122 | n.append(p[3]) 123 | 124 | p[0] = n 125 | 126 | 127 | # Actually ternary_expression 128 | def p_simple_expression (p): 129 | '''simple_expression : or_expr 130 | | or_expr COND simple_expression COND_ELSE simple_expression''' 131 | if len(p) == 2: 132 | p[0] = p[1] 133 | else: 134 | p[0] = vm.IfNode(p[1], p[3], p[5]).lineno(p.lineno(2)) 135 | 136 | def p_or_expr (p): 137 | '''or_expr : and_expr 138 | | or_expr OR and_expr''' 139 | p[0] = make_list2(p, vm.OrNode, 2) 140 | 141 | def p_and_expr (p): 142 | '''and_expr : eq_expr 143 | | and_expr AND eq_expr''' 144 | p[0] = make_list2(p, vm.AndNode, 2) 145 | 146 | def p_eq_expr (p): 147 | '''eq_expr : cmp_expr 148 | | cmp_expr EQ cmp_expr 149 | | cmp_expr NEQ cmp_expr''' 150 | p[0] = make_binop(p) 151 | 152 | def p_cmp_expr (p): 153 | '''cmp_expr : add_expr 154 | | add_expr GT add_expr 155 | | add_expr GE add_expr 156 | | add_expr LT add_expr 157 | | add_expr LE add_expr''' 158 | p[0] = make_binop(p) 159 | 160 | def p_add_expr (p): 161 | '''add_expr : mul_expr 162 | | add_expr PLUS mul_expr 163 | | add_expr MINUS mul_expr''' 164 | p[0] = make_binop(p) 165 | 166 | def p_mul_expr (p): 167 | '''mul_expr : unary_expr 168 | | mul_expr MUL unary_expr 169 | | mul_expr DIV unary_expr 170 | | mul_expr MOD unary_expr''' 171 | p[0] = make_binop(p) 172 | 173 | precedence = ( 174 | ('right', 'UPLUS', 'UMINUS'), # Unary minus operator 175 | ) 176 | def p_unary_expr (p): 177 | '''unary_expr : sqbr_expr 178 | | NOT sqbr_expr 179 | | PLUS sqbr_expr %prec UPLUS 180 | | MINUS sqbr_expr %prec UMINUS''' 181 | if len(p) == 2: 182 | p[0] = p[1] 183 | else: 184 | p[0] = vm.UniOpNode(p[1], p[2]).lineno(p.lineno(1)) 185 | 186 | def p_sqbr_expr (p): 187 | '''sqbr_expr : atom 188 | | atom LSQBR simple_expression RSQBR''' 189 | p[0] = make_binop(p) 190 | 191 | def p_atom (p): 192 | '''atom : fun_call 193 | | var_ref 194 | | literal 195 | | LPAR simple_expression RPAR''' 196 | if len(p) == 2: 197 | p[0] = p[1] 198 | else: 199 | p[0] = p[2] 200 | 201 | def p_var_ref (p): 202 | 'var_ref : ID' 203 | p[0] = vm.VarRefNode(p[1]).lineno(p.lineno(1)) 204 | 205 | def p_fun_call (p): 206 | '''fun_call : fun_call0 207 | | fun_call1 208 | | fun_call2 209 | | fun_call3 210 | | fun_call4 211 | | fun_call5''' 212 | p[0] = p[1] 213 | 214 | def _id2varref (v): 215 | if isinstance(v, str): 216 | return vm.Node().append(vm.VarRefNode(v)) 217 | else: 218 | n = vm.Node() 219 | for s in v.children: 220 | n.append(vm.VarRefNode(s)) 221 | return n 222 | 223 | def p_fun_call0 (p): 224 | 'fun_call0 : ID LPAR RPAR' 225 | p[0] = vm.FunCallNode(p[1], (None, None)).lineno(p.lineno(1)) 226 | 227 | def p_fun_call1 (p): 228 | 'fun_call1 : ID LPAR id_list RPAR' 229 | p[0] = vm.FunCallNode(p[1], (_id2varref(p[3]), None)).lineno(p.lineno(1)) 230 | 231 | def p_fun_call2 (p): 232 | 'fun_call2 : ID LPAR simple_expr_list RPAR' 233 | p[0] = vm.FunCallNode(p[1], (p[3], None)).lineno(p.lineno(1)) 234 | 235 | def p_fun_call3 (p): 236 | 'fun_call3 : ID LPAR kw_arg_list RPAR' 237 | p[0] = vm.FunCallNode(p[1], (None, p[3])).lineno(p.lineno(1)) 238 | 239 | def p_fun_call4 (p): 240 | 'fun_call4 : ID LPAR id_list COMMA kw_arg_list RPAR' 241 | p[0] = vm.FunCallNode(p[1], (_id2varref(p[3]), p[5])).lineno(p.lineno(1)) 242 | 243 | def p_fun_call5 (p): 244 | 'fun_call5 : ID LPAR simple_expr_list COMMA kw_arg_list RPAR' 245 | p[0] = vm.FunCallNode(p[1], (p[3], p[5])).lineno(p.lineno(1)) 246 | 247 | def p_kw_arg_list (p): 248 | '''kw_arg_list : kw_arg 249 | | kw_arg_list COMMA kw_arg''' 250 | if len(p) == 2: 251 | d, kv = {}, p[1] 252 | else: 253 | d, kv = p[1], p[3] 254 | d[kv[0]] = kv[1] 255 | p[0] = d 256 | 257 | def p_kw_arg (p): 258 | 'kw_arg : ID DEFINE simple_expression' 259 | p[0] = (p[1], p[3]) 260 | 261 | def p_literal (p): 262 | '''literal : INT_LITERAL 263 | | FLOAT_LITERAL 264 | | STR_LITERAL 265 | | BOOL_LITERAL 266 | | COLOR_LITERAL 267 | | list_literal''' 268 | p[0] = vm.LiteralNode(p[1]).lineno(p.lineno(1)) 269 | 270 | def p_list_leteral (p): 271 | 'list_literal : LSQBR simple_expr_list RSQBR' 272 | p[0] = p[2] 273 | p.set_lineno(0, p.lineno(1)) 274 | 275 | # complex expression 276 | def p_complex_expression (p): 277 | '''complex_expression : if_expr 278 | | for_expr''' 279 | p[0] = p[1] 280 | 281 | def p_if_expr (p): 282 | '''if_expr : IF_COND simple_expression stmts_block 283 | | IF_COND simple_expression stmts_block IF_COND_ELSE stmts_block''' 284 | if len(p) == 4: 285 | p[0] = vm.IfNode(p[2], p[3], None, False).lineno(p.lineno(1)) 286 | else: 287 | p[0] = vm.IfNode(p[2], p[3], p[5], False).lineno(p.lineno(1)) 288 | 289 | def p_for_expr (p): 290 | '''for_expr : FOR_STMT var_def FOR_STMT_TO simple_expression stmts_block 291 | | FOR_STMT var_def FOR_STMT_TO simple_expression FOR_STMT_BY''' # FIXME 292 | p[0] = vm.ForNode(p[2], p[4], p[5]).lineno(p.lineno(1)) 293 | 294 | def p_stmts_block (p): 295 | 'stmts_block : BEGIN statement_list END' 296 | p[0] = p[2] 297 | 298 | 299 | # function definition 300 | def p_fun_def_stmt (p): 301 | '''fun_def_stmt : fun_def_stmt_1 302 | | fun_def_stmt_m''' 303 | p[0] = p[1] 304 | 305 | def p_fun_def_stmt_1 (p): 306 | 'fun_def_stmt_1 : ID LPAR id_list RPAR ARROW simple_expression DELIM' 307 | p[0] = vm.FunDefNode(p[1], p[3], p[6]).lineno(p.lineno(1)) 308 | 309 | def p_fun_def_stmt_m (p): 310 | 'fun_def_stmt_m : ID LPAR id_list RPAR ARROW stmts_block' 311 | p[0] = vm.FunDefNode(p[1], p[3], p[6]).lineno(p.lineno(1)) 312 | 313 | ## Error handling 314 | class PineSyntaxError (PineError): 315 | pass 316 | 317 | def p_error (p): 318 | raise PineError("Unexpected token: {}".format(p)) 319 | 320 | 321 | from .lexer import Lexer 322 | def parse (data): 323 | parser = yacc.yacc() 324 | #import logging 325 | #logger = logging.getLogger() 326 | #logger.setLevel(logging.DEBUG) 327 | return parser.parse(data, lexer=Lexer())#), debug=logger) 328 | 329 | 330 | if __name__ == '__main__': 331 | import sys 332 | import re 333 | from .preprocess import preprocess 334 | with open(sys.argv[1]) as f: 335 | data = preprocess(f.read()) 336 | lines = data.splitlines() 337 | node = parse(data) 338 | for n in node.children: 339 | print("{0}: {1}: {2}".format(n.lno, lines[n.lno-1], n)) 340 | -------------------------------------------------------------------------------- /pine/preprocess.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # see https://www.tradingview.com/wiki/Appendix_A._Pine_Script_v2_preprocessor 3 | 4 | import re 5 | 6 | COMMENT_RE = re.compile(r'//.*$', re.MULTILINE) 7 | def remove_comment (string): 8 | return COMMENT_RE.sub('', string) 9 | 10 | INDENT_RE = re.compile(r'[ \t]+') 11 | def complement_block_tokens (string): 12 | lines = [] 13 | prev = 0 14 | last = 0 15 | for line in string.splitlines(): 16 | line = line.rstrip() 17 | 18 | if line: 19 | mo = INDENT_RE.match(line) 20 | if mo: 21 | ws = mo.group().replace("\t", ' ') # TAB = WS x 4? 22 | indent = int((len(ws) + 3 ) / 4) 23 | else: 24 | indent = 0 25 | 26 | if prev < indent: 27 | lines[last][1] = '|BGN|' * (indent - prev) 28 | elif prev > indent: 29 | lines[last][1] += '|END|' * (prev - indent) 30 | 31 | prev = indent 32 | delim = '|DLM|' 33 | last = len(lines) 34 | else: 35 | delim = '' 36 | 37 | lines.append([line, delim]) 38 | 39 | if prev != 0: 40 | lines[-1][1] += '|END|' * prev 41 | 42 | return "\n".join([l + d for l,d in lines]) 43 | 44 | def preprocess (string): 45 | # 1. remove comment FIXME 46 | string = remove_comment(string) 47 | 48 | # 2. insert block delimiter tokens 49 | string = complement_block_tokens(string) 50 | return string 51 | 52 | 53 | if __name__ == '__main__': 54 | import sys 55 | with open(sys.argv[1]) as f: 56 | print(preprocess(f.read())) 57 | -------------------------------------------------------------------------------- /pine/runner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from .vm.vm import InputScanVM, VM 4 | from .vm.compile import compile_pine 5 | from .market.base import Market 6 | from .market.bitmex import BitMexMarket 7 | from .vm.plot import PlotVM 8 | from .broker.base import Broker 9 | 10 | if __name__ == '__main__': 11 | import sys 12 | op = sys.argv[1] 13 | with open(sys.argv[2]) as f: 14 | node = compile_pine(f.read()) 15 | 16 | if op == 'input': 17 | vm = InputScanVM(Market()) 18 | vm.load_node(node) 19 | vm.node.dump() 20 | print(vm.meta) 21 | print(vm.run()) 22 | vm.dump_registers() 23 | from .market.base import MARKETS 24 | elif op == 'run': 25 | market = BitMexMarket() 26 | vm = VM(market) 27 | vm.load_node(node) 28 | vm.node.dump() 29 | vm.set_broker(Broker()) 30 | vm.run() 31 | vm.dump_registers() 32 | elif op == 'step': 33 | market = BitMexMarket() 34 | vm = VM(market) 35 | vm.load_node(node) 36 | vm.node.dump() 37 | vm.set_broker(Broker()) 38 | vm.step() 39 | vm.dump_registers() 40 | elif op == 'plot': 41 | vm = PlotVM(BitMexMarket()) 42 | vm.load_node(node) 43 | vm.run() 44 | -------------------------------------------------------------------------------- /pine/sample/a.pine: -------------------------------------------------------------------------------- 1 | //@version=2 2 | study('Preprocessor example') 3 | //if close > open // This line has one indent 4 | // x + y // This line has two indents 5 | //else 6 | // x - y 7 | 8 | fun(x, y) => 9 | if close > open // This line has one indent 10 | x + y // This line has two indents 11 | x + y // This line has two indents 12 | else 13 | x - y 14 | // Some whitespace and a comment 15 | // 16 | a = sma(close, 10) 17 | b = fun(a, 123) 18 | //c = security(tickerid, period, b) 19 | plot(b, title='Out', color=b > b[1] ? lime : red, // This statement will be continued on the next line 20 | style=linebr, trackprice=true) // It's prefixed with 5 spaces, so it won't be considered as an indent 21 | //alertcondition(c > 100) 22 | -------------------------------------------------------------------------------- /pine/sample/divergence.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | study("ダイバージェンス_v1",overlay=false) 3 | 4 | //入力設定 5 | len_high = input(10,title="Length High") 6 | len_low = input(10,title="Length low") 7 | len_rsi = input(20,title="rsi期間") 8 | 9 | //rsi計算 10 | rsi = rsi(close,len_rsi) 11 | 12 | //ローカル高値、安値確定タイミング 13 | phi = pivothigh(high,len_high,len_high) 14 | plo = pivotlow(low,len_low,len_low) 15 | 16 | plot(phi) 17 | plot(plo) 18 | 19 | //高値 20 | hi = high 21 | hi := phi ? high[len_high] : hi[1] 22 | 23 | //安値 24 | lo = low 25 | lo := plo ? low[len_low] : lo[1] 26 | 27 | //チャート高値タイミングのrsi 28 | hi_rsi = rsi 29 | hi_rsi := phi ? rsi[len_high] : hi_rsi[1] 30 | 31 | //チャート安値タイミングのrsi 32 | lo_rsi = rsi 33 | lo_rsi := plo ? rsi[len_low] : lo_rsi[1] 34 | 35 | //HH:高値切り上げ 36 | //LH:高値切り下げ 37 | //HL:安値切り上げ 38 | //LL:安値切り下げ 39 | HH = phi ? high[len_high] > hi[1] : false 40 | LH = phi ? hi[1] > high[len_high] : false 41 | HL = plo ? low[len_low] > lo[1] : false 42 | LL = plo ? lo[1] > low[len_low] : false 43 | 44 | //ダイバージェンス判定 45 | div1 = HH ? hi_rsi[1] > rsi[len_high] : false 46 | div2 = LH ? rsi[len_high] > hi_rsi[1] : false 47 | div3 = HL ? lo_rsi[1] > rsi[len_low] : false 48 | div4 = LL ? rsi[len_low] > lo_rsi[1] : false 49 | 50 | //チャートに表示 51 | plot(rsi,color=black,linewidth=2) 52 | plot(hi_rsi,color=blue,offset=-len_high) 53 | plot(lo_rsi,color=blue,offset=-len_low) 54 | plotshape(div1,text="chart HH\nrsi LH",location=location.bottom,offset=-len_high) 55 | plotshape(div2,text="chart LH\nrsi HH",location=location.bottom,offset=-len_high) 56 | plotshape(div3,text="chart HL\nrsi LL",location=location.bottom,offset=-len_low) 57 | plotshape(div4,text="chart LL\nrsi HL",location=location.bottom,offset=-len_low) 58 | -------------------------------------------------------------------------------- /pine/sample/dotenkun.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | strategy("DOTENKUN (UKI Ver.)", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=100, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0.0675) 3 | 4 | length = input(title="Length", type=integer, minval=1, maxval=1000, defval=5) 5 | k = input(title="K", type=float, minval=1, maxval=1000, defval=1.6, step=0.1) 6 | 7 | range = 0.0 8 | for i = 1 to length 9 | range := range + tr[i] 10 | range := range / length * k 11 | 12 | if (not na(close[length])) 13 | if (close > open and tr > range) 14 | strategy.entry("Long", strategy.long, comment="Long") 15 | if (open > close and tr > range) 16 | strategy.entry("Short", strategy.short, comment="Short") 17 | 18 | upBound = open + range 19 | downBound = open - range 20 | plot(upBound, title="upBound", color=green, linewidth=2, style=line) 21 | plot(downBound, title="downBound", color=red, linewidth=2, style=line) 22 | -------------------------------------------------------------------------------- /pine/sample/elize-moef.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //XBTUSD 3 | //2H 4 | strategy("elize-MOEF",overlay=true,commission_value=0.075) 5 | 6 | len = input(10) 7 | 8 | //MP = (高値 + 安値) ÷ 2 9 | //MPdiff = 2 × MP - 前日MP 10 | //MOEF = 0.13785 × MPdiff + 0.0007 × 前日MPdiff + 0.13785 × 2日前MPdiff + 1.2103 × 前日MOEF - 0.4867 × 2日前MOEF 11 | 12 | MOEF = close 13 | MP = hl2 14 | MPdiff = 2 * MP - MP[1] 15 | //MOEF := 0.13785 * MPdiff + 0.0007 * MPdiff[1] + 0.13785 * MPdiff[2] + 1.2103 * nz(MOEF[1]) - 0.4867 * nz(MOEF[2]) 16 | MOEF := 0.13785 * MPdiff + 0.0007 * MPdiff[1] + 0.13785 * MPdiff[2] + 1.2103 * nz(MOEF[1]) - 0.4867 * nz(MOEF[2]) 17 | plot(MOEF,color=black,linewidth=2) 18 | 19 | ema = ema(MOEF,len) 20 | sma = sma(MOEF,len) 21 | 22 | plot(ema, color=red) 23 | plot(sma, color=blue) 24 | 25 | buy = crossover(ema,sma) 26 | sell = crossunder(ema,sma) 27 | 28 | strategy.entry("buy",strategy.long,when=buy) 29 | strategy.entry("sell",strategy.short,when=sell) 30 | -------------------------------------------------------------------------------- /pine/sample/elize.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //XBTUSD 3 | //30m 4 | //名前、表示場所、手数料を決める 5 | strategy(title = "elize", shorttitle = "elize", overlay = true, commission_value=0.065) 6 | 7 | //入力設定 8 | len = input(54,title="期間") 9 | 10 | //EMA、SMAの計算 11 | ma1 = ema(close,len) 12 | ma2 = sma(close,len) 13 | 14 | //買い条件、売り条件 15 | buy = crossover(ma1,ma2) 16 | sel = crossunder(ma1,ma2) 17 | 18 | //買い注文、売り注文 19 | strategy.entry("BUY", strategy.long , when=buy) 20 | strategy.entry("SELL", strategy.short, when=sel) 21 | 22 | //インジをチャート上に表示 23 | plot(ma1,color=blue,linewidth=2) 24 | plot(ma2,color=red,linewidth=2) 25 | -------------------------------------------------------------------------------- /pine/sample/elliott.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | study(title = "Elliott Wave Counter[MASK MAN]",shorttitle = "EW cnt", overlay=true) 3 | len = input(title="Length" ,defval=10) 4 | is4 = input(title="EW4>EW1" ,defval=true) 5 | plot_zig = input(title="plot zigzag" ,defval=true) 6 | age = input(title="up:check , down:uncheck",defval=true) 7 | 8 | hi_ss = age ? high : -high 9 | lo_ss = age ? low : -low 10 | phi=pivothigh(hi_ss,len,len) 11 | plo=pivotlow(lo_ss ,len,len) 12 | 13 | state = 0 14 | state := phi ? 1 : plo ? 0 : state[1] 15 | hi = state == 1 16 | lo = state == 0 17 | hi_update = lo[1] and phi 18 | lo_update = hi[1] and plo 19 | 20 | hi0 = close 21 | hi1 = close 22 | lo0 = close 23 | lo1 = close 24 | hi0 := phi ? hi_ss[len] : hi0[1] 25 | hi1 := hi_update ? hi0[1] : hi1[1] 26 | lo0 := plo ? lo_ss[len] : lo0[1] 27 | lo1 := lo_update ? lo0[1] : lo1[1] 28 | 29 | EW = 0 30 | if (EW[1] == 0) 31 | if hi_update 32 | EW := (hi0 > lo0) ? 1 : 0 33 | else 34 | EW := 0 35 | else 36 | if (EW[1] == 1) 37 | if lo_update 38 | EW := (hi0 > lo0 and lo0 > lo1) ? 2 : 0 39 | else 40 | EW := 1 41 | else 42 | if (EW[1] == 2) 43 | if hi_update 44 | EW := (hi0 > lo0 and hi0 > hi1) ? 3 : 1 45 | else 46 | EW := 2 47 | else 48 | if (EW[1] == 3) 49 | ew4over1 = is4 ? lo0 > hi1 : true 50 | if lo_update 51 | EW := (hi0 > lo0 and lo0 > lo1 and ew4over1) ? 4 : 0 52 | else 53 | EW := 3 54 | else 55 | if (EW[1] == 4) 56 | if hi_update 57 | EW := (hi0 > lo0 and hi0 > hi1) ? 5 : 1 58 | else 59 | EW := 4 60 | else 61 | if (EW[1] == 5) 62 | if lo_update 63 | EW := (hi0 > lo0 and hi0 > hi1) ? 6 : 0 64 | else 65 | EW := 5 66 | else 67 | 68 | if (EW[1] == 6) 69 | if hi_update 70 | EW := (hi0 > lo0 and hi1 > hi0) ? 7 : 1 71 | else 72 | EW := 6 73 | else 74 | if (EW[1] == 7) 75 | if lo_update 76 | EW := (hi0 > lo0 and lo1 > lo0) ? 8 : 0 77 | else 78 | EW := 7 79 | else 80 | if (EW[1] == 8) 81 | if hi_update 82 | EW := (hi0 > lo0) ? 1 : 8 83 | else 84 | EW := 8 85 | 86 | EW0 = plo ? EW == 0 : na 87 | EW1 = phi ? EW == 1 : na 88 | EW2 = plo ? EW == 2 : na 89 | EW3 = phi ? EW == 3 : na 90 | EW4 = plo ? EW == 4 : na 91 | EW5 = phi ? EW == 5 : na 92 | EWa = plo ? EW == 6 : na 93 | EWb = phi ? EW == 7 : na 94 | EWc = plo ? EW == 8 : na 95 | 96 | plotshape(EW0?true:na,style=shape.triangledown,text="0",color=black,location=location.belowbar,offset=-len) 97 | plotshape(EW1?true:na,style=shape.triangledown,text="1",color=black,location=location.abovebar,offset=-len) 98 | plotshape(EW2?true:na,style=shape.triangledown,text="2",color=black,location=location.belowbar,offset=-len) 99 | plotshape(EW3?true:na,style=shape.triangledown,text="3",color=black,location=location.abovebar,offset=-len) 100 | plotshape(EW4?true:na,style=shape.triangledown,text="4",color=black,location=location.belowbar,offset=-len) 101 | plotshape(EW5?true:na,style=shape.triangledown,text="5",color=black,location=location.abovebar,offset=-len) 102 | plotshape(EWa?true:na,style=shape.triangledown,text="A",color=black,location=location.belowbar,offset=-len) 103 | plotshape(EWb?true:na,style=shape.triangledown,text="B",color=black,location=location.abovebar,offset=-len) 104 | plotshape(EWc?true:na,style=shape.triangledown,text="C",color=black,location=location.belowbar,offset=-len) 105 | 106 | zigzag = plot_zig ? phi ? high[len] : plo ? low[len] : na : na 107 | plot(zigzag,color=black,offset=-len,linewidth=2) 108 | -------------------------------------------------------------------------------- /pine/sample/highlow_linreg.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //XBTUSD 3 | //30 4 | strategy("High_Low Linreg", overlay=true, default_qty_type = strategy.fixed, initial_capital=10000, currency="USD", default_qty_value = 1,slippage=1,commission_type=strategy.commission.percent,commission_value=0.065) 5 | 6 | //テスト期間 7 | syear=input(1900,title="開始(年)") 8 | smonth=input(01,title="開始(月)") 9 | sday=input(01,title="開始(日)") 10 | shour=input(00,title="開始(時)") 11 | sminute=input(00,title="開始(分)") 12 | eyear=input(2100,title="終了(年)") 13 | emonth=input(12,title="終了(月)") 14 | eday=input(31,title="終了(日)") 15 | ehour=input(00,title="終了(時)") 16 | eminute=input(00,title="終了(分)") 17 | timescale = timestamp(eyear, emonth, eday, ehour, eminute) > time and time > timestamp(syear, smonth, sday, shour, sminute) 18 | 19 | //------------------------------------ 20 | 21 | //MA 入力 22 | ma_type = input(defval = "linreg", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "MA Type") 23 | ma_src = input(ohlc4, "MA Source") 24 | ma_len = input(55, "MA Length") 25 | ma_offset = input(0, "MA Offset") 26 | 27 | //MA Type 関数宣言 28 | ma_tf(ma_type, src, len, offset) => 29 | ma_type_fun = ma_type == "ema" ? ema(src, len) : 30 | ma_type == "rma" ? rma(src, len) : 31 | ma_type == "swma" ? swma(src) : 32 | ma_type == "vwma" ? vwma(src, len) : 33 | ma_type == "wma" ? wma(src, len) : 34 | ma_type == "linreg" ? linreg(src, len, offset) : 35 | sma(src, len) 36 | ma_type_fun 37 | 38 | //MAの計算 39 | ma = ma_tf(ma_type, ma_src, ma_len, ma_offset) 40 | 41 | //MAを描画 42 | plot(ma, color=red, title="MA") 43 | 44 | //------------------------------------ 45 | 46 | //High-Low Band 入力 47 | hl_len = input(15, "High-Low Band Length") 48 | hl_offset = input(30, "High-Low Band Offset") 49 | use_oc = input(true, "High-Low Bandに始値・終値を使用") 50 | 51 | //High-Low Band 計算 52 | h_band = use_oc ? highest(max(open, close)[hl_offset], hl_len) : highest(high[hl_offset], hl_len) 53 | l_band = use_oc ? lowest(min(open, close)[hl_offset], hl_len) : lowest(low[hl_offset], hl_len) 54 | m_band = (h_band + l_band) / 2 55 | 56 | //High-Low MA 入力 57 | hl_ma_type = input(defval = "linreg", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "High-Low MA Type") 58 | hl_ma_len = input(33, "High-Low MA Length") 59 | hl_ma_offset = input(0, "High-Low MA Offset") 60 | 61 | //High-Low MA 計算 62 | h_ma = ma_tf(hl_ma_type, h_band, hl_ma_len, hl_ma_offset) 63 | m_ma = ma_tf(hl_ma_type, m_band, hl_ma_len, hl_ma_offset) 64 | l_ma = ma_tf(hl_ma_type, l_band, hl_ma_len, hl_ma_offset) 65 | 66 | //High-Low MA 描画 67 | //plot(h_band, color=blue, title="High Band") 68 | //plot(m_band, color=aqua, title="Middle Band") 69 | //plot(l_band, color=yellow, title="Low Band") 70 | 71 | plot(h_ma, color=blue, title="High Band MA") 72 | plot(m_ma, color=aqua, title="Middle Band MA") 73 | plot(l_ma, color=yellow, title="Low Band MA") 74 | 75 | //------------------------------------ 76 | 77 | //売買条件 入力 78 | buy_on = input(true, "買いエントリー有り") 79 | buy_exit_on = input(true, "買い決済有り") 80 | sell_on = input(true, "売りエントリー有り") 81 | sell_exit_on = input(true, "売り決済有り") 82 | 83 | b_entry1 = input(defval = "High Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Buy Entry Line 1") 84 | b_entry2 = input(defval = "Low Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Buy Entry Line 2") 85 | 86 | b_exit = input(defval = "Middle Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Buy Exit Line") 87 | 88 | s_entry1 = input(defval = "Low Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Sell Entry Line 1") 89 | s_entry2 = input(defval = "High Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Sell Entry Line 2") 90 | 91 | s_exit = input(defval = "Middle Band MA", options=["High Band MA", "Middle Band MA", "Low Band MA"], title = "Sell Exit Line") 92 | 93 | //売買判定 94 | src1 = b_entry1 == "High Band MA" ? h_ma : 95 | b_entry1 == "Middle Band MA" ? m_ma : 96 | l_ma 97 | src2 = b_entry2 == "High Band MA" ? h_ma : 98 | b_entry2 == "Middle Band MA" ? m_ma : 99 | l_ma 100 | src3 = b_exit == "High Band MA" ? h_ma : 101 | b_exit == "Middle Band MA" ? m_ma : 102 | l_ma 103 | src4 = s_entry1 == "High Band MA" ? h_ma : 104 | s_entry1 == "Middle Band MA" ? m_ma : 105 | l_ma 106 | src5 = s_entry2 == "High Band MA" ? h_ma : 107 | s_entry2 == "Middle Band MA" ? m_ma : 108 | l_ma 109 | src6 = s_exit == "High Band MA" ? h_ma : 110 | s_exit == "Middle Band MA" ? m_ma : 111 | l_ma 112 | 113 | buy1 = crossover(ma, src1) and buy_on 114 | buy2 = crossover(ma, src2) and buy_on 115 | buy = buy1 or buy2 116 | 117 | buy_exit = crossunder(ma, src3) and buy_exit_on 118 | 119 | sell1 = crossunder(ma, src4) and sell_on 120 | sell2 = crossunder(ma, src5) and sell_on 121 | sell = sell1 or sell2 122 | 123 | sell_exit = crossover(ma, src6) and sell_exit_on 124 | 125 | if timescale 126 | strategy.entry("buy",true,when=buy) 127 | strategy.entry("sell",false,when=sell) 128 | if not sell 129 | strategy.close("buy",when=buy_exit) 130 | if not buy 131 | strategy.close("sell",when=sell_exit) 132 | -------------------------------------------------------------------------------- /pine/sample/input.pine: -------------------------------------------------------------------------------- 1 | study("input test") 2 | 3 | k1 = 1000 4 | input(title="Length", type=integer, minval=1, maxval=k1, defval=5) 5 | input(title="K", type=float, minval=1, maxval=1000, defval=1.6, step=0.1) 6 | 7 | a = 0.0 8 | k2 = 10 9 | for i = 0 to 10 10 | a := a[1] + i 11 | input(title="mutable", defval=a) 12 | 13 | x = if a > 0.0 14 | 1 15 | else 16 | 0 17 | input(title="ifval", defval=x) 18 | -------------------------------------------------------------------------------- /pine/sample/ma.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | study("MA Type Function(Sample 2)",overlay=true) 3 | 4 | //MA 入力 5 | ma_type_s = input(defval = "ema", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "短期MA Type") 6 | src_s = input(close, "短期MA Source") 7 | len_s = input(5, "短期MA Length") 8 | 9 | ma_type_m = input(defval = "ema", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "中期MA Type") 10 | src_m = input(close, "中期MA Source") 11 | len_m = input(20, "中期MA Length") 12 | 13 | ma_type_l = input(defval = "ema", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "長期MA Type") 14 | src_l = input(close, "長期MA Source") 15 | len_l = input(40, "長期MA Length") 16 | 17 | //MA Type 関数宣言 18 | ma_tf(ma_type, src, len, offset) => 19 | ma_type_fun = ma_type == "ema" ? ema(src, len) : 20 | ma_type == "rma" ? rma(src, len) : 21 | ma_type == "swma" ? swma(src) : 22 | ma_type == "vwma" ? vwma(src, len) : 23 | ma_type == "wma" ? wma(src, len) : 24 | ma_type == "linreg" ? linreg(src, len, offset) : 25 | sma(src, len) 26 | ma_type_fun 27 | 28 | //MAの計算(offsetはlinreg用だが入力は必要) 29 | s_ma = ma_tf(ma_type_s, src_s, len_s, 0) 30 | m_ma = ma_tf(ma_type_m, src_m, len_m, 0) 31 | l_ma = ma_tf(ma_type_l, src_l, len_l, 0) 32 | 33 | //MAを描画 34 | plot(s_ma, color=red, title="短期MA") 35 | plot(m_ma, color=green, title="中期MA") 36 | plot(l_ma, color=blue, title="長期MA") 37 | -------------------------------------------------------------------------------- /pine/sample/moef.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //XBTUSD 3 | //2H 4 | strategy("MOEF strategy",overlay=true,commission_value=0.075) 5 | 6 | param = input(7) 7 | 8 | //MP = (高値 + 安値) ÷ 2 9 | //MPdiff = 2 × MP - 前日MP 10 | //MOEF = 0.13785 × MPdiff + 0.0007 × 前日MPdiff + 0.13785 × 2日前MPdiff + 1.2103 × 前日MOEF - 0.4867 × 2日前MOEF 11 | 12 | MOEF = close 13 | MP = hl2 14 | MPdiff = 2 * MP - MP[1] 15 | //MOEF := 0.13785 * MPdiff + 0.0007 * MPdiff[1] + 0.13785 * MPdiff[2] + 1.2103 * nz(MOEF[1]) - 0.4867 * nz(MOEF[2]) 16 | MOEF := 0.13785 * MPdiff + 0.0007 * MPdiff[1] + 0.13785 * MPdiff[2] + 1.2103 * nz(MOEF[1]) - 0.4867 * nz(MOEF[2]) 17 | plot(MOEF,color=black,linewidth=2) 18 | 19 | buy = crossover(MOEF,MOEF[param]) 20 | sell = crossunder(MOEF,MOEF[param]) 21 | 22 | strategy.entry("buy",strategy.long,when=buy) 23 | strategy.entry("sell",strategy.short,when=sell) 24 | -------------------------------------------------------------------------------- /pine/sample/mtf_stochrsi.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | study(title="yutas_StochRSI", shorttitle="Y_StochRSI") 3 | source = close 4 | 5 | //----------------------------- 6 | // StochRSI用の設定 7 | //----------------------------- 8 | useCurrentRes_str = input(true, title="現在のチャートのタイムフレームを使用(STR)") 9 | resCustom_str = input(title="異なるタイムフレームを使用。 上のチェックボックスをオフにします", type=resolution, defval="1") 10 | smoothK = input(3,minval=1, title="%K") 11 | smoothD = input(3,minval=1, title="日") 12 | lengthRSI = input(14, minval=1) 13 | lengthStoch = input(14, minval=1, title="ストキャス期間") 14 | strss = input(true, title="・strが交差する時にサインを表示") 15 | os = input(25,minval=1, title="上の帯") 16 | ob = input(75,minval=1, title="下の帯") 17 | rsi1 = rsi(source, lengthRSI) 18 | 19 | //-------------------------時間足の判別-------------------------// 20 | res_str = useCurrentRes_str ? period : resCustom_str 21 | //----------------------------ここまで----------------------------// 22 | 23 | //----------------------------- 24 | // StochRSIの処理 25 | //----------------------------- 26 | k = sma(stoch(rsi1, rsi1, rsi1, lengthStoch), smoothK) 27 | d = sma(k, smoothD) 28 | 29 | // 設定した時間足に分解 30 | outk = security(tickerid, res_str, k) 31 | outd = security(tickerid, res_str, d) 32 | 33 | h0 = hline(os) 34 | h1 = hline(ob) 35 | //----------------------------- 36 | // 指標の表示(Stochastic) 37 | //----------------------------- 38 | 39 | hline(os,color=silver, title="上の帯") 40 | hline(ob,color=silver, title="下の帯") 41 | 42 | plot(outk, color=blue,title="k") 43 | plot(outd, color=red,title="日",linewidth=1) 44 | 45 | STRcircleYPosition = outk 46 | plot(strss and cross(outk, outd) and outk >= outd and outk ? STRcircleYPosition : na, title="STR GC", style=circles, linewidth=5, color=blue) 47 | plot(strss and cross(outk, outd) and outk < outd and outk ? STRcircleYPosition : na, title="STR DC", style=circles, linewidth=5, color=red) 48 | 49 | fill(h0, h1, color=#9517F2, transp=80, title='背景') 50 | -------------------------------------------------------------------------------- /pine/sample/offset.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | study(title="MACD(オフセット戦略_改)", overlay=false) 3 | 4 | // Getting inputs 5 | len = input(38) 6 | ss = input(ohlc4) 7 | delay = input(21) 8 | signal_length = input(title="Signal Smoothing", type=integer, minval = 1, maxval = 50, defval = 28) 9 | hist_vol = input(45) 10 | 11 | // Plot colors 12 | col_grow_above = "aa" 13 | col_grow_below = #FFCDD2 14 | col_fall_above = #B2DFDB 15 | col_fall_below = #EF5350 16 | col_macd = #0094ff 17 | col_signal = #ff6a00 18 | 19 | // Calculating 20 | ema = ema(ss,len) 21 | ema_delay = ema[delay] 22 | macd = ema - ema_delay 23 | signal = sma(macd, signal_length) 24 | hist = macd - signal 25 | 26 | plot(hist, title="Histogram", style=columns, color=(hist>=0 ? (hist[1] < hist ? col_grow_above : col_fall_above) : (hist[1] < hist ? col_grow_below : col_fall_below) ), transp=0 ) 27 | plot(macd, title="MACD", color=col_macd, transp=0) 28 | plot(signal, title="Signal", color=col_signal, transp=0) 29 | hline(hist_vol, color=red, linestyle=dashed) 30 | hline(hist_vol*-1, color=red, linestyle=dashed) 31 | -------------------------------------------------------------------------------- /pine/sample/offset_kai.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //XBBTUSD 3 | //4H 4 | strategy("オフセット戦略_改",overlay=true,commission_value=0.075) 5 | //strategy("オフセット戦略_改(成績)",overlay=false,commission_value=0.075) 6 | 7 | //入力設定 8 | len = input(38) 9 | ss = input(ohlc4) 10 | delay = input(21) 11 | signal_length = input(28) 12 | hist_vol = input(45) 13 | 14 | //emaを計算 15 | ema = ema(ss,len) 16 | 17 | //過去のema 18 | ema_delay = ema[delay] 19 | 20 | //emaと過去のemaのmacd 21 | macd = ema - ema_delay 22 | signal = sma(macd, signal_length) 23 | hist = macd - signal 24 | 25 | //売買条件 26 | buy = crossover(ema,ema_delay) and abs(hist) > abs(hist_vol) or macd > 0 and close > open and abs(hist) > abs(hist_vol) 27 | sell = crossunder(ema,ema_delay) and abs(hist) > abs(hist_vol) or macd < 0 and close < open and abs(hist) > abs(hist_vol) 28 | ex_b = macd < 0 and close < open and abs(hist) > abs(hist_vol) 29 | ex_s = macd > 0 and close > open and abs(hist) > abs(hist_vol) 30 | 31 | //買い注文、売り注文 32 | strategy.entry("buy",true,when=buy) 33 | strategy.entry("sell",false,when=sell) 34 | strategy.close("buy",when=ex_b or crossunder(ema,ema_delay)) 35 | strategy.close("sell",when=ex_s or crossover(ema,ema_delay)) 36 | -------------------------------------------------------------------------------- /pine/sample/offsetenv.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | strategy("Offset MA & Envelope",overlay=true, default_qty_type = strategy.fixed, initial_capital=10000, currency="USD", default_qty_value = 1,slippage=1,commission_type=strategy.commission.percent,commission_value=0.065) 3 | 4 | //テスト期間 5 | syear=input(1900,title="開始(年)") 6 | smonth=input(01,title="開始(月)") 7 | sday=input(01,title="開始(日)") 8 | shour=input(00,title="開始(時)") 9 | sminute=input(00,title="開始(分)") 10 | eyear=input(2100,title="終了(年)") 11 | emonth=input(12,title="終了(月)") 12 | eday=input(31,title="終了(日)") 13 | ehour=input(00,title="終了(時)") 14 | eminute=input(00,title="終了(分)") 15 | timescale = timestamp(eyear, emonth, eday, ehour, eminute) > time and time > timestamp(syear, smonth, sday, shour, sminute) 16 | 17 | 18 | //MA 入力 19 | ma_type1 = input(defval = "linreg", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "短期MA Type") 20 | src1 = input(ohlc4, "MA1 Source") 21 | len1 = input(48, "MA1 Length") 22 | 23 | ma_type2 = input(defval = "linreg", options = ["ema","sma","rma","swma","vwma","wma","linreg"], title = "中期MA Type") 24 | src2 = input(ohlc4, "MA2 Source") 25 | len2 = input(50, "MA2 Length") 26 | offset2 = input(28, "MA2 Offset") 27 | ep = input(2.0, "Envelope Percent", step=0.1) 28 | 29 | //MA Type 関数宣言 30 | ma_tf(ma_type, src, len, offset) => 31 | ma_type_fun = ma_type == "ema" ? ema(src, len) : 32 | ma_type == "rma" ? rma(src, len) : 33 | ma_type == "swma" ? swma(src) : 34 | ma_type == "vwma" ? vwma(src, len) : 35 | ma_type == "wma" ? wma(src, len) : 36 | ma_type == "linreg" ? linreg(src, len, offset) : 37 | sma(src, len) 38 | ma_type_fun 39 | 40 | //MAの計算 41 | ma1 = ma_tf(ma_type1, src1, len1, 0) 42 | ma2 = offset(ma_tf(ma_type2, src2, len2, 0), offset2) 43 | 44 | //Envelopeの計算 45 | upper = ma2 * (1 + (ep / 100)) 46 | lower = ma2 * (1 - (ep / 100)) 47 | 48 | 49 | //売買条件 50 | buy_on = input(true, "買いエントリー有り") 51 | buy_exit_on = input(true, "買い決済有り") 52 | sell_on = input(true, "売りエントリー有り") 53 | sell_exit_on = input(true, "売り決済有り") 54 | 55 | buy1 = crossover(ma1, lower) and buy_on 56 | buy2 = crossover(ma1, upper) and buy_on 57 | buy = buy1 or buy2 58 | 59 | buy_exit = crossunder(ma1, ma2) and buy_exit_on 60 | 61 | sell1 = crossunder(ma1, upper) and sell_on 62 | sell2 = crossunder(ma1, lower) and sell_on 63 | sell = sell1 or sell2 64 | 65 | sell_exit = crossover(ma1, ma2) and sell_exit_on 66 | 67 | if timescale 68 | strategy.entry("buy",true,when=buy) 69 | strategy.entry("sell",false,when=sell) 70 | if not sell 71 | strategy.close("buy",when=buy_exit) 72 | if not buy 73 | strategy.close("sell",when=sell_exit) 74 | 75 | 76 | //MAを描画 77 | plot(ma1, color=red, title="MA1") 78 | plot(ma2, color=aqua, title="MA2") 79 | plot(upper, color=blue, title="Envelope +%") 80 | plot(lower, color=blue, title="Envelope -%") 81 | -------------------------------------------------------------------------------- /pine/sample/piano_delta.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //30m 3 | strategy(title = "piano_delta", shorttitle = "piano_delta", overlay = true, default_qty_type = strategy.fixed, initial_capital=10000, currency="USD", 4 | default_qty_value = 1,slippage=1,commission_type=strategy.commission.percent,commission_value=0.065) 5 | timescale = (time > timestamp(2017, 01, 01, 00, 00) and time < timestamp(2100, 12, 31, 00, 00)) 6 | 7 | // MA 8 | len=input(defval=49,title="len",type=integer) 9 | param=input(defval=2,title="param",type=integer) 10 | ma1 = ema(close,len) 11 | ma2 = sma(close,len+param) 12 | 13 | // DELTA MA 14 | len_s = input(defval=47, minval=1, title="Dma Length") 15 | lrc = linreg(close, len_s, 0) 16 | lrc1 = linreg(close, len_s, 1) 17 | Dma = (lrc-lrc1) 18 | 19 | // FAKE CROSS JUDGE 20 | Dmaecf= input(defval=12,title="Dma_entry_cutoff +/-",type=integer) 21 | Dmaccf= input(defval=12,title="Dma_close_cutoff +/-",type=integer) 22 | DmaLC= input(defval=23,title="Dma_losscut +/-",type=integer) 23 | nFAKEb= Dma > -Dmaecf 24 | nFAKEs = Dma < Dmaecf 25 | nFAKEsc= Dma > -Dmaccf 26 | nFAKEbc = Dma < Dmaccf 27 | 28 | // LOGIC 29 | buy = crossover(ma1,ma2) and nFAKEb 30 | sel = crossunder(ma1,ma2) and nFAKEs 31 | exit_buy = crossunder(ma1,ma2) and nFAKEbc 32 | exit_sel = crossover(ma1,ma2) and nFAKEsc 33 | LCbuy = crossunder(Dma,-DmaLC) 34 | LCsel = crossunder(Dma,DmaLC) 35 | strategy.entry("BUY", strategy.long , when= buy) 36 | strategy.entry("SELL", strategy.short, when= sel) 37 | strategy.close("BUY",when= exit_buy) 38 | strategy.close("SELL",when= exit_sel) 39 | strategy.close("BUY",when= LCbuy) 40 | strategy.close("SELL",when= LCsel) 41 | 42 | plot(ma1,color=blue,linewidth=1) 43 | plot(ma2,color=red,linewidth=1) 44 | -------------------------------------------------------------------------------- /pine/sample/ppo_bullbear.pine: -------------------------------------------------------------------------------- 1 | //@version=2 2 | //Credit to https://www.tradingview.com/script/p3oqCa56-Pekipek-s-PPO-Divergence-BETA/ (I just changed the visuals a bit) 3 | //A simple strategy that uses the divergences to open trades and the highs/lows to close them. Would love to see any variations! - @scarf 4 | //FYI: I have alerts set up for the purple and orange circles on daily forex charts amd I get a lot of excellent trade entries. 5 | strategy("PPO Bull/Bear Divergence to High/Low Trader", overlay=false) 6 | 7 | source = open 8 | long_term_div = input(true, title="Use long term Divergences?") 9 | div_lookback_period = input(55, minval=1, title="Lookback Period") 10 | fastLength = input(12, minval=1), slowLength=input(26,minval=1) 11 | signalLength=input(9,minval=1) 12 | smoother = input(2,minval=1) 13 | fastMA = ema(source, fastLength) 14 | slowMA = ema(source, slowLength) 15 | macd = fastMA - slowMA 16 | macd2=(macd/slowMA)*100 17 | d = sma(macd2, smoother) // smoothing PPO 18 | 19 | bullishPrice = low 20 | 21 | priceMins = bullishPrice > bullishPrice[1] and bullishPrice[1] < bullishPrice[2] or low[1] == low[2] and low[1] < low and low[1] < low[3] or low[1] == low[2] and low[1] == low[3] and low[1] < low and low[1] < low[4] or low[1] == low[2] and low[1] == low[3] and low[1] and low[1] == low[4] and low[1] < low and low[1] < low[5] // this line identifies bottoms and plateaus in the price 22 | oscMins= d > d[1] and d[1] < d[2] // this line identifies bottoms in the PPO 23 | 24 | BottomPointsInPPO = oscMins 25 | 26 | bearishPrice = high 27 | priceMax = bearishPrice < bearishPrice[1] and bearishPrice[1] > bearishPrice[2] or high[1] == high[2] and high[1] > high and high[1] > high[3] or high[1] == high[2] and high[1] == high[3] and high[1] > high and high[1] > high[4] or high[1] == high[2] and high[1] == high[3] and high[1] and high[1] == high[4] and high[1] > high and high[1] > high[5] // this line identifies tops in the price 28 | oscMax = d < d[1] and d[1] > d[2] // this line identifies tops in the PPO 29 | 30 | TopPointsInPPO = oscMax 31 | 32 | currenttrough4=valuewhen (oscMins, d[1], 0) // identifies the value of PPO at the most recent BOTTOM in the PPO 33 | lasttrough4=valuewhen (oscMins, d[1], 1) // NOT USED identifies the value of PPO at the second most recent BOTTOM in the PPO 34 | currenttrough5=valuewhen (oscMax, d[1], 0) // identifies the value of PPO at the most recent TOP in the PPO 35 | lasttrough5=valuewhen (oscMax, d[1], 1) // NOT USED identifies the value of PPO at the second most recent TOP in the PPO 36 | 37 | currenttrough6=valuewhen (priceMins, low[1], 0) // this line identifies the low (price) at the most recent bottom in the Price 38 | lasttrough6=valuewhen (priceMins, low[1], 1) // NOT USED this line identifies the low (price) at the second most recent bottom in the Price 39 | currenttrough7=valuewhen (priceMax, high[1], 0) // this line identifies the high (price) at the most recent top in the Price 40 | lasttrough7=valuewhen (priceMax, high[1], 1) // NOT USED this line identifies the high (price) at the second most recent top in the Price 41 | 42 | delayedlow = priceMins and barssince(oscMins) < 3 ? low[1] : na 43 | delayedhigh = priceMax and barssince(oscMax) < 3 ? high[1] : na 44 | 45 | // only take tops/bottoms in price when tops/bottoms are less than 5 bars away 46 | filter = barssince(priceMins) < 5 ? lowest(currenttrough6, 4) : na 47 | filter2 = barssince(priceMax) < 5 ? highest(currenttrough7, 4) : na 48 | 49 | //delayedbottom/top when oscillator bottom/top is earlier than price bottom/top 50 | y11 = valuewhen(oscMins, delayedlow, 0) 51 | y12 = valuewhen(oscMax, delayedhigh, 0) 52 | 53 | // only take tops/bottoms in price when tops/bottoms are less than 5 bars away, since 2nd most recent top/bottom in osc 54 | y2=valuewhen(oscMax, filter2, 1) // identifies the highest high in the tops of price with 5 bar lookback period SINCE the SECOND most recent top in PPO 55 | y6=valuewhen(oscMins, filter, 1) // identifies the lowest low in the bottoms of price with 5 bar lookback period SINCE the SECOND most recent bottom in PPO 56 | 57 | long_term_bull_filt = valuewhen(priceMins, lowest(div_lookback_period), 1) 58 | long_term_bear_filt = valuewhen(priceMax, highest(div_lookback_period), 1) 59 | 60 | y3=valuewhen(oscMax, currenttrough5, 0) // identifies the value of PPO in the most recent top of PPO 61 | y4=valuewhen(oscMax, currenttrough5, 1) // identifies the value of PPO in the second most recent top of PPO 62 | 63 | y7=valuewhen(oscMins, currenttrough4, 0) // identifies the value of PPO in the most recent bottom of PPO 64 | y8=valuewhen(oscMins, currenttrough4, 1) // identifies the value of PPO in the SECOND most recent bottom of PPO 65 | 66 | y9=valuewhen(oscMins, currenttrough6, 0) 67 | y10=valuewhen(oscMax, currenttrough7, 0) 68 | 69 | bulldiv= BottomPointsInPPO ? d[1] : na // plots dots at bottoms in the PPO 70 | beardiv= TopPointsInPPO ? d[1]: na // plots dots at tops in the PPO 71 | 72 | 73 | i = currenttrough5 < highest(d, div_lookback_period) // long term bearish oscilator divergence 74 | i2 = y10 > long_term_bear_filt // long term bearish top divergence 75 | i3 = delayedhigh > long_term_bear_filt // long term bearish delayedhigh divergence 76 | 77 | i4 = currenttrough4 > lowest(d, div_lookback_period) // long term bullish osc divergence 78 | i5 = y9 < long_term_bull_filt // long term bullish bottom div 79 | i6 = delayedlow < long_term_bull_filt // long term bullish delayedbottom div 80 | 81 | 82 | plot(0, color=gray) 83 | plot(d, color=black) 84 | plot(bulldiv, title = "Bottoms", color=maroon, style=circles, linewidth=3, offset= -1) 85 | plot(beardiv, title = "Tops", color=green, style=circles, linewidth=3, offset= -1) 86 | 87 | bearishdiv1 = (y10 > y2 and oscMax and y3 < y4) ? true : false 88 | bearishdiv2 = (delayedhigh > y2 and y3 < y4) ? true : false 89 | bearishdiv3 = (long_term_div and oscMax and i and i2) ? true : false 90 | bearishdiv4 = (long_term_div and i and i3) ? true : false 91 | 92 | bullishdiv1 = (y9 < y6 and oscMins and y7 > y8) ? true : false 93 | bullishdiv2 = (delayedlow < y6 and y7 > y8) ? true : false 94 | bullishdiv3 = (long_term_div and oscMins and i4 and i5) ? true : false 95 | bullishdiv4 = (long_term_div and i4 and i6) ? true : false 96 | 97 | bearish = bearishdiv1 or bearishdiv2 or bearishdiv3 or bearishdiv4 98 | bullish = bullishdiv1 or bullishdiv2 or bullishdiv3 or bullishdiv4 99 | 100 | //Used for alerts when this is an indicator, not a strategy 101 | //alertcondition( bearishdiv, title="Bearish Div", message="Bearish Div: Short " ) 102 | //alertcondition( bullishdiv, title="Bullish Div", message="Bullish Div: Long " ) 103 | 104 | plot(y10>y2 and oscMax and y3 < y4 ? d :na, title = "Bearish Divergence1", color=orange, style= circles, linewidth=6) 105 | plot(y9 y8 ? d :na, title = "Bullish Divergence1", color=purple, style=circles, linewidth=6) 106 | plot(delayedhigh>y2 and y3 < y4 ? d :na, title = "Bearish Divergence2", color=orange, style= circles, linewidth=6) 107 | plot(delayedlow y8 ? d :na, title = "Bullish Divergence2", color=purple, style=circles, linewidth=6) 108 | 109 | plot(long_term_div and oscMax and i and i2 ? d :na, title = "Bearish Divergence3", color=orange, style= circles, linewidth=6) 110 | plot(long_term_div and oscMins and i4 and i5 ? d : na, title = "Bullish Divergence3", color=purple, style=circles, linewidth=6) 111 | plot(long_term_div and i and i3 ? d :na, title = "Bearish Divergence4", color=orange, style= circles, linewidth=6) 112 | plot(long_term_div and i4 and i6 ? d : na, title = "Bullish Divergence4", color=purple, style=circles, linewidth=6) 113 | 114 | // Enters trade on orange or purple circle 115 | if (bullish) 116 | strategy.entry("Long", strategy.long) 117 | 118 | if (bearish) 119 | strategy.entry("Short", strategy.short) 120 | 121 | // Exit trade on red or green dot 122 | if (beardiv) 123 | strategy.close("Long") 124 | 125 | if (bulldiv) 126 | strategy.close("Short") 127 | 128 | -------------------------------------------------------------------------------- /pine/sample/stoch.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | strategy(title = "ストキャスティクス", overlay = false) 3 | 4 | timescale = (time > timestamp(2017, 1, 1, 00, 00) and time < timestamp(2100, 12, 31, 00, 00)) 5 | 6 | //ストキャスティクス 7 | len_k = input(5,title="%K") 8 | len_d = input(2,title="%D") 9 | th1 = input(20) 10 | th2 = input(80) 11 | k = stoch(close,high,low,len_k) 12 | d = sma(k,len_d) 13 | plot(k,color=blue,title="%K") 14 | plot(d,color=orange,title="%D") 15 | f1 = plot(th1,color=black) 16 | f2 = plot(th2,color=black) 17 | fill(f1,f2,color=purple) 18 | 19 | //Long 20 | longCondition = crossover(k, d) and kth2 24 | 25 | //Trading 26 | if (longCondition) 27 | strategy.entry("BUY", strategy.long, na, when=timescale) 28 | if (shortCondition) 29 | strategy.entry("SELL", strategy.short, na, when=timescale) 30 | 31 | //Close 32 | if strategy.position_size>0 and crossover(k, th2) 33 | strategy.close_all() 34 | if strategy.position_size<0 and crossunder(d, th1) 35 | strategy.close_all() 36 | -------------------------------------------------------------------------------- /pine/sample/stochrsi.pine: -------------------------------------------------------------------------------- 1 | //@version=3 2 | //タイトルを「yutas_StochRSI_normal」とし、インジのサブチャートに表示するタイトルは「Y_StochRSI_normal」とする。 3 | study(title="yutas_StochRSI_normal", shorttitle="Y_StochRSI_normal") 4 | //sourceを終値とする。 5 | source = close 6 | 7 | //----------------------------- 8 | // StochRSI用の設定 9 | //----------------------------- 10 | 11 | smoothK = input(3,minval=1, title="%K") 12 | smoothD = input(3,minval=1, title="日") 13 | //RSIの期間を14で最低数値は1にする。 14 | lengthRSI = input(14, minval=1) 15 | //ストキャスの期間を14で最低数値は1でストキャス期間というタイトルにする。 16 | lengthStoch = input(14, minval=1, title="ストキャス期間") 17 | //クロスサインを出すかどうかのスイッチを作る。プロット関数に(strss)がtrueであれば表示、falseは非表示。設定画面ではチェックを入れるか入れないかで変わる。 18 | strss = input(true, title="・strが交差する時にサインを表示") 19 | //osの数値を標準設定で25で最低数値は1で上の帯というタイトルにする。 20 | os = input(25,minval=1, title="上の帯") 21 | //obの数値を標準設定で75で最低数値は1で下の帯というタイトルにする。 22 | ob = input(75,minval=1, title="下の帯") 23 | //rsi を終値(close)で計算する。source = closeは上で指定済。 24 | rsi1 = rsi(source, lengthRSI) 25 | 26 | //----------------------------- 27 | // StochRSIの処理 28 | //----------------------------- 29 | 30 | //RSI計算式 31 | //https://www.fxbroadnet.com/tech/technicalchart/tech09.jsp を参考 32 | //ストキャスRSI計算式 33 | //https://www.fxbroadnet.com/tech/technicalchart/tech17.jsp を参考 34 | 35 | k = sma(stoch(rsi1, rsi1, rsi1, lengthStoch), smoothK) 36 | d = sma(k, smoothD) 37 | 38 | //fill関数で塗りつぶしをする為に os の数値を h0 と指定しておく 39 | h0 = hline(os) 40 | //fill関数で塗りつぶしをする為に obの数値を h1 と指定しておく 41 | h1 = hline(ob) 42 | 43 | //----------------------------- 44 | // 指標の表示(Stochastic) 45 | //----------------------------- 46 | //一定の固定数値での水平ラインを os の数値で silver 色で 上の帯 というタイトルで表示する 47 | hline(os,color=silver, title="上の帯") 48 | //一定の固定数値での水平ラインを ob の数値で silver 色で 下の帯 というタイトルで表示する 49 | hline(ob,color=silver, title="下の帯") 50 | 51 | //k を blue 色でタイトルは k で ラインの太さを 1 で表示 52 | plot(k, color=blue,title="k") 53 | //d を red 色でタイトルは 日 で ラインの太さを 1 で表示 54 | plot(d, color=red,title="日",linewidth=1) 55 | 56 | //クロスサインを表示する場所を%Kのライン上にプロットする為 57 | STRcircleYPosition = k 58 | 59 | //strss が true の時 and K と d のラインがクロスした時 and K が d 以上で STRcircleYPosition にクロスサインを円形でラインを5の太さでblue色でタイトルはSTR GCとして表示する 60 | plot(strss and cross(k, d) and k >= d and k ? STRcircleYPosition : na, title="STR GC", style=circles, linewidth=5, color=blue) 61 | //strss が true の時 and K と d のラインがクロスした時 and K が d 未満で STRcircleYPosition にクロスサインを円形でラインを5の太さでred色でタイトルはSTR DCとして表示する 62 | plot(strss and cross(k, d) and k < d and k ? STRcircleYPosition : na, title="STR DC", style=circles, linewidth=5, color=red) 63 | 64 | //h0とh1の間を#9517F2(カラーコード)で 80の濃度で背景と言うタイトルで範囲塗りつぶし表示する 65 | fill(h0, h1, color=#9517F2, transp=80, title='背景') 66 | -------------------------------------------------------------------------------- /pine/vm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzh-dev/pine-bot-server/d36107328876adb3cd473587a5a31169fb6a5a98/pine/vm/__init__.py -------------------------------------------------------------------------------- /pine/vm/builtin_function.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import math 4 | import numpy as np 5 | import talib as ta 6 | 7 | from ..base import PineError 8 | from .helper import Series, series_np, NaN, series_immutable, series_mutable 9 | 10 | strategy_functions = {} 11 | strategy_risk_functions = {} 12 | plot_functions = {} 13 | 14 | 15 | class PineArgumentError (PineError): 16 | def __init__ (self, msg): 17 | super().__init__(msg) 18 | 19 | def _expand_args (args, kwargs, specs): 20 | args_dict = {} 21 | if args: 22 | la = len(args) 23 | ls = len(specs) 24 | if la > ls: 25 | raise PineArgumentError("Too many arguments: {0} for {1}".format(la , ls)) 26 | for a, spec in zip(args, specs): 27 | n, _, _ = spec 28 | args_dict[n] = a 29 | if kwargs: 30 | args_dict.update(kwargs) 31 | 32 | args_expanded = [] 33 | # TODO: wrong key check 34 | for name, typ, mand in specs: 35 | a = args_dict.get(name, None) 36 | if a is None: 37 | if mand: 38 | raise PineArgumentError("Missing mandatory arguemnt: {}".format(name)) 39 | else: 40 | if typ == float and type(a) == int: 41 | a = float(a) 42 | elif typ == int and type(a) == float and int(a) == a: 43 | a = int(a) 44 | elif typ == bool and type(a) == str: 45 | if a == 'false': 46 | a = False 47 | elif typ is not None and not isinstance(a, typ): 48 | raise PineArgumentError("Invalid argument type: {0}: {1} for {2}".format( 49 | name, type(a).__name__, typ.__name__)) 50 | args_expanded.append(a) 51 | 52 | return args_expanded 53 | 54 | def _expand_args_as_dict (args, kwargs, specs): 55 | expanded = _expand_args(args, kwargs, specs) 56 | 57 | args_ = {} 58 | for v, spec in zip(expanded, specs): 59 | args_[spec[0]] = v 60 | return args_ 61 | 62 | 63 | def _ta_ma (args, kwargs, func): 64 | source, length = _expand_args(args, kwargs, 65 | (('source', Series, True), ('length', int, True))) 66 | try: 67 | return series_np(func(source, length), source) 68 | except Exception as e: 69 | if str(e) == 'inputs are all NaN': 70 | return source.dup() 71 | raise 72 | 73 | 74 | pyabs = abs 75 | def abs (vm, args, kwargs): 76 | source = _expand_args(args, kwargs, (('source', None, True),))[0] 77 | if isinstance(source, Series): 78 | return series_np(np.abs(source), source) 79 | else: 80 | return pyabs(source) 81 | 82 | def acos (vm, args, kwargs): 83 | raise NotImplementedError 84 | 85 | def alertcondition (vm, args, kwargs): 86 | raise NotImplementedError 87 | 88 | def alma (vm, args, kwargs): 89 | raise NotImplementedError 90 | 91 | def asin (vm, args, kwargs): 92 | raise NotImplementedError 93 | 94 | def atan (vm, args, kwargs): 95 | raise NotImplementedError 96 | 97 | def atr (vm, args, kwargs): 98 | raise NotImplementedError 99 | 100 | def avg (vm, args, kwargs): 101 | raise NotImplementedError 102 | 103 | def barcolor (vm, args, kwargs): 104 | raise NotImplementedError 105 | 106 | def barssince (vm, args, kwargs): 107 | condition = _expand_args(args, kwargs, ( 108 | ('condition', None, True), 109 | ))[0] 110 | if not isinstance(condition, Series): 111 | if bool(condition): 112 | return Series(list(range(1,vm.size+1))) 113 | return Series([0]*vm.size) 114 | 115 | # TODO speed! 116 | c = 0 117 | vidx = condition.valid_index 118 | dest = Series([0] * vm.size) 119 | for i in range(0, vidx+1): 120 | if condition.to_bool_safe(i): 121 | c = 0 122 | else: 123 | c += 1 124 | dest[i] = c 125 | return dest.set_valid_index(condition) 126 | 127 | def bgcolor (vm, args, kwargs): 128 | return None 129 | 130 | def cci (vm, args, kwargs): 131 | raise NotImplementedError 132 | 133 | def ceil (vm, args, kwargs): 134 | raise NotImplementedError 135 | 136 | def change (vm, args, kwargs): 137 | raise NotImplementedError 138 | 139 | def cog (vm, args, kwargs): 140 | raise NotImplementedError 141 | 142 | def color (vm, args, kwargs): 143 | col, transp = _expand_args(args, kwargs, (('color', str, True), ('transp', int, True))) 144 | return "{0}:{1}".format(col, transp) 145 | 146 | def correlation (vm, args, kwargs): 147 | raise NotImplementedError 148 | 149 | def cos (vm, args, kwargs): 150 | raise NotImplementedError 151 | 152 | def cross (vm, args, kwargs): 153 | x, y = _expand_args(args, kwargs, (('x', Series, True), ('y', None, True))) 154 | if not isinstance(y, Series): 155 | y = series_immutable(y, vm.size) 156 | s = series_mutable(False, vm.size) 157 | imax = pymin([x.valid_index, y.valid_index]) 158 | for i in range(1, imax+1): 159 | x1, y1 = x[i], y[i] 160 | x2, y2 = x[i-1], y[i-1] 161 | s.append((x1 - y1) * (x2 - y2) < 0) 162 | return s 163 | 164 | def crossover (vm, args, kwargs): 165 | x, y = _expand_args(args, kwargs, (('x', Series, True), ('y', None, True))) 166 | if not isinstance(y, Series): 167 | y = series_immutable(y, vm.size) 168 | s = series_mutable(False, vm.size) 169 | imax = pymin([x.valid_index, y.valid_index]) 170 | for i in range(1, imax+1): 171 | x1, y1 = x[i], y[i] 172 | x2, y2 = x[i-1], y[i-1] 173 | s.append(x1 > y1 and x2 < y2) 174 | return s 175 | 176 | def crossunder (vm, args, kwargs): 177 | x, y = _expand_args(args, kwargs, (('x', Series, True), ('y', None, True))) 178 | if not isinstance(y, Series): 179 | y = series_immutable(y, vm.size) 180 | s = series_mutable(False, vm.size) 181 | imax = pymin([x.valid_index, y.valid_index]) 182 | for i in range(1, imax+1): 183 | x1, y1 = x[i], y[i] 184 | x2, y2 = x[i-1], y[i-1] 185 | s.append(x1 < y1 and x2 > y2) 186 | return s 187 | 188 | def cum (vm, args, kwargs): 189 | raise NotImplementedError 190 | 191 | def dayofmonth (vm, args, kwargs): 192 | raise NotImplementedError 193 | 194 | def dayofweek (vm, args, kwargs): 195 | raise NotImplementedError 196 | 197 | def dev (vm, args, kwargs): 198 | raise NotImplementedError 199 | 200 | def ema (vm, args, kwargs): 201 | return _ta_ma(args, kwargs, ta.EMA) 202 | 203 | def exp (vm, args, kwargs): 204 | raise NotImplementedError 205 | 206 | def falling (vm, args, kwargs): 207 | raise NotImplementedError 208 | 209 | def fill (vm, args, kwargs): 210 | return None 211 | plot_functions['fill'] = fill 212 | 213 | def fixnan (vm, args, kwargs): 214 | raise NotImplementedError 215 | 216 | def floor (vm, args, kwargs): 217 | raise NotImplementedError 218 | 219 | def heikinashi (vm, args, kwargs): 220 | raise NotImplementedError 221 | 222 | _xest_args1 = ( 223 | ('length', int, True), 224 | ) 225 | _xest_args2 = ( 226 | ('source', Series, True), 227 | ) + _xest_args1 228 | 229 | def _xest (vm, args, kwargs, sop, xop): 230 | try: 231 | source, length = _expand_args(args, kwargs, _xest_args2) 232 | except PineArgumentError: 233 | length = _expand_args(args, kwargs, _xest_args1)[0] 234 | source = sop(vm) 235 | return series_np(xop(source, length), source) 236 | 237 | def highest (vm, args, kwargs): 238 | return _xest(vm, args, kwargs, high, ta.MAX) 239 | 240 | def highestbars (vm, args, kwargs): 241 | raise NotImplementedError 242 | 243 | def hline (vm, args, kwargs): 244 | return None 245 | plot_functions['hline'] = hline 246 | 247 | def hour (vm, args, kwargs): 248 | raise NotImplementedError 249 | 250 | def iff (vm, args, kwargs): 251 | raise NotImplementedError 252 | 253 | def _parse_input_args (args, kwargs): 254 | return _expand_args(args, kwargs, 255 | ( 256 | ('defval', None, True), 257 | ('title', str, False), 258 | ('type', str, False), 259 | ('minval', None , False), 260 | ('maxval', None, False), 261 | ('confirm', bool, False), 262 | ('step', None, False), 263 | ('options', None, False), 264 | ) 265 | ) 266 | 267 | def input (vm, args, kwargs, node=None): 268 | _args = _parse_input_args(args, kwargs) 269 | # TODO make dynamic, type check 270 | # return defval 271 | return _args[0] 272 | 273 | def kagi (vm, args, kwargs): 274 | raise NotImplementedError 275 | 276 | def linebreak (vm, args, kwargs): 277 | raise NotImplementedError 278 | 279 | def linreg (vm, args, kwargs): 280 | source, length, _offset = _expand_args(args, kwargs, 281 | (('source', Series, True), ('length', int, True), ('offset', int, True))) 282 | try: 283 | return series_np(ta.LINEARREG(source, length) + _offset, source) 284 | except Exception as e: 285 | if str(e) == 'inputs are all NaN': 286 | return source.dup() 287 | raise 288 | 289 | def log (vm, args, kwargs): 290 | raise NotImplementedError 291 | 292 | def log10 (vm, args, kwargs): 293 | raise NotImplementedError 294 | 295 | def lowest (vm, args, kwargs): 296 | return _xest(vm, args, kwargs, low, ta.MIN) 297 | 298 | def lowestbars (vm, args, kwargs): 299 | raise NotImplementedError 300 | 301 | def macd (vm, args, kwargs): 302 | raise NotImplementedError 303 | 304 | def _mxx (args, kwargs, op, sop): 305 | x, y = _expand_args(args, kwargs, (('x', None, True), ('y', None, True))) 306 | if not isinstance(x, Series): 307 | if not isinstance(y, Series): 308 | # !x, !y 309 | if math.isna(x): 310 | return y 311 | elif math.isna(y): 312 | return x 313 | return op((x,y)) 314 | else: 315 | # !x, y 316 | if math.isna(x): 317 | return y.dup() 318 | x = series_np([x] * y.size, y) 319 | elif not isinstance(y, Series): 320 | # x, !y 321 | if math.isna(y): 322 | return x.dup() 323 | y = series_np([y] * x.size, x) 324 | 325 | r = sop([x,y], axis=0) 326 | return Series(r).set_valid_index(x, y) 327 | 328 | pymax = max 329 | def max (vm, args, kwargs): 330 | return _mxx(args, kwargs, pymax, np.nanmax) 331 | 332 | pymin = min 333 | def min (vm, args, kwargs): 334 | return _mxx(args, kwargs, pymin, np.nanmin) 335 | 336 | def minute (vm, args, kwargs): 337 | raise NotImplementedError 338 | 339 | def mom (vm, args, kwargs): 340 | raise NotImplementedError 341 | 342 | def month (vm, args, kwargs): 343 | raise NotImplementedError 344 | 345 | def na (vm, args, kwargs): 346 | x, = _expand_args(args, kwargs, (('x', None, True),)) 347 | if isinstance(x, Series): 348 | a = x[:x.valid_index+1] 349 | b = x[x.valid_index:] 350 | return Series([math.isnan(v) for v in a] + b).set_valid_index(x) 351 | else: 352 | return math.isnan(x) 353 | 354 | def nz (vm, args, kwargs): 355 | x, y = _expand_args(args, kwargs, (('x', None, True),('y', None, False))) 356 | if y is None: 357 | y = 0.0 358 | if not isinstance(x, Series): 359 | if math.isnan(x): 360 | return y 361 | else: 362 | return x 363 | else: 364 | a = list(x)[:x.valid_index+1] 365 | b = list(x)[x.valid_index+1:] 366 | a_ = [] 367 | for v in a: 368 | if math.isnan(v): 369 | v = y 370 | a_.append(v) 371 | return Series(a_ + b).set_valid_index(x) 372 | 373 | def offset (vm, args, kwargs): 374 | source, _offset = _expand_args(args, kwargs, 375 | (('source', Series, True), ('offset', int, True))) 376 | return source.shift(_offset) 377 | 378 | def precentile_linear_interpolation (vm, args, kwargs): 379 | raise NotImplementedError 380 | 381 | def precentile_nearest_rank (vm, args, kwargs): 382 | raise NotImplementedError 383 | 384 | def percentrank (vm, args, kwargs): 385 | raise NotImplementedError 386 | 387 | _pivot_args2 = ( 388 | ('leftbars', int, True), 389 | ('rightbars', int, True), 390 | ) 391 | _pivot_args3 = ( 392 | ('source', Series, True), 393 | ) + _pivot_args2 394 | 395 | def _expand_pivot_args (args, kwargs): 396 | try: 397 | return _expand_args(args, kwargs, _pivot_args3) 398 | except PineArgumentError: 399 | l, r = _expand_args(args, kwargs, _pivot_args2) 400 | return (None, l, r) 401 | 402 | from .builtin_variable import high, low 403 | 404 | # FIXME: follow TV's implementation 405 | #def _pivot_inner (source, left, right, ismax): 406 | # slen = len(source) 407 | # if slen < left + right + 1: 408 | # return Series([NaN] * slen) 409 | # 410 | # r = [NaN] * left 411 | # for i in range(left, slen - right): 412 | # v = source[i] 413 | # bars = source[i-left:i+right] 414 | # if ismax: 415 | # p = bars.max() 416 | # else: 417 | # p = bars.min() 418 | # if v == p: 419 | # r.append(v) 420 | # else: 421 | # r.append(NaN) 422 | # r += [NaN] * right 423 | # return Series(r) 424 | def _pivot_inner (source, left, right, ismax): 425 | slen = len(source) 426 | if slen < left + right + 1: 427 | return Series([NaN] * slen) 428 | 429 | r = [NaN] * (left + right) 430 | for i in range(left, slen - right): 431 | v = source[i] 432 | bars = source[i-left:i+right] 433 | if ismax: 434 | p = bars.max() 435 | else: 436 | p = bars.min() 437 | if v == p: 438 | r.append(v) 439 | else: 440 | r.append(NaN) 441 | #r += [NaN] * right 442 | return Series(r) 443 | 444 | def pivothigh (vm, args, kwargs): 445 | source, left, right = _expand_pivot_args(args, kwargs) 446 | if source is None: 447 | source = high(vm) 448 | return _pivot_inner(source, left, right, True) 449 | 450 | def pivotlow (vm, args, kwargs): 451 | source, left, right = _expand_pivot_args(args, kwargs) 452 | if source is None: 453 | source = low(vm) 454 | return _pivot_inner(source, left, right, False) 455 | 456 | def plot (vm, args, kwargs): 457 | return None 458 | def plotarrow (vm, args, kwargs): 459 | return None 460 | def plotbar (vm, args, kwargs): 461 | return None 462 | def plotcandle (vm, args, kwargs): 463 | return None 464 | def plotchar (vm, args, kwargs): 465 | return None 466 | def plotshape (vm, args, kwargs): 467 | return None 468 | def plotfigure (vm, args, kwargs): 469 | return None 470 | plot_functions['plot'] = plot 471 | plot_functions['plotarrow'] = plotarrow 472 | plot_functions['plotbar'] = plotbar 473 | plot_functions['plotcandle'] = plotcandle 474 | plot_functions['plotchar'] = plotchar 475 | plot_functions['plotshape'] = plotshape 476 | plot_functions['plotfigure'] = plotfigure 477 | 478 | def pow (vm, args, kwargs): 479 | raise NotImplementedError 480 | 481 | def renko (vm, args, kwargs): 482 | raise NotImplementedError 483 | 484 | def rising (vm, args, kwargs): 485 | raise NotImplementedError 486 | 487 | def rma (vm, args, kwargs): 488 | source, length = _expand_args(args, kwargs, 489 | (('source', Series, True), ('length', int, True))) 490 | 491 | slen = len(source) 492 | if slen <= length: 493 | return source.dup() 494 | 495 | r = [NaN] * (length - 1) 496 | a = float(length - 1) 497 | for i in range(length - 1, slen): 498 | v = source[i] 499 | p = r[i-1] 500 | if math.isnan(v): 501 | r.append(NaN) 502 | else: 503 | if math.isnan(p): 504 | p = 0.0 505 | r.append((p * a + v) / length) 506 | return series_np(r, source) 507 | 508 | def roc (vm, args, kwargs): 509 | raise NotImplementedError 510 | 511 | def round (vm, args, kwargs): 512 | raise NotImplementedError 513 | 514 | def rsi (vm, args, kwargs): 515 | x, y = _expand_args(args, kwargs, 516 | (('x', Series, True), ('y', int, True))) 517 | if math.isnan(x[-1]): 518 | return x.dup() 519 | return series_np(ta.RSI(x, y), x) 520 | 521 | def sar (vm, args, kwargs): 522 | raise NotImplementedError 523 | 524 | def second (vm, args, kwargs): 525 | raise NotImplementedError 526 | 527 | def _parse_security_args (args, kwargs): 528 | return _expand_args(args, kwargs, 529 | ( 530 | ('symbol', str, True), 531 | ('resolution', str, True), 532 | ('security', Series, True), 533 | ('gaps', bool , False), 534 | ('lookahead', bool, False), 535 | ) 536 | ) 537 | 538 | def security (vm, args, kwargs): 539 | _parse_security_args(args, kwargs) 540 | raise NotImplementedError 541 | 542 | def sign (vm, args, kwargs): 543 | raise NotImplementedError 544 | 545 | def sin (vm, args, kwargs): 546 | raise NotImplementedError 547 | 548 | def sma (vm, args, kwargs): 549 | return _ta_ma(args, kwargs, ta.SMA) 550 | 551 | def sqrt (vm, args, kwargs): 552 | raise NotImplementedError 553 | 554 | def stdev (vm, args, kwargs): 555 | source, length = _expand_args(args, kwargs, 556 | ( 557 | ('source', Series, True), 558 | ('length', int, True), 559 | ) 560 | ) 561 | try: 562 | return series_np(ta.STDDEV(source, length), source) 563 | except Exception as e: 564 | if str(e) == 'inputs are all NaN': 565 | return source.dup() 566 | raise 567 | 568 | def stoch (vm, args, kwargs): 569 | source, high, low, length = _expand_args(args, kwargs, 570 | ( 571 | ('source', Series, True), 572 | ('high', Series, True), 573 | ('low', Series, True), 574 | ('length', int, True), 575 | ) 576 | ) 577 | try: 578 | fk, _ = ta.STOCHF(high, low, source, length) 579 | return series_np(fk, source) 580 | except Exception as e: 581 | if str(e) == 'inputs are all NaN': 582 | return source.dup() 583 | raise 584 | 585 | def strategy (vm, args, kwargs): 586 | vm.meta = _expand_args_as_dict(args, kwargs, 587 | ( 588 | ('title', str, True), 589 | ('shorttitle', str, False), 590 | ('overlay', bool, False), 591 | ('precision', int, False), 592 | ('scale', int, False), 593 | ('pyramiding', int, False), 594 | ('calc_on_order_fills', bool, False), 595 | ('calc_on_every_click', bool, False), 596 | ('max_bars_back', int, False), 597 | ('backtest_fill_limits_assumption', int, False), 598 | ('default_qty_type', str, False), 599 | ('default_qty_value', float, False), 600 | ('currency', str, False), 601 | ('linktoseries', bool, False), 602 | ('slippage', int, False), 603 | ('commision_type', str, False), 604 | ('commision_value', float, False), 605 | ) 606 | ) 607 | return None 608 | 609 | def _evaluate_when (vm, val): 610 | if not isinstance(val, Series): 611 | return bool(val) 612 | return val[vm.ip] 613 | 614 | def strategy__cancel (vm, args, kwargs): 615 | raise NotImplementedError 616 | 617 | def strategy__cancel_all (vm, args, kwargs): 618 | raise NotImplementedError 619 | 620 | def strategy__close (vm, args, kwargs): 621 | kws = _expand_args_as_dict(args, kwargs, 622 | ( 623 | ('id', str, True), 624 | ('when', None, False), 625 | ) 626 | ) 627 | when = kws.get('when', None) 628 | if when is not None and not _evaluate_when(vm, when): 629 | return 630 | if vm.broker: 631 | return vm.broker.close(kws) 632 | return 633 | 634 | def strategy__close_all (vm, args, kwargs): 635 | kws = _expand_args_as_dict(args, kwargs, 636 | ( 637 | ('when', None, False), 638 | ) 639 | ) 640 | when = kws.get('when', None) 641 | if when is not None and not _evaluate_when(vm, when): 642 | return 643 | if vm.broker: 644 | return vm.broker.close_all(kws) 645 | return 646 | 647 | def strategy__entry (vm, args, kwargs): 648 | if not vm.broker: 649 | return 650 | kws = _expand_args_as_dict(args, kwargs, 651 | ( 652 | ('id', str, True), 653 | ('long', bool, True), 654 | ('qty', float, False), 655 | ('limit', float, False), 656 | ('stop', float, False), 657 | ('oca_name', str, False), 658 | ('oca_type', str, False), 659 | ('comment', str, False), 660 | ('when', None, False), 661 | ) 662 | ) 663 | when = kws.get('when', None) 664 | if when is not None and not _evaluate_when(vm, when): 665 | return 666 | if vm.broker: 667 | return vm.broker.entry(kws) 668 | return 669 | 670 | def strategy__exit (vm, args, kwargs): 671 | raise NotImplementedError 672 | if not vm.broker: 673 | return None 674 | kws = _expand_args_as_dict(args, kwargs, 675 | ( 676 | ('id', str, True), 677 | ('from_entry', str, False), 678 | ('qty', float, False), 679 | ('qty_percent', float, False), 680 | ('profit', float, False), 681 | ('oca_name', str, False), 682 | ('oca_type', str, False), 683 | ('comment', str, False), 684 | ('when', None, False), 685 | ) 686 | ) 687 | when = kws.get('when', None) 688 | if when is not None and not _evaluate_when(vm, when): 689 | return 690 | if vm.broker: 691 | return vm.broker.exit(kws) 692 | return 693 | 694 | def strategy__order (vm, args, kwargs): 695 | raise NotImplementedError 696 | 697 | strategy_functions['strategy.cancel'] = strategy__cancel 698 | strategy_functions['strategy.cancel_all'] = strategy__cancel_all 699 | strategy_functions['strategy.close'] = strategy__close 700 | strategy_functions['strategy.close_all'] = strategy__close_all 701 | strategy_functions['strategy.entry'] = strategy__entry 702 | strategy_functions['strategy.exit'] = strategy__exit 703 | strategy_functions['strategy.order'] = strategy__order 704 | 705 | 706 | def strategy__risk__allow_entry_in (vm, args, kwargs): 707 | raise NotImplementedError 708 | 709 | def strategy__risk__max_cons_loss_days (vm, args, kwargs): 710 | raise NotImplementedError 711 | 712 | def strategy__risk__max_drawdown (vm, args, kwargs): 713 | raise NotImplementedError 714 | 715 | def strategy__risk__max_intraday_filled_orders (vm, args, kwargs): 716 | raise NotImplementedError 717 | 718 | def strategy__risk__max_intraday_loss (vm, args, kwargs): 719 | raise NotImplementedError 720 | 721 | def strategy__risk__max_position_size (vm, args, kwargs): 722 | raise NotImplementedError 723 | 724 | strategy_risk_functions['strategy.risk.allow_entry_in'] = strategy__risk__allow_entry_in 725 | strategy_risk_functions['strategy.risk.max_cons_loss_days'] = strategy__risk__max_cons_loss_days 726 | strategy_risk_functions['strategy.risk.max_drawdown'] = strategy__risk__max_drawdown 727 | strategy_risk_functions['strategy.risk.max_intraday_filled_orders'] = strategy__risk__max_intraday_filled_orders 728 | strategy_risk_functions['strategy.risk.max_intraday_loss'] = strategy__risk__max_intraday_loss 729 | strategy_risk_functions['strategy.risk.max_position_size'] = strategy__risk__max_position_size 730 | 731 | 732 | def study (vm, args, kwargs): 733 | vm.meta = _expand_args_as_dict(args, kwargs, 734 | ( 735 | ('title', str, True), 736 | ('shorttitle', str, False), 737 | ('overlay', bool, False), 738 | ('precision', int, False), 739 | ('scale', int, False), 740 | ('max_bars_back', int, False), 741 | ('linktoseries', bool, False), 742 | ) 743 | ) 744 | return None 745 | 746 | def sum (vm, args, kwargs): 747 | raise NotImplementedError 748 | 749 | def swma (vm, args, kwargs): 750 | raise NotImplementedError 751 | 752 | def tan (vm, args, kwargs): 753 | raise NotImplementedError 754 | 755 | def tickerid (vm, args, kwargs): 756 | raise NotImplementedError 757 | 758 | def time (vm, args, kwargs): 759 | raise NotImplementedError 760 | 761 | def timestamp (vm, args, kwargs): 762 | import datetime 763 | import pytz 764 | spec1 = ( 765 | ('year', int, True), 766 | ('month', int, True), 767 | ('day', int, True), 768 | ('hour', int, True), 769 | ('minute', int, True), 770 | ) 771 | spec2 = ( 772 | ('timezone', str, False), 773 | ) + spec1 774 | 775 | if args and isinstance(args[0], str): 776 | tz, year, month, day, hour, minute = _expand_args(args, kwargs, spec2) 777 | tzinfo = pytz.timezone(tz) 778 | else: 779 | year, month, day, hour, minute = _expand_args(args, kwargs, spec1) 780 | tzinfo = None 781 | 782 | dt = datetime.datetime(year, month, day, hour, minute, tzinfo=tzinfo) 783 | return int(dt.timestamp()) 784 | 785 | def tostring (vm, args, kwargs): 786 | raise NotImplementedError 787 | 788 | def tr (vm, args, kwargs): 789 | raise NotImplementedError 790 | 791 | def tsi (vm, args, kwargs): 792 | raise NotImplementedError 793 | 794 | def valuewhen (vm, args, kwargs): 795 | condition, source, occurrence = _expand_args(args, kwargs, ( 796 | ('condition', None, True), 797 | ('source', Series, True), 798 | ('occurrence', int, True), 799 | )) 800 | if not isinstance(condition, Series): 801 | if bool(condition): 802 | return source.offset(occurrence) 803 | return source.dup_none() 804 | 805 | # TODO speed! 806 | vidx = pymin([condition.valid_index, source.valid_index]) 807 | tval = [] 808 | dest = [] 809 | dval = source.default_elem() 810 | for i in range(0, vidx+1): 811 | if condition.to_bool_safe(i): 812 | tval.append(source[i]) 813 | if len(tval) > occurrence: 814 | dest.append(tval[-occurrence-1]) 815 | else: 816 | dest.append(dval) 817 | for i in range(vidx+1, vm.size): 818 | dest.append(dval) 819 | 820 | return Series(dest).set_valid_index(condition, source) 821 | 822 | def variance (vm, args, kwargs): 823 | raise NotImplementedError 824 | 825 | def vwap (vm, args, kwargs): 826 | raise NotImplementedError 827 | 828 | def vwma (vm, args, kwargs): 829 | raise NotImplementedError 830 | 831 | def weekofyear (vm, args, kwargs): 832 | raise NotImplementedError 833 | 834 | def wma (vm, args, kwargs): 835 | return _ta_ma(args, kwargs, ta.WMA) 836 | 837 | def year (vm, args, kwargs): 838 | raise NotImplementedError 839 | -------------------------------------------------------------------------------- /pine/vm/builtin_variable.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import numpy as np 4 | import talib as ta 5 | 6 | STYLE_LINE = 0 7 | STYLE_STEPLINE = 1 8 | STYLE_HISTOGRAM = 2 9 | STYLE_CROSS = 3 10 | STYLE_AREA = 4 11 | STYLE_COLUMNS = 5 12 | STYLE_CIRCLES = 6 13 | 14 | from ..base import PineError 15 | from .helper import bseries, NaN, series_np, series_mutable 16 | 17 | sources = {} 18 | 19 | def accdist (vm=None): 20 | raise NotImplementedError 21 | 22 | def adjustment__dividends (vm=None): 23 | raise NotImplementedError 24 | def adjustment__none (vm=None): 25 | raise NotImplementedError 26 | def adjustment__splits (vm=None): 27 | raise NotImplementedError 28 | 29 | def aqua (vm=None): 30 | return '#00FFFF' 31 | 32 | def area (vm=None): 33 | return STYLE_AREA 34 | def areabr (vm=None): 35 | return 0 36 | 37 | def barmerge__gaps_off (vm=None): 38 | return False 39 | def barmerge__gaps_on (vm=None): 40 | return True 41 | def barmerge__lookahead_off (vm=None): 42 | return False 43 | def barmerge__lookahead_on (vm=None): 44 | return True 45 | 46 | def barstate__isconfirmed (vm=None): 47 | raise NotImplementedError 48 | def barstate__isfirst (vm=None): 49 | raise NotImplementedError 50 | def barstate__ishistory (vm=None): 51 | raise NotImplementedError 52 | def barstate__islast (vm=None): 53 | raise NotImplementedError 54 | def barstate__isnew (vm=None): 55 | raise NotImplementedError 56 | def barstate__isrealtime (vm=None): 57 | raise NotImplementedError 58 | 59 | def black (vm=None): 60 | return '#000000' 61 | def blue (vm=None): 62 | return '#0000FF' 63 | 64 | def bool (vm=None): 65 | return 'bool' 66 | 67 | def circles (vm=None): 68 | return STYLE_CIRCLES 69 | 70 | def close (vm=None, count=0): 71 | if vm is None: 72 | return None 73 | if count: 74 | return vm.market.close(count) 75 | return bseries(vm.market.close(), close) 76 | sources['close'] = close 77 | 78 | def columns (vm=None): 79 | return STYLE_COLUMNS 80 | def cross (vm=None): 81 | return STYLE_CROSS 82 | 83 | def currency__AUD (vm=None): 84 | return NotImplementedError 85 | def currency__CAD (vm=None): 86 | return NotImplementedError 87 | def currency__CHF (vm=None): 88 | return NotImplementedError 89 | def currency__EUR (vm=None): 90 | return NotImplementedError 91 | def currency__GBP (vm=None): 92 | return NotImplementedError 93 | def currency__HKD (vm=None): 94 | return NotImplementedError 95 | def currency__JPY (vm=None): 96 | return NotImplementedError 97 | def currency__NOK (vm=None): 98 | return NotImplementedError 99 | def currency__NONE (vm=None): 100 | return NotImplementedError 101 | def currency__NZD (vm=None): 102 | return NotImplementedError 103 | def currency__RUB (vm=None): 104 | return NotImplementedError 105 | def currency__SKE (vm=None): 106 | return NotImplementedError 107 | def currency__SGD (vm=None): 108 | return NotImplementedError 109 | def currency__TRY (vm=None): 110 | return NotImplementedError 111 | def currency__USD (vm=None): 112 | return 'USD' 113 | def currency__ZAR (vm=None): 114 | return NotImplementedError 115 | 116 | def dashed (vm=None): 117 | return 0 118 | 119 | def dayofmonth (vm=None): 120 | raise NotImplementedError 121 | def dayofweek (vm=None): 122 | raise NotImplementedError 123 | 124 | def dotted (vm=None): 125 | return 0 126 | 127 | def float (vm=None): 128 | return 'float' 129 | 130 | def friday (vm=None): 131 | raise NotImplementedError 132 | 133 | def fuchsia (vm=None): 134 | return '#FF00FF' 135 | 136 | def gray (vm=None): 137 | return '#808080' 138 | 139 | def green (vm=None): 140 | return '#008000' 141 | 142 | def high (vm=None, count=0): 143 | if vm is None: 144 | return None 145 | if count: 146 | return vm.market.high(count) 147 | return bseries(vm.market.high(), high) 148 | sources['high'] = high 149 | 150 | def histogram (vm=None): 151 | return STYLE_HISTOGRAM 152 | 153 | def hl2 (vm=None, count=0): 154 | if vm is None: 155 | return None 156 | if count: 157 | h = vm.market.high(count) 158 | l = vm.market.low(count) 159 | return (h + l) / 2.0 160 | h = vm.market.high() 161 | l = vm.market.low() 162 | series = [sum(v2) / 2.0 for v2 in zip(h, l)] 163 | return bseries(series, hl2) 164 | sources['hl2'] = hl2 165 | 166 | def hlc3 (vm=None, count=0): 167 | if vm is None: 168 | return None 169 | if count: 170 | h = vm.market.high() 171 | l = vm.market.low() 172 | c = vm.market.close() 173 | return (h + l + c) / 3.0 174 | h = vm.market.high() 175 | l = vm.market.low() 176 | c = vm.market.close() 177 | series = [sum(v3) / 3.0 for v3 in zip(h, l, c)] 178 | return bseries(series, hlc3) 179 | sources['hlc3'] = hlc3 180 | 181 | def hour (vm=None): 182 | if vm is None: 183 | return 0 184 | return vm.timestamps[vm.ip] % (3600 * 24) / 3600 185 | 186 | def integer (vm=None): 187 | return 'integer' 188 | 189 | def interval (vm=None): 190 | raise NotImplementedError 191 | 192 | def isdaily (vm=None): 193 | raise NotImplementedError 194 | 195 | def isdwm (vm=None): 196 | raise NotImplementedError 197 | 198 | def isintraday (vm=None): 199 | raise NotImplementedError 200 | 201 | def ismonthly (vm=None): 202 | raise NotImplementedError 203 | 204 | def isweekly (vm=None): 205 | raise NotImplementedError 206 | 207 | def lime (vm=None): 208 | return '#00ff00' 209 | 210 | def line (vm=None): 211 | return STYLE_LINE 212 | 213 | def linebr (vm=None): 214 | return 0 215 | 216 | def location__abovebar (vm=None): 217 | return 'abovebar' 218 | def location__absolute (vm=None): 219 | return 'absolute' 220 | def location__belowbar (vm=None): 221 | return 'belowbar' 222 | def location__bottom (vm=None): 223 | return 'bottom' 224 | def location__top (vm=None): 225 | return 'top' 226 | 227 | def low (vm=None, count=0): 228 | if vm is None: 229 | return None 230 | if count: 231 | return vm.market.low(count) 232 | return bseries(vm.market.low(), low) 233 | sources['low'] = low 234 | 235 | def maroon (vm=None): 236 | return '#800000' 237 | 238 | def minute (vm=None): 239 | if vm is None: 240 | return 0 241 | return vm.timestamps[vm.ip] % 3600 / 60 242 | 243 | def monday (vm=None): 244 | raise NotImplementedError 245 | 246 | def month (vm=None): 247 | raise NotImplementedError 248 | 249 | def n (vm=None): 250 | raise NotImplementedError 251 | 252 | def na (vm=None): 253 | return NaN 254 | 255 | def navy (vm=None): 256 | return '#000080' 257 | 258 | def ohlc4 (vm=None, count=0): 259 | if vm is None: 260 | return None 261 | if count: 262 | o = vm.market.open() 263 | h = vm.market.high() 264 | l = vm.market.low() 265 | c = vm.market.close() 266 | return (o + h + l + c) / 4.0 267 | o = vm.market.open() 268 | h = vm.market.high() 269 | l = vm.market.low() 270 | c = vm.market.close() 271 | series = [sum(v4) / 4.0 for v4 in zip(o, h, l, c)] 272 | return bseries(series, ohlc4) 273 | sources['ohlc4'] = ohlc4 274 | 275 | def olive (vm=None): 276 | return '#808000' 277 | 278 | raise NotImplementedError 279 | 280 | def open (vm=None, count=0): 281 | if vm is None: 282 | return None 283 | if count: 284 | return vm.market.open(count) 285 | return bseries(vm.market.open(), open) 286 | sources['open'] = open 287 | 288 | def orange (vm=None): 289 | return '#ff7f00' 290 | 291 | def period (vm=None): 292 | if vm is None: 293 | return None 294 | return vm.market.period() 295 | 296 | def purple (vm=None): 297 | return '#800080' 298 | 299 | def red (vm=None): 300 | return '#ff0000' 301 | 302 | def resolution (vm=None): 303 | return 'resolution' 304 | 305 | def saturday (vm=None): 306 | raise NotImplementedError 307 | 308 | def scale__left (vm=None): 309 | return 0 310 | def scale__none (vm=None): 311 | return 0 312 | def scale__right (vm=None): 313 | return 0 314 | 315 | def second (vm=None): 316 | if vm is None: 317 | return 0 318 | return vm.timestamps[vm.ip] % 60 319 | 320 | def session (vm=None): 321 | raise NotImplementedError 322 | def session__extended (vm=None): 323 | raise NotImplementedError 324 | def session__regular (vm=None): 325 | raise NotImplementedError 326 | 327 | def shape__arrowdown (vm=None): 328 | return 'arrowdown' 329 | def shape__arrowup (vm=None): 330 | return 'arrowup' 331 | def shape__circle (vm=None): 332 | return 'circle' 333 | def shape__cross (vm=None): 334 | return 'cross' 335 | def shape__diamond (vm=None): 336 | return 'diamond' 337 | def shape__flag (vm=None): 338 | return 'flag' 339 | def shape__labeldown (vm=None): 340 | return 'labeldown' 341 | def shape__labelup (vm=None): 342 | return 'labelup' 343 | def shape__square (vm=None): 344 | return 'square' 345 | def shape__triangledown (vm=None): 346 | return 'triangledown' 347 | def shape__triangleup (vm=None): 348 | return 'triangleup' 349 | def shape__xcross (vm=None): 350 | return 'xcross' 351 | 352 | def silver (vm=None): 353 | return "#c0c0c0" 354 | 355 | def size__auto (vm=None): 356 | return 'auto' 357 | def size__huge (vm=None): 358 | return 'huge' 359 | def size__large (vm=None): 360 | return 'large' 361 | def size__normal (vm=None): 362 | return 'normal' 363 | def size__small (vm=None): 364 | return 'small' 365 | def size__tiny (vm=None): 366 | return 'tiny' 367 | 368 | def solid (vm=None): 369 | return 0 370 | 371 | def source (vm=None): 372 | return 'source' 373 | 374 | def stepline (vm=None): 375 | return STYLE_STEPLINE 376 | 377 | def strategy__cash (vm=None): 378 | return 'cash' 379 | def strategy__closedtrades (vm=None): 380 | raise NotImplementedError 381 | def strategy__commission__cash_per_contract (vm=None): 382 | return 'cash_per_contract' 383 | def strategy__commission__cash_per_order (vm=None): 384 | return 'cash_per_order' 385 | def strategy__commission__percent (vm=None): 386 | return 'percent' 387 | def strategy__direction_all (vm=None): 388 | raise NotImplementedError 389 | def strategy__direction_long (vm=None): 390 | raise NotImplementedError 391 | def strategy__direction_short (vm=None): 392 | raise NotImplementedError 393 | def strategy__equity (vm=None): 394 | raise NotImplementedError 395 | def strategy__eventrades (vm=None): 396 | raise NotImplementedError 397 | def strategy__fixed (vm=None): 398 | return 'fixed' 399 | def strategy__grossloss (vm=None): 400 | raise NotImplementedError 401 | def strategy__grossprofit (vm=None): 402 | raise NotImplementedError 403 | def strategy__initial_capital (vm=None): 404 | raise NotImplementedError 405 | def strategy__long (vm=None): 406 | return True 407 | def strategy__losstrades (vm=None): 408 | raise NotImplementedError 409 | def strategy__max_contracts_held_all (vm=None): 410 | raise NotImplementedError 411 | def strategy__max_contracts_held_long (vm=None): 412 | raise NotImplementedError 413 | def strategy__max_drawdown (vm=None): 414 | raise NotImplementedError 415 | def strategy__netprofit (vm=None): 416 | raise NotImplementedError 417 | def strategy__oca__cancel (vm=None): 418 | raise NotImplementedError 419 | def strategy__oca__none (vm=None): 420 | raise NotImplementedError 421 | def strategy__oca__reduce (vm=None): 422 | raise NotImplementedError 423 | def strategy__openprofit (vm=None): 424 | raise NotImplementedError 425 | def strategy__opentrades (vm=None): 426 | raise NotImplementedError 427 | def strategy__percent_of_equity (vm=None): 428 | return 'percent_of_equity' 429 | def strategy__position_avg_price (vm=None): 430 | raise NotImplementedError 431 | def strategy__position_entry_name (vm=None): 432 | raise NotImplementedError 433 | def strategy__position_size (vm=None): 434 | if vm is None: 435 | return None 436 | if vm.broker is None: 437 | return 0.0 438 | sz = vm.broker.position_size() 439 | if vm.ip == 0: 440 | return series_mutable(sz, vm.size) 441 | return sz 442 | 443 | def strategy__short (vm=None): 444 | return False 445 | def strategy__wintrades (vm=None): 446 | raise NotImplementedError 447 | 448 | def string (vm=None): 449 | return 'string' 450 | 451 | def sunday (vm=None): 452 | raise NotImplementedError 453 | 454 | def symbol (vm=None): 455 | return 'symbol' 456 | 457 | def syminfo__mintick (vm=None): 458 | if vm is None: 459 | return 0.0 460 | return vm.market.mintick() 461 | raise NotImplementedError 462 | def syminfo__pointvalue (vm=None): 463 | raise NotImplementedError 464 | def syminfo__prefix (vm=None): 465 | raise NotImplementedError 466 | def syminfo__root (vm=None): 467 | raise NotImplementedError 468 | def syminfo__session (vm=None): 469 | raise NotImplementedError 470 | def syminfo__timezone (vm=None): 471 | raise NotImplementedError 472 | 473 | def teal (vm=None): 474 | return '#008080' 475 | 476 | def thursday (vm=None): 477 | raise NotImplementedError 478 | 479 | def ticker (vm=None): 480 | raise NotImplementedError 481 | def tickerid (vm=None): 482 | if vm is None: 483 | return None 484 | return vm.market.tickerid() 485 | 486 | def time (vm=None, count=0): 487 | if vm is None: 488 | return None 489 | if count: 490 | return vm.market.timestamp(count) 491 | return vm.timestamps 492 | def timenow (vm=None): 493 | raise NotImplementedError 494 | 495 | def tr (vm=None): 496 | if vm is None: 497 | return None 498 | high = np.array(vm.market.high(), dtype='f8') 499 | low = np.array(vm.market.low(), dtype='f8') 500 | close = np.array(vm.market.close(), dtype='f8') 501 | return series_np(ta.TRANGE(high, low, close)) 502 | 503 | def tuesday (vm=None): 504 | raise NotImplementedError 505 | 506 | def volume (vm=None): 507 | raise NotImplementedError 508 | 509 | def vwap (vm=None): 510 | raise NotImplementedError 511 | 512 | def wednesday (vm=None): 513 | raise NotImplementedError 514 | 515 | def weekofyear (vm=None): 516 | raise NotImplementedError 517 | 518 | def white (vm=None): 519 | return '#000000' 520 | 521 | def year (vm=None): 522 | raise NotImplementedError 523 | 524 | def yellow (vm=None): 525 | return '#ffff00' 526 | -------------------------------------------------------------------------------- /pine/vm/compile.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from inspect import getmembers, isfunction, ismethod 4 | 5 | from . import builtin_function 6 | from . import builtin_variable 7 | from ..base import PineError 8 | 9 | def _load_builtins (mod, mod_sfx, dest): 10 | for name, func in getmembers(mod, isfunction): 11 | if not func.__module__.endswith(mod_sfx): 12 | continue 13 | if name.startswith('_'): 14 | continue 15 | name = name.replace('__', '.') 16 | dest[name] = func 17 | 18 | builtin_variables = {} 19 | _load_builtins(builtin_variable, '.builtin_variable', builtin_variables) 20 | 21 | builtin_functions = {} 22 | _load_builtins(builtin_function, '.builtin_function', builtin_functions) 23 | 24 | class FuncExpander (object): 25 | 26 | def __init__ (self): 27 | self.funcs = {} 28 | 29 | def register_function (self, node): 30 | self.funcs[node.name] = node 31 | 32 | def lookup_function (self, name): 33 | f = self.funcs.get(name, None) 34 | if f: 35 | return f 36 | f = builtin_functions.get(name, None) 37 | if f: 38 | return f 39 | raise PineError('function is not found: {}'.format(name)) 40 | 41 | def execute (self, node): 42 | return node.expand_func(self) 43 | 44 | def is_strategy_func (self, fname): 45 | return fname in builtin_function.strategy_functions 46 | 47 | def is_plot_func (self, fname): 48 | return fname in builtin_function.plot_functions 49 | 50 | class VarResolver (object): 51 | 52 | def __init__ (self): 53 | self.vars = [{}] 54 | 55 | def push_scope (self): 56 | self.vars.insert(0, {}) 57 | def pop_scope (self): 58 | self.vars.pop(0) 59 | 60 | def define_variable (self, node): 61 | self.vars[0][node.name] = node 62 | 63 | def lookup_variable (self, name): 64 | for t in self.vars: 65 | if name in t: 66 | return t[name] 67 | 68 | v = builtin_variables.get(name, None) 69 | if v is None: 70 | raise PineError("variable not found: {}".format(name)) 71 | return v 72 | 73 | def execute (self, node): 74 | return node.resolve_var(self) 75 | 76 | 77 | def compile_node (node): 78 | node = FuncExpander().execute(node) 79 | node = VarResolver().execute(node) 80 | return node 81 | 82 | from ..preprocess import preprocess 83 | from ..parser import parse 84 | def compile_pine (data): 85 | prepped = preprocess(data) 86 | node = parse(prepped) 87 | compiled = compile_node(node) 88 | return compiled 89 | -------------------------------------------------------------------------------- /pine/vm/helper.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import numpy as np 4 | from ..base import PineError 5 | 6 | NaN = float('nan') 7 | 8 | class Series (np.ndarray): 9 | 10 | def __new__ (cls, vals): 11 | obj = np.asarray(vals).view(cls) 12 | obj.valid_index = len(vals) - 1 13 | return obj 14 | 15 | def __array_finalize__ (self, obj): 16 | if obj is None: 17 | return 18 | self.valid_index = getattr(obj, 'valid_index', None) 19 | #self.valid_index = len(self) 20 | 21 | def logical_or (self, other): 22 | s = self 23 | if isinstance(s[0], float): 24 | s = np.nan_to_num(s) 25 | o = other 26 | if isinstance(o, Series) and isinstance(o, float): 27 | o = np.nan_to_num(o) 28 | return np.logical_or(s, o).set_valid_index(self, other) 29 | 30 | def logical_and (self, other): 31 | s = self 32 | if isinstance(s[0], float): 33 | s = np.nan_to_num(s) 34 | o = other 35 | if isinstance(o, Series) and isinstance(o, float): 36 | o = np.nan_to_num(o) 37 | return np.logical_and(s, o).set_valid_index(self, other) 38 | 39 | def logical_not (self): 40 | return np.logical_not(self).set_valid_index(self) 41 | 42 | def sign (self): 43 | return np.sign(self).set_valid_index(self) 44 | 45 | def set_valid_index (self, a, b=None): 46 | if isinstance(b, Series): 47 | if isinstance(a, Series): 48 | self.valid_index = min(a.valid_index, b.valid_index) 49 | else: 50 | self.valid_index = b.valid_index 51 | elif isinstance(a, Series): 52 | self.valid_index = a.valid_index 53 | else: 54 | raise PineError("Trying to set valid index for non-Series values: {0}, {1}").format(a, b) 55 | return self 56 | 57 | def out_of_date (self, vm): 58 | return self.valid_index < vm.ip 59 | 60 | def filled (self): 61 | return self.valid_index == (self.size - 1) 62 | 63 | def default_elem (self): 64 | if self.dtype == 'float64': 65 | return NaN 66 | elif self.dtype == 'int64': 67 | return 0 68 | elif self.dtype == 'bool': 69 | return False 70 | elif self.dtype == 'object': 71 | return False 72 | else: 73 | raise PineError("No default value for series: {}".format(self.dtype)) 74 | 75 | def to_bool_safe (self, idx=None): 76 | if idx is None: 77 | r = self 78 | else: 79 | r = self[idx] 80 | if self.dtype != 'float64': 81 | return r 82 | else: 83 | return np.nan_to_num(r) 84 | 85 | def shift (self, offset): 86 | if offset == 0: 87 | return self 88 | 89 | d = self.default_elem() 90 | if abs(offset) >= self.size: 91 | r = Series([d] * self.size) 92 | else: 93 | r = np.roll(self, offset) 94 | if offset > 0: 95 | rng = range(0, offset) 96 | else: 97 | rng = range(self.size+offset, self.size) 98 | for i in rng: 99 | r[i] = d 100 | return r.set_valid_index(self) 101 | 102 | def step (self, v=None): 103 | self[:self.size-1] = self[1:] 104 | self[-1] = self.default_elem() 105 | if v: 106 | self[-1] = v 107 | else: 108 | self[-1] = self.default_elem() 109 | self.valid_index -= 1 110 | return self 111 | 112 | def to_mutable_series (self): 113 | r = Series([self.default_elem()] * self.size) 114 | r[0] = self[0] 115 | r.valid_index = 0 116 | return r 117 | 118 | def append (self, v): 119 | self.valid_index += 1 120 | self[self.valid_index] = v 121 | return v 122 | 123 | def dup (self): 124 | return self.copy().set_valid_index(self) 125 | 126 | def dup_none (self): 127 | r = Series([self.default_elem()] * self.size) 128 | r.valid_index = self.valid_index 129 | return r 130 | 131 | 132 | class BuiltinSeries (Series): 133 | 134 | @property 135 | def varname (self): 136 | return self.varfunc.__name__ 137 | 138 | def bseries (vals, func): 139 | s = BuiltinSeries(vals) 140 | s.varfunc = func 141 | return s 142 | 143 | def series_np (np_array, source): 144 | return Series(np_array).set_valid_index(source) 145 | 146 | def series_mutable (v, size): 147 | if isinstance(v, float): 148 | d = NaN 149 | elif isinstance(v, int): 150 | d = 0 151 | elif isinstance(v, bool): 152 | d = False 153 | else: 154 | d = None 155 | s = Series([d] * size) 156 | s[0] = v 157 | s.valid_index = 0 158 | return s 159 | 160 | def series_immutable (v, size): 161 | return Series([v] * size) 162 | -------------------------------------------------------------------------------- /pine/vm/node.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import copy 4 | import math 5 | import numpy 6 | 7 | from ..base import PineError 8 | from .helper import Series, NaN, series_mutable 9 | 10 | # AST Node 11 | class Node (object): 12 | 13 | def __init__ (self): 14 | self.children = [] 15 | self.args = [] 16 | self.lno = None 17 | self.to_dump = True 18 | 19 | def __str__ (self): 20 | me = "{0}: {1}".format(self.__class__.__name__, ", ".join([str(a) for a in self.args])) 21 | if self.to_dump: 22 | self.to_dump = False 23 | ln = [me] 24 | for n in self.children: 25 | for l in str(n).splitlines(): 26 | ln.append(' ' + l) 27 | return "\n".join(ln) 28 | else: 29 | return me + " ..." 30 | 31 | def _reset_dump (self): 32 | self.to_dump = True 33 | for n in self.children: 34 | n._reset_dump() 35 | return self 36 | 37 | def dump (self): 38 | print(self._reset_dump()) 39 | 40 | def append (self, node): 41 | self.children.append(node) 42 | return self 43 | 44 | def lineno (self, lineno): 45 | self.lno = lineno 46 | return self 47 | 48 | def expand_func (self, ctxt): 49 | self.children = [n.expand_func(ctxt) for n in self.children] 50 | self.children = [n for n in self.children if n is not None] 51 | return self 52 | 53 | def resolve_var (self, ctxt): 54 | self.children = [n.resolve_var(ctxt) for n in self.children] 55 | return self 56 | 57 | def collect_anotation (self, ctxt): 58 | for n in self.children: 59 | n.collect_anotation(ctxt) 60 | 61 | def evaluate (self, vm): 62 | v = None 63 | for c in self.children: 64 | v = c.evaluate(vm) 65 | return v 66 | 67 | 68 | class LiteralNode (Node): 69 | def __init__ (self, literal): 70 | super().__init__() 71 | if isinstance(literal, Node): 72 | self.children = literal.children.copy() 73 | else: 74 | self.args.append(literal) 75 | 76 | def evaluate (self, vm): 77 | if self.args: 78 | return self.args[0] 79 | else: 80 | return [e.evaluate(vm) for e in self.children] 81 | 82 | 83 | class OrNode (Node): 84 | 85 | def evaluate (self, vm): 86 | r = False 87 | for n in self.children: 88 | v = n.evaluate(vm) 89 | if not isinstance(v, Series): 90 | if bool(v): 91 | return v 92 | elif r is False: 93 | r = v 94 | else: 95 | r = v.logical_or(r) 96 | return r 97 | 98 | class AndNode (Node): 99 | 100 | def evaluate (self, vm): 101 | r = True 102 | for n in self.children: 103 | v = n.evaluate(vm) 104 | if not isinstance(v, Series): 105 | if not bool(v): 106 | return v 107 | elif r is True: 108 | r = v 109 | else: 110 | r = v.logical_and(r) 111 | return r 112 | 113 | 114 | class BinOpNode (Node): 115 | def __init__ (self, op, a, b): 116 | super().__init__() 117 | self.args.append(op) 118 | self.append(a) 119 | self.append(b) 120 | 121 | def evaluate (self, vm): 122 | operator = self.args[0] 123 | a = self.children[0].evaluate(vm) 124 | b = self.children[1].evaluate(vm) 125 | 126 | if operator == r'[': 127 | return self.index_access(vm, a, b) 128 | 129 | if operator == r'==': 130 | op = lambda a,b: a == b 131 | elif operator == r'!=': 132 | op = lambda a,b: a != b 133 | elif operator == r'>': 134 | op = lambda a,b: a > b 135 | elif operator == r'>=': 136 | op = lambda a,b: a >= b 137 | elif operator == r'<': 138 | op = lambda a,b: a < b 139 | elif operator == r'<=': 140 | op = lambda a,b: a <= b 141 | elif operator == r'+': 142 | op = lambda a,b: a + b 143 | elif operator == r'-': 144 | op = lambda a,b: a - b 145 | elif operator == r'*': 146 | op = lambda a,b: a * b 147 | elif operator == r'/': 148 | op = lambda a,b: a / b 149 | elif operator == r'%': 150 | op = lambda a,b: a % b 151 | else: 152 | raise PineError('invalid operator: {}'.format(operator)) 153 | 154 | # FIXME need type check 155 | s = op(a, b) 156 | if not isinstance(s, Series): 157 | return s 158 | else: 159 | return s.set_valid_index(a, b) 160 | 161 | def index_access (self, vm, a, b): 162 | if not isinstance(a, Series): # and not isinstance(a, list): 163 | raise PineError('cannot access by index for: {0}'.format(type(a))) 164 | if not isinstance(b, int): 165 | raise PineError('index must be an interger'.format(b)) 166 | return a.shift(b) 167 | 168 | 169 | class UniOpNode (Node): 170 | def __init__ (self, op, a): 171 | super().__init__() 172 | self.args.append(op) 173 | self.append(a) 174 | 175 | def evaluate (self, vm): 176 | op = self.args[0] 177 | rhv = self.children[0].evaluate(vm) 178 | 179 | if isinstance(rhv, Series): 180 | if op == 'not': 181 | return rhv.logical_not() 182 | if op == '+': 183 | return rhv 184 | if op == '-': 185 | return rhv.sign() 186 | raise PineError('invalid unary op: {}'.format(op)) 187 | 188 | if op == 'not': 189 | return not bool(rhv) 190 | if op == '+': 191 | return rhv 192 | if op == '-': 193 | return -rhv 194 | raise PineError('invalid unary op: {}'.format(op)) 195 | 196 | class BuiltinVarRefNode (Node): 197 | def __init__ (self, ident, func): 198 | super().__init__() 199 | self.args.append(ident) 200 | self.args.append(func) 201 | 202 | def evaluate (self, vm): 203 | func = self.args[1] 204 | 205 | if vm.ip == 0: 206 | v = func(vm) 207 | if isinstance(v, Series) and v.valid_index == 0: 208 | vm.set_register(self, v) 209 | return v 210 | 211 | val = vm.get_register(self) 212 | if val is not None: 213 | if val.out_of_date(vm): 214 | v = func(vm) 215 | vm.set_register_value(self, v) 216 | return val 217 | else: 218 | return func(vm) 219 | 220 | class VarRefNode (Node): 221 | def __init__ (self, ident): 222 | super().__init__() 223 | self.args.append(ident) 224 | 225 | def resolve_var (self, ctxt): 226 | ident = self.args[0] 227 | v = ctxt.lookup_variable(ident) 228 | if not isinstance(v, Node): # built-in variable 229 | try: 230 | v_ = v() 231 | except NotImplementedError: 232 | v_ = None 233 | if v_ is None: 234 | return BuiltinVarRefNode(ident, v).lineno(self.lno) 235 | else: 236 | return LiteralNode(v_).lineno(self.lno) 237 | # User-defined var 238 | self.append(v) 239 | return self 240 | 241 | class KwArgsNode (Node): 242 | 243 | def __init__ (self, kwargs): 244 | super().__init__() 245 | if kwargs: 246 | for k, n in kwargs.items(): 247 | self.args.append(k) 248 | self.append(n) 249 | 250 | def evaluate (self, vm): 251 | kws = {} 252 | for k, n in zip(self.args, self.children): 253 | kws[k] = n.evaluate(vm) 254 | return kws 255 | 256 | class FunCallNode (Node): 257 | def __init__ (self, fname, args): 258 | super().__init__() 259 | self.args.append(fname) 260 | aargs = args[0] 261 | if not isinstance(aargs, Node): 262 | aargs = Node() 263 | self.append(aargs) 264 | kwargs = args[1] 265 | if not isinstance(kwargs, Node): 266 | kwargs = KwArgsNode(kwargs) 267 | self.append(kwargs) 268 | 269 | @property 270 | def fname (self): 271 | return self.args[0] 272 | 273 | def expand_func (self, ctxt): 274 | fname = self.args[0] 275 | func = ctxt.lookup_function(fname) 276 | 277 | self.children = [n.expand_func(ctxt) for n in self.children] 278 | 279 | if isinstance(func, Node): # user-defined 280 | return UserFuncCallNode(fname, self.children, copy.deepcopy(func)) 281 | else: 282 | if fname == 'study' or fname == 'strategy': 283 | cls = MetaInfoFuncNode 284 | elif fname == 'input': 285 | cls = InputFuncNode 286 | elif fname == 'security': 287 | cls = SecurityFuncNode 288 | elif ctxt.is_strategy_func(fname): 289 | cls = StrategyFuncNode 290 | elif ctxt.is_plot_func(fname): 291 | cls = PlotFuncNode 292 | else: 293 | cls = BuiltinFunCallNode 294 | return cls(fname, self.children, func) 295 | 296 | def evaluate (self, vm): 297 | raise NotImplementedError 298 | 299 | class BuiltinFunCallNode (FunCallNode): 300 | 301 | def __init__ (self, fname, args, func): 302 | super().__init__(fname, args) 303 | self.func = func 304 | 305 | def _pre_evaluate (self, vm): 306 | fn = self.fname 307 | cb = self.func 308 | if hasattr(vm, fn): 309 | cb = getattr(vm, fn) 310 | 311 | args, kwargs = self.children 312 | _args = [a.evaluate(vm) for a in args.children] 313 | _kwargs = kwargs.evaluate(vm) 314 | return (cb, _args, _kwargs) 315 | 316 | def evaluate (self, vm): 317 | cb, args, kwargs = self._pre_evaluate(vm) 318 | try: 319 | return cb(vm, args, kwargs) 320 | except NotImplementedError as e: 321 | raise PineError("not implemented: {}".format(self.fname)) from e 322 | except Exception as e: 323 | raise 324 | raise PineError("{0}: {1}".format(str(e), self.fname)) from e 325 | 326 | class MetaInfoFuncNode (BuiltinFunCallNode): 327 | def collect_anotation (self, ctxt): 328 | ctxt.register_meta(self) 329 | 330 | def evaluate (self, vm): 331 | if not vm.meta: 332 | return super().evaluate(vm) 333 | 334 | class InputFuncNode (BuiltinFunCallNode): 335 | def collect_anotation (self, ctxt): 336 | ctxt.register_input(self) 337 | 338 | def evaluate (self, vm): 339 | cb, args, kwargs = self._pre_evaluate(vm) 340 | return cb(vm, args, kwargs, self) 341 | 342 | 343 | class SecurityFuncNode (BuiltinFunCallNode): 344 | def collect_anotation (self, ctxt): 345 | ctxt.register_security(self) 346 | 347 | class StrategyFuncNode (BuiltinFunCallNode): 348 | def collect_anotation (self, ctxt): 349 | ctxt.register_strategy(self) 350 | 351 | class PlotFuncNode (BuiltinFunCallNode): 352 | def collect_anotation (self, ctxt): 353 | ctxt.register_plot(self) 354 | 355 | 356 | class UserFuncCallNode (Node): 357 | 358 | # args: fname, [arg_id, ...] 359 | # children: arg_var_defs, body 360 | def __init__ (self, fname, args, node): 361 | super().__init__() 362 | self.args.append(fname) 363 | 364 | arg_ids = node.args[1].children 365 | self.args.append(arg_ids) 366 | 367 | argdef = Node() 368 | argn = args[0] # FIXME kwarg should be denied. 369 | for v, n in zip(arg_ids, argn.children): 370 | argdef.append(VarDefNode(v, n).lineno(node.lno)) 371 | 372 | self.append(argdef) 373 | self.append(node.children[0]) 374 | 375 | def resolve_var (self, ctxt): 376 | try: 377 | ctxt.push_scope() 378 | return super().resolve_var(ctxt) 379 | finally: 380 | ctxt.pop_scope() 381 | 382 | class IfNode (Node): 383 | def __init__ (self, condition, ifclause, elseclause, is_expr=True): 384 | super().__init__() 385 | self.append(condition) 386 | self.append(ifclause) 387 | if elseclause: 388 | self.append(elseclause) 389 | self.is_expr = is_expr 390 | 391 | def resolve_var (self, ctxt): 392 | # condition 393 | self.children[0] = self.children[0].resolve_var(ctxt) 394 | # true 395 | try: 396 | ctxt.push_scope() 397 | self.children[1] = self.children[1].resolve_var(ctxt) 398 | finally: 399 | ctxt.pop_scope() 400 | # false 401 | if len(self.children) > 2: 402 | try: 403 | ctxt.push_scope() 404 | self.children[2] = self.children[2].resolve_var(ctxt) 405 | finally: 406 | ctxt.pop_scope() 407 | return self 408 | 409 | def _first_eval_as_series (self, vm, c, s1, s2): 410 | if self.is_expr and c.filled(): 411 | s1 = s1.evaluate(vm) 412 | if isinstance(s1, Series): 413 | r = s1.copy() 414 | else: 415 | r = Series([s1] * len(c)) 416 | if s2: 417 | s2 = s2.evaluate(vm) 418 | if not isinstance(s2, Series): 419 | s2 = Series([s2] * len(c)) 420 | else: 421 | s2 = Series([s1.default_elem()] * len(c)) 422 | 423 | c_ = c.to_bool_safe() 424 | for i in range(0, len(c)): 425 | if not bool(c_[i]): 426 | r[i] = s2[i] 427 | 428 | r.set_valid_index(c) 429 | return r 430 | else: 431 | # return mutable Series 432 | if c.to_bool_safe(vm.ip): 433 | r = s1.evaluate(vm) 434 | elif s2: 435 | r = s2.evaluate(vm) 436 | else: 437 | r = None 438 | if isinstance(r, Series): 439 | return r.to_mutable_series() 440 | else: 441 | return series_mutable(r, c.size) 442 | 443 | def evaluate (self, vm): 444 | c = self.children[0].evaluate(vm) 445 | s1 = self.children[1] 446 | if len(self.children) > 2: 447 | s2 = self.children[2] 448 | else: 449 | s2 = None 450 | 451 | val = None 452 | if isinstance(c, Series): 453 | val = vm.get_register(self) 454 | if val is None: 455 | val = self._first_eval_as_series(vm, c, s1, s2) 456 | vm.set_register(self, val) 457 | return val 458 | else: 459 | c = bool(c.to_bool_safe(vm.ip)) 460 | r = None 461 | if bool(c) and not math.isnan(c): 462 | r = s1.evaluate(vm) 463 | elif s2: 464 | r = s2.evaluate(vm) 465 | else: 466 | r = None 467 | 468 | if val is not None: 469 | if not val.filled(): 470 | if isinstance(r, Series): 471 | r = r[vm.ip] 472 | vm.set_register_value(self, r) 473 | return val 474 | else: 475 | return r 476 | 477 | class ForNode (Node): 478 | 479 | def __init__ (self, var_def, to_clause, stmts_block): 480 | super().__init__() 481 | self.append(var_def) 482 | self.append(to_clause) 483 | self.append(stmts_block) 484 | 485 | def resolve_var (self, ctxt): 486 | try: 487 | ctxt.push_scope() 488 | return super().resolve_var(ctxt) 489 | finally: 490 | ctxt.pop_scope() 491 | 492 | def evaluate (self, vm): 493 | var_def, to_node, body = self.children 494 | retval = None 495 | 496 | counter_init = var_def.evaluate(vm) 497 | counter_last = to_node.evaluate(vm) 498 | if counter_init <= counter_last: 499 | op = '+' 500 | else: 501 | op = '-' 502 | 503 | counter = counter_init 504 | while True: 505 | # TODO continue, break 506 | try: 507 | # TODO this is doable in resolve_var... 508 | vm.push_register_scope() 509 | retval = body.evaluate(vm) 510 | finally: 511 | vm.pop_register_scope() 512 | 513 | if counter == counter_last: 514 | break 515 | 516 | if op == '+': 517 | counter += 1 518 | else: 519 | counter -= 1 520 | vm.set_register(var_def, counter) 521 | 522 | vm.set_register(var_def, counter_init) 523 | return retval 524 | 525 | class DefNode (Node): 526 | 527 | def __init__ (self, name): 528 | super().__init__() 529 | self.args.append(name) 530 | 531 | @property 532 | def name (self): 533 | return self.args[0] 534 | 535 | class FunDefNode (DefNode): 536 | def __init__ (self, fname, args, body): 537 | super().__init__(fname) 538 | self.args.append(args) 539 | self.append(body) 540 | 541 | def expand_func (self, ctxt): 542 | super().expand_func(ctxt) 543 | ctxt.register_function(self) 544 | return None 545 | 546 | def resolve_var (self, ctxt): 547 | raise NotImplementedError 548 | 549 | def evaluate (self, vm): 550 | raise NotImplementedError 551 | 552 | class VarDefNode (DefNode): 553 | def __init__ (self, ident, expr): 554 | super().__init__(ident) # ident 555 | self.args.append(False) # mutable 556 | self.args.append(None) # type 557 | self.append(expr) 558 | 559 | def resolve_var (self, ctxt): 560 | super().resolve_var(ctxt) 561 | ctxt.define_variable(self) 562 | return self 563 | 564 | def make_mutable (self): 565 | self.args[1] = True 566 | 567 | def evaluate (self, vm): 568 | val = vm.get_register(self) 569 | rhv = self.children[0] 570 | if val is None: 571 | rhv = rhv.evaluate(vm) 572 | mutable = self.args[1] 573 | if mutable: 574 | val = vm.alloc_register(self, rhv) 575 | else: 576 | val = vm.set_register(self, rhv) 577 | elif isinstance(val, Series): 578 | if val.out_of_date(vm): 579 | rhv = rhv.evaluate(vm) 580 | if isinstance(rhv, Series): 581 | rhv = rhv[vm.ip] 582 | vm.set_register_value(self, rhv) 583 | return val 584 | 585 | class VarAssignNode (Node): 586 | def __init__ (self, ident, expr): 587 | super().__init__() 588 | self.args.append(ident) 589 | self.append(expr) 590 | 591 | def resolve_var (self, ctxt): 592 | # rhv 593 | super().resolve_var(ctxt) 594 | # make lookup node 595 | ident = self.args[0] 596 | v = ctxt.lookup_variable(ident) 597 | if not isinstance(v, Node): 598 | raise PineError('cannot assign to built-in variable: {}'.format(ident)) 599 | v.make_mutable() 600 | self.children.insert(0, v) 601 | # rhv 602 | return self 603 | 604 | def evaluate (self, vm): 605 | dest = self.children[0] 606 | rhv = self.children[1].evaluate(vm) 607 | if isinstance(rhv, Series): 608 | rhv = rhv[vm.ip] 609 | vm.set_register_value(dest, rhv) 610 | return dest 611 | -------------------------------------------------------------------------------- /pine/vm/plot.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import numpy 4 | 5 | from . import builtin_function, builtin_variable 6 | from .vm import VM 7 | from .helper import Series, NaN 8 | 9 | class PlotVM (VM): 10 | 11 | def __init__ (self, market=None): 12 | super().__init__(market) 13 | self.outputs = [] 14 | 15 | def load_node (self, node): 16 | super().load_node(node) 17 | # TODO good to save plot and strategy inputs if volatile 18 | # by inserting proxy node 19 | 20 | def run (self): 21 | super().run() 22 | if self.broker: 23 | self.plot_orders(self.broker.order_history) 24 | 25 | def plot (self, vm, args, kwargs): 26 | if not vm.is_last_step(): 27 | return None 28 | 29 | series, title, color, linewidth, style,\ 30 | trackprice, transp, histbase,\ 31 | offset, editable, show_last = builtin_function._expand_args(args, kwargs, ( 32 | ('series', None, True), 33 | ('title', str, False), 34 | ('color', None, False), 35 | ('linewidth', int, False), 36 | ('style', int, False), 37 | ('trackprice', bool, False), 38 | ('transp', int, False), 39 | ('histbase', float, False), 40 | ('offset', int, False), 41 | ('editable', bool, False), 42 | ('show_last', int, False), 43 | )) 44 | 45 | if not isinstance(series, Series): 46 | series = Series([series] * vm.size) 47 | 48 | plot = {'title': title} 49 | 50 | if style: 51 | if style == builtin_variable.STYLE_LINE: 52 | typ = 'line' 53 | elif style == builtin_variable.STYLE_STEPLINE: 54 | typ = 'line' 55 | elif style == builtin_variable.STYLE_HISTOGRAM: 56 | typ = 'bar' 57 | elif style == builtin_variable.STYLE_CROSS: 58 | typ = 'marker' 59 | plot['mark'] = '+' 60 | elif style == builtin_variable.STYLE_AREA: 61 | typ = 'band' 62 | elif style == builtin_variable.STYLE_COLUMNS: 63 | typ = 'bar' 64 | elif style == builtin_variable.STYLE_CIRCLES: 65 | typ = 'marker' 66 | plot['mark'] = 'o' 67 | else: 68 | typ = 'line' 69 | plot['type'] = typ 70 | 71 | if color is not None: 72 | if isinstance(color, Series): # FIXME 73 | color = color[-1] 74 | color, *_transp = color.split(':') 75 | if _transp: 76 | transp = _transp[0] 77 | plot['color'] = color 78 | if linewidth: 79 | plot['width'] = linewidth 80 | if transp: 81 | plot['alpha'] = transp * 0.01 82 | if offset: 83 | series = series.shift(offset) 84 | 85 | plot['series'] = series 86 | self.outputs.append(plot) 87 | return plot 88 | 89 | def plotshape (self, vm, args, kwargs): 90 | if not vm.is_last_step(): 91 | return None 92 | 93 | series, title, style, location,\ 94 | color, transp,\ 95 | offset, text, textcolor,\ 96 | join, editable, show_last, size = builtin_function._expand_args(args, kwargs, ( 97 | ('series', None, True), 98 | ('title', str, False), 99 | ('style', str, False), 100 | ('location', str, False), 101 | ('color', str, False), 102 | ('transp', int, False), 103 | ('offset', int, False), 104 | ('text', str, False), 105 | ('textcolor', str, False), 106 | ('join', bool, False), 107 | ('editable', bool, False), 108 | ('show_last', int, False), 109 | ('size', str, False), 110 | )) 111 | 112 | if not isinstance(series, Series): 113 | series = Series([series] * vm.size) 114 | 115 | plot = {'title': title} 116 | 117 | if location is None: 118 | pass 119 | 120 | if color is not None: 121 | if isinstance(color, Series): 122 | color = color[-1] 123 | color, *_transp = color.split(':') 124 | if _transp: 125 | transp = _transp[0] 126 | plot['color'] = color 127 | if size: 128 | plot['size'] = size 129 | if transp: 130 | plot['alpha'] = transp * 0.01 131 | if offset: 132 | series = series.shift(offset) 133 | 134 | plot['series'] = series 135 | #self.outputs.append(plot) 136 | return None 137 | 138 | def hline (self, vm, args, kwargs): 139 | if not vm.is_last_step(): 140 | return None 141 | 142 | price, title,\ 143 | color, linestyle, linewidth, editable = builtin_function._expand_args(args, kwargs, ( 144 | ('price', float, True), 145 | ('title', str, False), 146 | ('color', str, False), 147 | ('linestyle', int, False), 148 | ('linewidth', int, False), 149 | ('editable', bool, False), 150 | )) 151 | 152 | plot = {'title': title, 'series': price, 'type': 'hline'} 153 | if color: 154 | plot['color'] = color 155 | if linewidth: 156 | plot['width'] = linewidth 157 | 158 | self.outputs.append(plot) 159 | return plot 160 | 161 | def fill (self, vm, args, kwargs): 162 | if not vm.is_last_step(): 163 | return None 164 | 165 | s1, s2,\ 166 | color, transp, title, editable, _ = builtin_function._expand_args(args, kwargs, ( 167 | ('series1', dict, True), 168 | ('series2', dict, True), 169 | ('color', str, False), 170 | ('transp', int, False), 171 | ('title', str, False), 172 | ('editable', bool, False), 173 | ('show_last', bool, False), 174 | )) 175 | 176 | plot = {'title': title, 'series': s1['series'], 'series2': s2['series'], 'type': 'fill'} 177 | 178 | if color is not None: 179 | if isinstance(color, Series): 180 | color = color[-1] 181 | color, *_transp = color.split(':') 182 | if _transp: 183 | transp = _transp[0] 184 | plot['color'] = color 185 | if transp: 186 | plot['alpha'] = transp * 0.01 187 | 188 | self.plots.append(plot) 189 | return plot 190 | 191 | 192 | def plot_orders (self, order_history): 193 | # FIXME 194 | close = builtin_variable.close(self) 195 | longs, shorts = [(NaN,0)], [(NaN, 0)] 196 | has_long = has_short = False 197 | for price, orders in zip(close, order_history): 198 | l = s = (NaN, 0) 199 | if orders: 200 | qty = sum([o['qty'] for o in orders]) 201 | if qty > 0: 202 | l = (price, qty) 203 | has_long = True 204 | elif qty < 0: 205 | s = (price, -qty) 206 | has_short = True 207 | longs.append(l) 208 | shorts.append(s) 209 | 210 | longs.pop(-1) 211 | shorts.pop(-1) 212 | if has_long: 213 | self.outputs.append({ 214 | 'title': 'Buy', 215 | 'series': [p for p,q in longs], 216 | 'type': 'order', 217 | 'mark': '^', 218 | 'width': 4, 219 | 'color': 'green', 220 | 'labels': [q for p,q in longs], 221 | }) 222 | if has_short: 223 | self.outputs.append({ 224 | 'title': 'Sell', 225 | 'series': [p for p,q in shorts], 226 | 'type': 'order', 227 | 'mark': 'v', 228 | 'width': 4, 229 | 'color': 'red', 230 | 'labels': [q for p,q in shorts], 231 | }) 232 | -------------------------------------------------------------------------------- /pine/vm/step.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import hashlib 4 | import random 5 | import time 6 | import datetime 7 | 8 | from .vm import VM 9 | from .helper import Series, BuiltinSeries 10 | 11 | class StepVM (VM): 12 | 13 | def __init__ (self, market, pine_code): 14 | super().__init__(market) 15 | self.code = pine_code 16 | self.ident = datetime.datetime.now().isoformat() + '@' +\ 17 | hashlib.sha1(str(random.random()).encode('utf-8')).hexdigest() 18 | 19 | def scan_market (self): 20 | # TODO 21 | for n in self.securties: 22 | n.evaluate(self) 23 | self.reset_context() 24 | return [] 25 | 26 | def set_ohlcv (self, ohlcv): 27 | self.market.set_ohlcv(ohlcv) 28 | self.reset_context() 29 | 30 | @property 31 | def clock (self): 32 | return int(self.timestamps[-1]) 33 | 34 | @property 35 | def next_clock (self): 36 | return int(self.timestamps[-1]) + self.market.resolution * 60 37 | 38 | def step_new (self): 39 | ## TODO SubVM 40 | 41 | # step registers 42 | for s in [v for v in self.registers.values() if isinstance(v, Series)]: 43 | if isinstance(s, BuiltinSeries): 44 | s.step(s.varfunc(self, 1)) 45 | else: 46 | s.step() 47 | self.timestamps.step(self.timestamps.varfunc(self, 1)) 48 | 49 | # set ip 50 | self.ip = self.size - 2 51 | 52 | # step and return actions 53 | self.step() 54 | return self.broker.next_actions 55 | -------------------------------------------------------------------------------- /pine/vm/vm.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from . import builtin_function 4 | from . import builtin_variable 5 | from ..base import PineError 6 | 7 | from .helper import Series, BuiltinSeries, bseries, NaN 8 | 9 | 10 | class AnnotationCollector (object): 11 | 12 | def __init__ (self): 13 | self.meta = None 14 | self.inputs = [] 15 | self.securities = [] 16 | self.strategies = [] 17 | self.plots = [] 18 | 19 | def register_meta (self, node): 20 | self.meta = node 21 | 22 | def register_input (self, node): 23 | if node not in self.inputs: 24 | self.inputs.append(node) 25 | 26 | def register_security (self, node): 27 | if node not in self.securities: 28 | self.securities.append(node) 29 | 30 | def register_strategy (self, node): 31 | if node not in self.strategies: 32 | self.strategies.append(node) 33 | 34 | def register_plot (self, node): 35 | if node not in self.plots: 36 | self.plots.append(node) 37 | 38 | def execute (self, node): 39 | node.collect_anotation(self) 40 | return (self.meta, self.inputs, self.securities, self.strategies, self.plots) 41 | 42 | 43 | class BaseVM (object): 44 | 45 | def __init__ (self, market=None): 46 | self.market = market 47 | self.node = None 48 | self.ident = '' 49 | self.meta = {} 50 | self.inputs = {} 51 | self.securities = [] 52 | self.strategies = [] 53 | self.plots = [] 54 | self.builtin_variable_cache = {} 55 | self.broker = None 56 | 57 | def reset_context (self): 58 | # setup registers 59 | self.registers = {} 60 | self.scoped_registers = [] 61 | # timestamps 62 | self.timestamps = bseries(self.market.timestamp(), builtin_variable.time) 63 | # reset VM's ip 64 | self.ip = 0 65 | 66 | @property 67 | def title (self): 68 | return self.meta.get('title', 'No title') 69 | @property 70 | def overlay (self): 71 | return self.meta.get('overlay', False) 72 | 73 | def set_broker (self, broker): 74 | self.broker = broker 75 | return broker 76 | 77 | @property 78 | def size (self): 79 | return self.market.size() 80 | 81 | def load_node (self, node): 82 | self.node = node 83 | 84 | meta, self.inputs, self.securties,\ 85 | self.strategies, self.plots = AnnotationCollector().execute(node) 86 | 87 | if meta: 88 | meta.evaluate(self) 89 | if self.broker: 90 | self.broker.setup(meta) 91 | 92 | self.reset_context() 93 | 94 | def push_register_scope (self): 95 | self.scoped_registers.append({}) 96 | def pop_register_scope (self): 97 | registers = self.scoped_registers.pop(-1) 98 | for n in registers.keys(): 99 | self.registers.pop(n) 100 | 101 | def get_register (self, node): 102 | return self.registers.get(node, None) 103 | 104 | def set_register (self, node, val): 105 | self.registers[node] = val 106 | if self.scoped_registers: 107 | self.scoped_registers[node] = val 108 | return val 109 | 110 | def alloc_register (self, node, v): 111 | if isinstance(v, Series): 112 | v = v.default_elem() 113 | if isinstance(v, float): 114 | v = [NaN] 115 | elif isinstance(v, int): 116 | v = [0] 117 | elif isinstance(v, bool): 118 | v = [False] 119 | else: 120 | raise PineError("invalid type for mutable variable: {0}: {1}".format(type(v), v)) 121 | val = Series(v * self.size) 122 | val.valid_index = -1 123 | return self.set_register(node, val) 124 | 125 | def set_register_value (self, node, val): 126 | dest = self.registers[node] 127 | dest[self.ip] = val 128 | dest.valid_index = self.ip 129 | return dest 130 | 131 | def dump_registers (self): 132 | for n, v in self.registers.items(): 133 | n.dump() 134 | if isinstance(v, Series): 135 | print("=====> {0}: {1}".format(v.valid_index, v)) 136 | else: 137 | print("=====> {}".format(v)) 138 | 139 | def step (self): 140 | self.node.evaluate(self) 141 | if self.broker: 142 | self.broker.step() 143 | self.ip += 1 144 | 145 | def is_last_step (self): 146 | return self.ip + 1 == self.size 147 | 148 | def run (self): 149 | #import time 150 | #t = time.time() 151 | while self.ip < self.size: 152 | self.step() 153 | #t_ = time.time() 154 | #print(t_ - t) 155 | #t = t_ 156 | 157 | def get_default_input_title (self, node): 158 | idx = self.inputs.index(node) 159 | return 'input{}'.format(idx + 1) 160 | 161 | 162 | class InputScanVM (BaseVM): 163 | 164 | def input (self, vm, args, kwargs, node): 165 | defval, title, typ,\ 166 | minval, maxval, confirm, step, options = builtin_function._parse_input_args(args, kwargs) 167 | 168 | if not title and node: 169 | title = vm.get_default_input_title(node) 170 | 171 | defval_ = defval 172 | if typ is None: 173 | t = type(defval) 174 | if t == bool: 175 | typ = 'bool' 176 | elif t == int: 177 | typ = 'integer' 178 | elif t == float: 179 | typ = 'float' 180 | elif isinstance(defval, BuiltinSeries): 181 | typ = 'source' 182 | defval_ = defval.varname 183 | if not options: 184 | options = tuple(builtin_variable.sources.keys()) 185 | else: 186 | typ = 'string' 187 | # symbol, resolution, session 188 | 189 | return { 190 | 'defval': defval_, 191 | 'title': title, 192 | 'type': typ, 193 | 'minval': minval, 194 | 'maxval': maxval, 195 | 'options': options, 196 | } 197 | 198 | def run (self): 199 | return [n.evaluate(self) for n in self.inputs] 200 | 201 | 202 | class VM (BaseVM): 203 | 204 | def __init__ (self, market=None): 205 | super().__init__(market) 206 | self.user_inputs = None 207 | 208 | def set_user_inputs (self, user_inputs): 209 | self.user_inputs = user_inputs 210 | 211 | def input (self, vm, args, kwargs, node): 212 | defval, title, typ,\ 213 | minval, maxval, _, step, options = builtin_function._parse_input_args(args, kwargs) 214 | 215 | if not self.user_inputs: 216 | return defval 217 | 218 | if not title: 219 | title = vm.get_default_input_title(node) 220 | val = self.user_inputs.get(title, None) 221 | 222 | # bool, integer, float, string, symbol, resolution, session, source 223 | if not typ: 224 | t = type(defval) 225 | if t == bool: 226 | typ = 'bool' 227 | elif t == int: 228 | typ = 'integer' 229 | elif t == float: 230 | typ = 'float' 231 | elif isinstance(defval, BuiltinSeries): 232 | typ = 'source' 233 | 234 | if typ == 'bool': 235 | val = bool(val) 236 | elif typ == 'integer': 237 | val = int(val) 238 | elif typ == 'float': 239 | val = float(val) 240 | elif typ == 'source': 241 | func = builtin_variable.sources[val] 242 | val = func(self) 243 | 244 | return val 245 | -------------------------------------------------------------------------------- /repl-app.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from flask import Flask 4 | from flask import render_template 5 | from flask import request 6 | 7 | app = Flask(__name__) 8 | app.config['TEMPLATES_AUTO_RELOAD'] = True 9 | 10 | @app.route('/') 11 | def landing (): 12 | return render_template('landing.html') 13 | 14 | import json 15 | from collections import OrderedDict 16 | with open('static/exchange-support.json') as f: 17 | exchanges = json.loads(f.read(), object_pairs_hook=OrderedDict) 18 | @app.route('/exchange-support') 19 | def exchange_support (): 20 | return render_template('exchange-support.html', exchanges=exchanges) 21 | 22 | from pine.base import PineError 23 | from pine.vm.vm import InputScanVM 24 | from pine.vm.plot import PlotVM 25 | from pine.vm.compile import compile_pine 26 | from pine.market.base import Market, MARKETS, resolution_to_str 27 | import pine.market.bitmex 28 | import pine.market.bitflyer 29 | from pine.broker.base import Broker 30 | 31 | def convert_to_form (spec): 32 | return spec 33 | 34 | import sys, traceback 35 | 36 | @app.route('/evaluate', methods=['POST']) 37 | def evaluate (): 38 | code = request.form['code'] 39 | try: 40 | node = compile_pine(code) 41 | # Exract input 42 | vm = InputScanVM(Market()) 43 | vm.load_node(node) 44 | inputs = vm.run() 45 | pine_title = vm.title 46 | forms = [convert_to_form(i) for i in inputs] 47 | symbols = [] 48 | for m,cls in MARKETS.items(): 49 | for t in cls.SYMBOLS: 50 | symbols.append(':'.join([m,t])) 51 | resolutions = [] 52 | for r in Market.RESOLUTIONS: 53 | resolutions.append((r, resolution_to_str(r))) 54 | return render_template('input_forms.html', title=pine_title, forms=forms, code=code, symbols=symbols, resolutions=resolutions) 55 | 56 | except PineError as e: 57 | return render_template('evaluate_error.html', error=str(e)) 58 | except Exception as e: 59 | tb = traceback.format_exception(*sys.exc_info()) 60 | return render_template('evaluate_error_exception.html', error=e, tb=tb) 61 | 62 | @app.route('/run', methods=['POST']) 63 | def run (): 64 | symbol = 'BITMEX:XBTUSD' 65 | code = '' 66 | resolution = None 67 | inputs = {} 68 | indicator_pane = 1 69 | for k in request.form: 70 | try: 71 | if k == 'code': 72 | code = request.form[k] 73 | elif k == 'symbol': 74 | symbol = request.form[k] 75 | elif k == 'resolution': 76 | resolution = request.form.get(k, type=int) 77 | else: 78 | inputs[k] = request.form[k] 79 | except Exception as e: 80 | print(e, k, request.form) 81 | raise 82 | try: 83 | # FIXME parse again 84 | node = compile_pine(code) 85 | 86 | # Run 87 | market, symbol_ = symbol.split(':') 88 | market = MARKETS[market](symbol_, resolution) 89 | vm = PlotVM(market) 90 | vm.load_node(node) 91 | vm.set_user_inputs(inputs) 92 | vm.set_broker(Broker()) 93 | vm.run() 94 | 95 | if vm.overlay: 96 | indicator_pane = 0 97 | 98 | html = _make_chart(market, vm.outputs, indicator_pane) 99 | return html 100 | except PineError as e: 101 | return render_template('evaluate_error.html', error=str(e)) 102 | except Exception as e: 103 | tb = traceback.format_exception(*sys.exc_info()) 104 | return render_template('evaluate_error_exception.html', error=e, tb=tb) 105 | 106 | 107 | import time, requests 108 | import pandas as pd 109 | from chart_creator import ChartCreator as cc 110 | 111 | import math 112 | def _make_non_na (timestamps, series, labels=None): 113 | ts = [] 114 | srs = [] 115 | lbls = [] 116 | if labels is None: 117 | labels_ = [None] * len(timestamps) 118 | else: 119 | labels_ = labels 120 | for t, v, l in zip(timestamps, series, labels_): 121 | if math.isnan(v): 122 | continue 123 | ts.append(t) 124 | srs.append(v) 125 | lbls.append(l) 126 | if labels: 127 | return (ts, srs, lbls) 128 | return (ts, srs) 129 | 130 | def _make_chart (market, plots, indicator_pane): 131 | file_path = "chart.html" 132 | 133 | # OHLCVデータ取得 134 | df = market.ohlcv_df() 135 | 136 | # チャート初期化 137 | cc.initialize() 138 | 139 | # メインチャート(ax:0)設定 140 | cc.add_subchart(ax=0, label="Price", grid=True) 141 | 142 | # ローソクバー設定(OHLCV) 143 | cc.set_ohlcv_df(df) 144 | 145 | if indicator_pane != 0: 146 | cc.add_subchart(ax=indicator_pane, grid=True) 147 | 148 | ts = df['unixtime'].values 149 | 150 | for plot in plots: 151 | title = plot['title'] 152 | series = plot['series'] 153 | 154 | typ = plot.get('type', 'line') 155 | color = plot.get('color', 'blue') 156 | width = plot.get('width', 1) 157 | 158 | if typ == 'line': 159 | t, s = _make_non_na(ts, series) 160 | if t: 161 | cc.set_line(t, s, 162 | ax=indicator_pane, color=color, width=width, name=title) 163 | elif typ == 'band': 164 | t, s = _make_non_na(ts, series) 165 | alpha = plot.get('alpha', 0.5) 166 | ymin = min(s) 167 | ymax = max(s) 168 | ymin = ymin - (ymax - ymin) * 0.5 169 | if t: 170 | cc.set_band(t, s, [ymin] * len(series), 171 | ax=indicator_pane, up_color=color, edge_width=width, alpha=alpha, name=title) 172 | elif typ == 'bar': 173 | t, s = _make_non_na(ts, series) 174 | if t: 175 | cc.set_bar(t, s, ax=indicator_pane, color=color, name=title) 176 | elif typ == 'hline': 177 | cc.set_line([ts[0], ts[-1]], [series, series], 178 | ax=indicator_pane, color=color, width=width, name=title) 179 | elif typ == 'marker': 180 | t, s = _make_non_na(ts, series) 181 | if t: 182 | cc.set_marker(t, s, 183 | ax=indicator_pane, color=color, size=width*10, mark=plot['mark'], name=title) 184 | elif typ == 'fill': 185 | series2 = plot['series2'] 186 | alpha = plot.get('alpha', 0.5) 187 | ts_ = ts 188 | if type(series) == float: 189 | ts_ = [ts[0], ts[-1]] 190 | series = [series, series] 191 | series2 = [series2, series2] 192 | cc.set_band(ts_, series2, series, 193 | ax=indicator_pane, up_color=color, alpha=alpha, name=title) 194 | elif typ == 'order': 195 | labels = plot['labels'] 196 | t, s, l = _make_non_na(ts, series, labels) 197 | if t: 198 | cc.set_marker(t, s, 199 | ax=0, color=color, size=width*10, mark=plot['mark'], name=title, text=l) 200 | 201 | 202 | # SMA計算 203 | #sma = df["close"].rolling(window=14, min_periods=1).mean() 204 | 205 | # メインチャートにSMA設定 206 | #cc.set_line(df["unixtime"].values, sma, ax=0, color="blue", width=1.0, name="SMA") 207 | 208 | # MACD計算 209 | #ema12 = df["close"].ewm(span=12).mean() 210 | #ema26 = df["close"].ewm(span=26).mean() 211 | #macd = ema12 - ema26 212 | #signal = macd.ewm(span=9).mean() 213 | #hist = macd - signal 214 | 215 | # MACDサブチャート(ax:1)追加 216 | #cc.add_subchart(ax=1, label="MACD", grid=True) 217 | 218 | # MACD設定 219 | #cc.set_line(df["unixtime"].values, macd, ax=1, color="red", width=1.0, name="MACD") 220 | #cc.set_line(df["unixtime"].values, signal, ax=1, color="cyan", width=1.0, name="Signal") 221 | #cc.set_bar(df["unixtime"].values, hist, ax=1, color="orange", name="Hist") 222 | 223 | # チャート生成 224 | return cc.create_chart(file_path, chart_mode="html") 225 | 226 | 227 | if __name__ == '__main__': 228 | import os 229 | port = int(os.environ.get('PORT', 5000)) 230 | app.run(port=port) 231 | -------------------------------------------------------------------------------- /templates/evaluate_error.html: -------------------------------------------------------------------------------- 1 |
2 |

Fail to evaluate

3 |

{{error}}

4 | -------------------------------------------------------------------------------- /templates/evaluate_error_exception.html: -------------------------------------------------------------------------------- 1 |
2 |

Fail to evaluate

3 |

{{error}}

4 |
{{ ''.join(tb) }}
5 | -------------------------------------------------------------------------------- /templates/exchange-support.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exchange support list 5 | 6 | 7 | 8 |

Exchange suport list

9 | {% for name, xchg in exchanges.items() %} 10 |

{{ name }}

11 |
12 |
name:
13 |
{{ xchg.name }}
14 |
ids:
15 |
{{ xchg.ids }}
16 |
cryptowatch:
17 |
{{ xchg.cryptowatch }}
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for name, market in xchg.markets.items() %} 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
nameidscryptowatch?resolutions
{{ name }}{{ market.ids }}{{ market.cryptowatch }}{{ market.resolutions }}
39 | {% endfor %} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/input_forms.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }} inputs

3 |
4 | 5 | 6 | 11 | 12 | 17 | 18 | {% for form in forms %} 19 |
20 | 21 | {% if form.type == 'integer' or form.type == 'float' %} 22 | 24 | {% elif form.type == 'source' %} 25 | 30 | {% elif form.type == 'string' %} 31 | {% if form.options %} 32 | 37 | {% else %} 38 | 39 | {% endif %} 40 | {% elif form.type == 'bool' %} 41 | 43 | {% endif %} 44 | {% endfor %} 45 |
46 | 47 |
48 |
49 | -------------------------------------------------------------------------------- /templates/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pine REPL 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Pine REPL

12 | Supported exchanges and markets 13 |
14 |
15 | 17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | 47 | 48 | 49 | --------------------------------------------------------------------------------