├── lib ├── __init__.py ├── __pycache__ │ └── pretty_printer.cpython-38.pyc ├── symphony_object.py ├── edn_syntax.py ├── manual_testing.py ├── linter.py ├── quantconnect.py ├── symphony_backtest.py ├── get_backtest_data.py ├── transpilers.py ├── logic.py ├── human.py ├── vectorbt.py └── traversers.py ├── desiredOutputs └── humans ├── requirements.txt ├── .gitignore ├── dev └── main ├── inputs ├── bulk_symphonies.txt ├── jamestest.edn ├── README.md ├── simple.edn ├── bad_inputs │ ├── symphony_not_root_level.edn │ ├── missing_double_quotes.edn │ └── no_root_symphony_protected_leverage.json ├── copy_simple_500.json ├── inputFile.edn ├── tqqq_long_term.edn ├── betaballer-modified.edn ├── weird.edn └── protected_leverage3x.json ├── README.md ├── LICENSE └── symphony_parser.py /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /desiredOutputs/humans: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | edn_format 2 | requests 3 | yfinance 4 | pandas-ta 5 | vectorbt 6 | quantstats 7 | pytz 8 | revChatGPT -------------------------------------------------------------------------------- /lib/__pycache__/pretty_printer.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androslee/compose_symphony_parser/HEAD/lib/__pycache__/pretty_printer.cpython-38.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs/VSWorkspaceState.json 6 | __pycache__ 7 | data -------------------------------------------------------------------------------- /dev/main: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: ./dev/main lib/human.py 4 | # - runs lib.human's main() function (so you don't have to use the official entry points; useful for lib development) 5 | # - uses nodemon (`npm install -g nodemon`) as filewatcher, so re-runs when you save 6 | # - passes through any extra args, in case you're developing an argparse script 7 | 8 | module=`echo $1 | sed "s@/@.@g" | sed "s@.py@@g"` 9 | shift 1 10 | echo $module 11 | 12 | nodemon -e py -x "python3 -c 'from $module import main;main()' $@" 13 | -------------------------------------------------------------------------------- /inputs/bulk_symphonies.txt: -------------------------------------------------------------------------------- 1 | https://app.composer.trade/symphony/WDQzBV4Mse7Zypxk4LtC/details 2 | https://app.composer.trade/symphony/2UtDB182da9ERa0WNQqr/details 3 | https://app.composer.trade/symphony/8uxg3BhJjDtxN2q1sFod/details%20(this%20does%20better%20than%20QLD%20for%20the%20long%20run%20because%20of%20the%20UVXY%20for%20when%20things%20get%20really%20dangerous) 4 | https://app.composer.trade/symphony/9TkOh583s7kFqD3T1ufP/details 5 | https://app.composer.trade/symphony/ENIv7HRFOYK5q7CW91NX/details 6 | https://app.composer.trade/symphony/8uxg3BhJjDtxN2q1sFod/details 7 | -------------------------------------------------------------------------------- /lib/symphony_object.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import requests 4 | import edn_format 5 | 6 | from . import edn_syntax 7 | 8 | 9 | def get_symphony(symphony_id: str) -> dict: 10 | composerConfig = { 11 | "projectId": "leverheads-278521", 12 | "databaseName": "(default)" 13 | } 14 | print(f"Fetching symphony {symphony_id} from Composer") 15 | response = requests.get( 16 | f'https://firestore.googleapis.com/v1/projects/{composerConfig["projectId"]}/databases/{composerConfig["databaseName"]}/documents/symphony/{symphony_id}') 17 | response.raise_for_status() 18 | 19 | response_json = response.json() 20 | return response_json 21 | 22 | 23 | def extract_root_node_from_symphony_response(response: dict) -> dict: 24 | return typing.cast(dict, edn_syntax.convert_edn_to_pythonic( 25 | edn_format.loads(response['fields']['latest_version_edn']['stringValue']))) 26 | -------------------------------------------------------------------------------- /lib/edn_syntax.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | import edn_format 3 | 4 | from . import manual_testing 5 | 6 | 7 | def convert_edn_to_immutable_value(d): 8 | if type(d) == edn_format.immutable_dict.ImmutableDict: 9 | return tuple([(convert_edn_to_immutable_value(k), convert_edn_to_immutable_value(v)) 10 | for k, v in d.items()]) 11 | else: 12 | return convert_edn_to_pythonic(d) 13 | 14 | 15 | def convert_edn_to_pythonic(d): 16 | if type(d) == edn_format.immutable_dict.ImmutableDict: 17 | return {convert_edn_to_immutable_value(k): convert_edn_to_pythonic(v) for k, v in d.items()} 18 | elif type(d) == edn_format.immutable_list.ImmutableList: 19 | return [convert_edn_to_pythonic(v) for v in d] 20 | elif type(d) == edn_format.edn_lex.Keyword: 21 | return ":" + d.name 22 | else: 23 | return d 24 | 25 | 26 | def main(): 27 | pprint.pprint(manual_testing.get_root_node_from_path("inputs/weird.edn")) 28 | -------------------------------------------------------------------------------- /lib/manual_testing.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import typing 3 | import edn_format 4 | import json 5 | from . import edn_syntax 6 | 7 | 8 | def get_root_node_from_path(path: str): 9 | try: 10 | # Data is wrapped with "" and has escaped all the "s, this de-escapes 11 | data_with_wrapping_string_removed = json.load( 12 | open(path, 'r')) 13 | root_data_immutable = edn_format.loads( 14 | data_with_wrapping_string_removed) 15 | root_data = typing.cast( 16 | dict, edn_syntax.convert_edn_to_pythonic(root_data_immutable)) 17 | root_node = root_data[":symphony"] 18 | except: 19 | root_data_immutable = edn_format.loads(open(path, 'r').read()) 20 | root_node = typing.cast( 21 | dict, edn_syntax.convert_edn_to_pythonic(root_data_immutable)) 22 | 23 | return root_node 24 | 25 | 26 | def debug_print_node(node): 27 | new_node = copy.copy(node) 28 | del new_node[":children"] 29 | del new_node[":step"] 30 | print(json.dumps(new_node, indent=4, sort_keys=True)) 31 | -------------------------------------------------------------------------------- /inputs/jamestest.edn: -------------------------------------------------------------------------------- 1 | {:id "Gw5yazg6hrL5h9zvI9O1", :step :root, :name "Testaroo", :description "", :rebalance :daily, :children [{:id "fc9ee517-81bb-4f65-9fb3-08464e374e07", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id "dbe8e37e-2be3-4ff4-8360-6d139747ed2c", :step :group, :name "Asdf", :children [{:step :wt-cash-specified, :id "467026e7-d564-4096-a044-b3c1ac133ca0", :children [{:name "Alphabet Inc. Class C", :ticker "GOOG", :has_marketcap true, :weight {:num "01", :den "2"}, :id "53a5ab10-37be-4105-9803-77e2e30845f7", :exchange "XNAS", :price 100.29, :step :asset, :dollar_volume 2157861603.51} {:select? true, :children [{:name "Direxion Daily S&P 500 Bull 3x Shares", :ticker "SPXL", :has_marketcap false, :id "7d552f49-8e74-44d4-82a5-16bcc08011f8", :exchange "ARCX", :price 57.83, :step :asset, :dollar_volume 945033744.89} {:name "Energy Select Sector SPDR Fund", :ticker "XLE", :has_marketcap false, :id "fa6abbf6-12d9-4b86-926c-603755313259", :exchange "ARCX", :price 84.41, :step :asset, :dollar_volume 2623848131.65}], :select-fn :top, :select-n "1", :sort-by-fn :current-price, :sort-by-window-days 21, :weight {:num "12", :den 100}, :id "a6775e89-089a-4c9e-a31d-edf4175f6ce1", :sort-by? true, :step :filter} {:name "Apple Inc.", :ticker "AAPL", :has_marketcap true, :weight {:num "6", :den 100}, :id "1d998246-c9a7-43f1-ae11-c8be45b09559", :exchange "XNAS", :price 143.86, :step :asset, :dollar_volume 8851233507.62}]}]}]}]} -------------------------------------------------------------------------------- /lib/linter.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from . import traversers, manual_testing 3 | 4 | 5 | # 6 | # Idea: take root_node and print out all issues can check for 7 | # - troublesome tickers / low-volume 8 | # - tickers with little data 9 | # - patterns/indicators we find are flawed 10 | # - TODO: think of additional common issues we might scan for 11 | # 12 | 13 | 14 | def log_warnings_for_dangerous_tickers(root_node): 15 | allocateable_assets = traversers.collect_allocateable_assets(root_node) 16 | print("Possible assets to allocate toward:", allocateable_assets) 17 | problematic_assets = { # TODO: look entries up (I have this in the ETF DB) 18 | "UGE": "Volume is too low", 19 | # TODO: remove this: 20 | "VIXY": "(Fake error for testing this code, ignore)", 21 | } 22 | for asset in allocateable_assets: 23 | if asset in problematic_assets: 24 | print("WARNING", asset, problematic_assets[asset]) 25 | 26 | 27 | def log_earliest_backtest_date(root_node): 28 | all_referenced_assets = traversers.collect_referenced_assets(root_node) 29 | print("All assets referenced:", all_referenced_assets) 30 | latest_founded_asset = max(all_referenced_assets, key=get_founded_date) 31 | latest_founded_date = get_founded_date(latest_founded_asset) 32 | print( 33 | f"Earliest backtest date is {latest_founded_date} (when {latest_founded_asset} was founded)") 34 | 35 | 36 | def get_founded_date(ticker: str) -> datetime.date: 37 | print("TODO: implement actual founded_date lookup") 38 | return datetime.date(2012, 1, 1) 39 | 40 | 41 | def main(): 42 | # path = 'inputs/inputFile.edn' 43 | # path = 'inputs/jamestest.edn' 44 | path = 'inputs/tqqq_long_term.edn' 45 | root_node = manual_testing.get_root_node_from_path(path) 46 | 47 | log_warnings_for_dangerous_tickers(root_node) 48 | print() 49 | log_earliest_backtest_date(root_node) 50 | print() 51 | -------------------------------------------------------------------------------- /lib/quantconnect.py: -------------------------------------------------------------------------------- 1 | import io 2 | import typing 3 | 4 | from . import logic, manual_testing 5 | 6 | from revChatGPT.revChatGPT import Chatbot 7 | 8 | # Credentials for: https://chat.openai.com/chat 9 | config = { 10 | "email": "YOUR-EMAIL", 11 | "password": "YOUR-PASSWORD" 12 | } 13 | 14 | def main(): 15 | path = 'inputs/tqqq_long_term.edn' 16 | root_node = manual_testing.get_root_node_from_path(path) 17 | print(output_strategy(root_node)) 18 | 19 | 20 | def output_strategy(text) -> str: 21 | # Initialize the Chatbot 22 | chatbot = Chatbot(config, conversation_id=None) 23 | # Generate the prompt to ask ChatGPT for the code 24 | prompt = f"""Could you convert the following trading logic into a C# QuantConnect strategy? When finished please say "THE END". 25 | 26 | ``` 27 | {text} 28 | ``` 29 | """ 30 | 31 | # Wait on ChatGPT 32 | print('---------------------------------------------------------------') 33 | print('Asking ChatGPT for the QuantConnect strategy...') 34 | print('---------------------------------------------------------------') 35 | print('PROMPT: ') 36 | print(prompt) 37 | print('---------------------------------------------------------------') 38 | 39 | response = chatbot.get_chat_response(prompt, output="text") 40 | message = response['message'] 41 | 42 | if 'THE END' in message: 43 | print('[*] Received "THE END", returning output.') 44 | return message 45 | else: 46 | while True: 47 | print('[*] Asking ChatGPT for more (code was truncated)...') 48 | more = chatbot.get_chat_response("continue", output="text") 49 | 50 | if 'THE END' in more['message']: 51 | print('[*] Received "THE END", concatinating output.') 52 | message += more['message'] 53 | return message 54 | 55 | print('[*] Still waiting on ChatGPT...') 56 | message += more['message'] 57 | -------------------------------------------------------------------------------- /inputs/README.md: -------------------------------------------------------------------------------- 1 | # Symphony Rules Notes 2 | 3 | Expressed in EDN (a subset of Clojure syntax), not too different from JSON. Examples below have been translated to JSON. 4 | 5 | ## Root 6 | 7 | Data available (other than :symphony): 8 | 9 | Depending on how you fetch, this data will not be available and will skip straight to the root node. 10 | 11 | ```json 12 | { 13 | ":uid": "sdfadfddasf", // text 14 | ":capital": 10000, 15 | ":apply-taf-fee?": true, // ? 16 | ":symphony-benchmarks": [], 17 | ":slippage-percent": 0.0005, // this is 5 basis points 18 | ":client-commit": "bvcxdfff", // ? 19 | ":apply-reg-fee?": true, // ? 20 | ":ticker-benchmarks": [ 21 | // benchmarks section for comparison 22 | { 23 | ":color": "#F6609F", 24 | ":id": "SPY", 25 | ":checked?": true, 26 | ":type": ":ticker", 27 | ":ticker": "SPY" 28 | } 29 | ] 30 | } 31 | ``` 32 | 33 | ## Tree 34 | 35 | Inside `":symphony"` is a tree. The top layer is the root node. 36 | 37 | - descendants are under `":children"`, this is a list of nodes. 38 | - the current node's type is `":step"`. 39 | - the node's unique identifier is the `":id"` field. Unclear if this changes. 40 | 41 | ## Node Types 42 | 43 | `:root`: the first node under `:symphony`. 44 | 45 | ```json 46 | { 47 | // these first 3 are common to all nodes (:children is optional, left blank for brevity) 48 | // will not be repeated for other examples in this doc. 49 | ":id": "43refdscxz", 50 | ":step": ":root", 51 | ":children": [], 52 | 53 | ":name": "farm fun 99", 54 | ":description": "", 55 | ":rebalance": ":daily" // other values currently unknown 56 | } 57 | ``` 58 | 59 | `:wt-cash-equal` and `:wt-cash-specified`: weight all children equally. Children will have :weight attribute, which has :num and :den (numberator and denominator). 60 | 61 | `:if`: all children are of type `:if-child`. 62 | 63 | `:if-child`: if condition is met, 100% of weight passes to children (not other sibling nodes). 64 | 65 | May be an `else` block, which only triggers if all other sibling `:if-child` conditions are false. 66 | 67 | A **condition** is made of 2 **values** (which may be _fixed_ or an _indicator_) and a **comparison** (like `<`). 68 | One **value** is called the left-hand-side value (lhs for shot), the other is the right-hand-side (rhs). The logic for these differs slightly. Only the right-hand-side can be a fixed value. 69 | -------------------------------------------------------------------------------- /inputs/simple.edn: -------------------------------------------------------------------------------- 1 | "{:end-date-in-epoch-days 19283, :uid \"TyDCPJATawgZC6gDzGQCngUGRLj1\", :start-date-in-epoch-days 11979, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :client-commit \"asdfasdf\", :apply-reg-fee? true, :symphony {:id \"U917ZWjlPahB5eyIrvxh\", :step :root, :name \"Copy of Simple S&P 500\", :description \"\", :rebalance :daily, :children [{:id \"5ad3ed86-c6c3-4a67-b5ce-93b7267707cc\", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id \"f7dd16cf-dd91-4941-b4ce-52519640cffa\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"afac18d7-6f1a-4ecf-9850-db73eadeaf36\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :moving-average-price, :rhs-window-days \"210\", :lhs-window-days \"21\", :lhs-val \"SPY\", :id \"75e77451-1785-425f-b3df-656ba25ae8e3\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"363f974e-aecd-41f0-b3c9-16eecda9aa6b\", :step :if-child, :is-else-condition? true, :children [{:id \"81cd62de-7e54-4a80-877d-7f0d2deabd4e\", :step :wt-cash-equal, :children [{:id \"d702b070-bb13-468e-96cf-ab879a18a8df\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"b0faa017-7dce-410b-8300-d4e676bbf473\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :rhs-window-days \"31\", :lhs-window-days \"10\", :lhs-val \"QQQ\", :id \"ee007eb2-fc86-469a-8863-c2e605ee1095\", :comparator :lt, :rhs-val \"30\", :step :if-child} {:id \"5f22f055-5056-4fda-8211-28511e5b7f06\", :step :if-child, :is-else-condition? true, :children [{:id \"47966da7-3eef-4d06-8145-8ebe9c9ec3aa\", :step :wt-cash-equal, :children [{:id \"9ae935c2-484f-4b77-b037-3a88557138a6\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"b7eaf395-cb36-450c-be91-c09208ea6e6c\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"31\", :lhs-val \"SPY\", :id \"fdf03453-87cc-4d12-afdb-300da32bd3f3\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"1eed8ea9-966a-42aa-b93b-3920dc104ba3\", :step :if-child, :is-else-condition? true, :children [{:name \"iShares 1-3 Year Treasury Bond ETF\", :ticker \"SHY\", :has_marketcap false, :id \"25769a69-6625-446f-a3f5-8e9e40eee010\", :exchange \"XNAS\", :price 81.04, :step :asset, :dollar_volume 310618783.28000003}]}]}]}]}]}]}]}]}]}]}, :ticker-benchmarks [{:type :ticker, :id \"SPY\", :color \"#F6609F\", :checked? true, :ticker \"SPY\"} {:type :ticker, :id \"QQQ\", :checked? true, :color \"#FFBB38\", :ticker \"QQQ\"}]}" -------------------------------------------------------------------------------- /inputs/bad_inputs/symphony_not_root_level.edn: -------------------------------------------------------------------------------- 1 | "{:end-date-in-epoch-days 19283, :uid \"TyDCPJATawgZC6gDzGQCngUGRLj1\", :start-date-in-epoch-days 11979, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :client-commit \"asdfasdf\", :apply-reg-fee? true, :symphony {:id \"U917ZWjlPahB5eyIrvxh\", :step :root, :name \"Copy of Simple S&P 500\", :description \"\", :rebalance :daily, :children [{:id \"5ad3ed86-c6c3-4a67-b5ce-93b7267707cc\", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id \"f7dd16cf-dd91-4941-b4ce-52519640cffa\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"afac18d7-6f1a-4ecf-9850-db73eadeaf36\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :moving-average-price, :rhs-window-days \"210\", :lhs-window-days \"21\", :lhs-val \"SPY\", :id \"75e77451-1785-425f-b3df-656ba25ae8e3\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"363f974e-aecd-41f0-b3c9-16eecda9aa6b\", :step :if-child, :is-else-condition? true, :children [{:id \"81cd62de-7e54-4a80-877d-7f0d2deabd4e\", :step :wt-cash-equal, :children [{:id \"d702b070-bb13-468e-96cf-ab879a18a8df\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"b0faa017-7dce-410b-8300-d4e676bbf473\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :rhs-window-days \"31\", :lhs-window-days \"10\", :lhs-val \"QQQ\", :id \"ee007eb2-fc86-469a-8863-c2e605ee1095\", :comparator :lt, :rhs-val \"30\", :step :if-child} {:id \"5f22f055-5056-4fda-8211-28511e5b7f06\", :step :if-child, :is-else-condition? true, :children [{:id \"47966da7-3eef-4d06-8145-8ebe9c9ec3aa\", :step :wt-cash-equal, :children [{:id \"9ae935c2-484f-4b77-b037-3a88557138a6\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"b7eaf395-cb36-450c-be91-c09208ea6e6c\", :exchange \"XNAS\", :price 271.48, :step :asset, :dollar_volume 18531670298.68, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"31\", :lhs-val \"SPY\", :id \"fdf03453-87cc-4d12-afdb-300da32bd3f3\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"1eed8ea9-966a-42aa-b93b-3920dc104ba3\", :step :if-child, :is-else-condition? true, :children [{:name \"iShares 1-3 Year Treasury Bond ETF\", :ticker \"SHY\", :has_marketcap false, :id \"25769a69-6625-446f-a3f5-8e9e40eee010\", :exchange \"XNAS\", :price 81.04, :step :asset, :dollar_volume 310618783.28000003}]}]}]}]}]}]}]}]}]}]}, :ticker-benchmarks [{:type :ticker, :id \"SPY\", :color \"#F6609F\", :checked? true, :ticker \"SPY\"} {:type :ticker, :id \"QQQ\", :checked? true, :color \"#FFBB38\", :ticker \"QQQ\"}]}" -------------------------------------------------------------------------------- /inputs/copy_simple_500.json: -------------------------------------------------------------------------------- 1 | "{:uid \"TyDCPJATawgZC6gDzGQCngUGRLj1\", :start-date-in-epoch-days 16000, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :client-commit \"2be0aa38d0a616314bf15086b3d19bd6ce5f64ce\", :apply-reg-fee? true, :symphony {:id \"PdgUAAy4GmEQvGyKsYZt\", :step :root, :name \"Copy of Simple S&P 500\", :description \"\", :rebalance :daily, :children [{:id \"0f373b89-8705-4974-a648-457777ad4893\", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id \"ed43d967-9c83-47fc-8dc0-049e90c69647\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"2d43d557-2a91-4acb-aea3-94283ec6ea01\", :exchange \"XNAS\", :price 262.75, :step :asset, :dollar_volume 17028744996.5, :children-count 0}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :moving-average-price, :rhs-window-days \"210\", :lhs-window-days \"21\", :lhs-val \"SPY\", :id \"a04140f7-005b-4d9e-8ec1-b7c261913cb5\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"22da1d86-a988-4039-abfe-a53007f189f4\", :step :if-child, :is-else-condition? true, :children [{:id \"99681966-ea36-4c7b-ba7a-4b6bf54adc84\", :step :wt-cash-equal, :children [{:id \"5c9550d3-8234-4de4-9427-75d7132e5f6b\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"23d90500-957a-405c-9bd8-e6c5f430fb47\", :exchange \"XNAS\", :price 262.75, :step :asset, :dollar_volume 17028744996.5}], :rhs-fn :moving-average-price, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :rhs-window-days \"31\", :lhs-window-days \"10\", :lhs-val \"QQQ\", :id \"e3188fc6-d59b-49a4-8e34-d2e88f25929f\", :comparator :lt, :rhs-val \"30\", :step :if-child} {:id \"ba10d046-2e7f-4f3e-be07-3a0b4c5e2e76\", :step :if-child, :is-else-condition? true, :children [{:id \"2c703187-0a00-4683-a9ee-3e1a77bac766\", :step :wt-cash-equal, :children [{:id \"ae565be6-8b44-4f3b-bab2-64eafb1539e2\", :step :if, :children [{:children [{:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"8ec90e55-f650-4356-9478-6a2ca7ed6164\", :exchange \"XNAS\", :price 262.75, :step :asset, :dollar_volume 17028744996.5}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"31\", :lhs-val \"SPY\", :id \"3b1f2174-d825-48dd-88f1-66db3314a23a\", :comparator :gt, :rhs-val \"SPY\", :step :if-child} {:id \"085d1640-8557-405d-943a-4b38bb8ec3e0\", :step :if-child, :is-else-condition? true, :children [{:name \"iShares 1-3 Year Treasury Bond ETF\", :ticker \"SHY\", :has_marketcap false, :id \"68e8c8c7-9b0d-406e-9cec-2c4da68764e0\", :exchange \"XNAS\", :price 81.04, :step :asset, :dollar_volume 310618783.28000003}]}]}]}]}]}]}]}]}]}]}, :ticker-benchmarks [{:color \"#F6609F\", :id \"SPY\", :checked? true, :type :ticker, :ticker \"SPY\"} {:type :ticker, :color \"#FFBB38\", :checked? true, :id \"QQQ\", :ticker \"QQQ\"} {:checked? true, :type :ticker, :id \"TQQQ\", :color \"#FC5100\", :ticker \"TQQQ\"} {:type :ticker, :id \"QLD\", :checked? true, :color \"#BA84FF\", :ticker \"QLD\"}]}" 2 | -------------------------------------------------------------------------------- /lib/symphony_backtest.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | import typing 4 | 5 | import requests 6 | import edn_format 7 | import pandas as pd 8 | import pytz 9 | 10 | from . import edn_syntax 11 | 12 | 13 | UTC_TIMEZONE = pytz.UTC 14 | 15 | 16 | def epoch_days_to_date(days: int) -> datetime.date: 17 | # if your offset is negative, this will fix the off-by-one error 18 | # if your offset is positive, you'll never know this problem even exists 19 | return datetime.datetime.fromtimestamp(days * 24 * 60 * 60, tz=UTC_TIMEZONE).date() 20 | 21 | 22 | def date_to_epoch_days(day: datetime.date) -> int: 23 | return int(datetime.datetime.combine(day, datetime.time( 24 | 0, 0), tzinfo=UTC_TIMEZONE).timestamp() / 60 / 60 / 24) 25 | 26 | 27 | assert epoch_days_to_date(19289) == datetime.date(2022, 10, 24) 28 | 29 | 30 | def get_composer_backtest_results(symphony_id: str, start_date: datetime.date, end_date: typing.Union[datetime.date,None]=None) -> dict: 31 | start_epoch_days = date_to_epoch_days(start_date) 32 | utc_today = datetime.datetime.now().astimezone(UTC_TIMEZONE).date() 33 | payload = "{:uid nil, :start-date-in-epoch-days START_DATE_EPOCH_DAYS, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :apply-reg-fee? true, :symphony \"SYMPHONY_ID_GOES_HERE\", :ticker-benchmarks []}" 34 | if end_date: 35 | end_epoch_days = date_to_epoch_days(end_date) 36 | payload = "{:uid nil, :start-date-in-epoch-days START_DATE_EPOCH_DAYS, :end-date-in-epoch-days END_DATE_EPOCH_DAYS, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :apply-reg-fee? true, :symphony \"SYMPHONY_ID_GOES_HERE\", :ticker-benchmarks []}" 37 | payload = payload.replace("END_DATE_EPOCH_DAYS", str(end_epoch_days)) 38 | payload = payload.replace("SYMPHONY_ID_GOES_HERE", symphony_id) 39 | payload = payload.replace("START_DATE_EPOCH_DAYS", str(start_epoch_days)) 40 | 41 | 42 | print( 43 | f"Fetching backtest results for {symphony_id} from {start_date} to {end_date if end_date else utc_today}...") 44 | 45 | tries_remaining = 3 46 | response = None 47 | while tries_remaining: 48 | try: 49 | response = requests.post( 50 | "https://backtest.composer.trade/v2/backtest", 51 | json=payload) 52 | response.raise_for_status() 53 | break 54 | except requests.HTTPError as e: 55 | time.sleep(3) 56 | print("Error when submitting backtest:", e) 57 | tries_remaining -= 1 58 | 59 | if not response: 60 | raise Exception("Failed to submit backtest after retries") 61 | 62 | backtest_result = edn_syntax.convert_edn_to_pythonic( 63 | edn_format.loads(response.text)) 64 | 65 | return typing.cast(dict, backtest_result) 66 | 67 | 68 | def extract_allocations_from_composer_backtest_result(backtest_result: dict) -> pd.DataFrame: 69 | composer_allocations = pd.DataFrame( 70 | backtest_result[':tdvm-weights']).fillna(0).round(4) 71 | composer_allocations.index = pd.DatetimeIndex( 72 | [epoch_days_to_date(i) for i in composer_allocations.index]) 73 | composer_allocations.sort_index(inplace=True) 74 | return composer_allocations.round(4) 75 | 76 | 77 | def extract_returns_from_composer_backtest_result(backtest_result: dict, symphony_id: str) -> pd.Series: 78 | dvm_capital = backtest_result[':dvm-capital'][symphony_id] 79 | returns_by_day = sorted([ 80 | (pd.to_datetime(epoch_days_to_date(k)), v) for k, v in dvm_capital.items() 81 | ]) 82 | returns = pd.Series([row[1] for row in returns_by_day], index=[ 83 | row[0] for row in returns_by_day]).pct_change().dropna() 84 | return returns 85 | -------------------------------------------------------------------------------- /inputs/inputFile.edn: -------------------------------------------------------------------------------- 1 | "{:uid \"sdfadfddasf\", :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [], :slippage-percent 0.0005, :client-commit \"bvcxdfff\", :apply-reg-fee? true, :symphony {:id \"43refdscxz\", :step :root, :name \"farm fun 99\", :description \"\", :rebalance :daily, :children [{:id \"a6cea29d-15a3-48d6-900a-e8570fdef804\", :step :wt-cash-equal, :children [{:id \"d4ece5ac-9a5f-4f50-86ee-f2ca82a5b31e\", :step :if, :children [{:children [{:id \"4fcf1c2a-e747-413f-b3b3-75c6004c791b\", :step :wt-cash-equal, :children [{:id \"4a465370-bd84-493a-b4e1-cedc27e00cae\", :step :if, :children [{:children [{:id \"c7303d3f-3a51-422f-ac91-9b349ab798a0\", :step :group, :name \"Overbought S&P. Sell the rip. Buy volatility\", :children [{:step :wt-cash-equal, :id \"5b7f5cdf-4611-4aa2-b277-e03b1fc87a70\", :children [{:select? true, :children [{:name \"ProShares Ultra VIX Short-Term Futures ETF\", :ticker \"UVXY\", :has_marketcap false, :id \"c82bef09-9dd6-498f-aa54-b1920bd52fed\", :exchange \"BATS\", :price 11.45, :step :asset, :dollar_volume 453424499.84999996} {:name \"ProShares VIX Short-Term Futures ETF\", :ticker \"VIXY\", :has_marketcap false, :id \"57f4b706-3cca-4e06-895e-7f435bae9c3a\", :exchange \"BATS\", :price 15.84, :step :asset, :dollar_volume 149119153.92}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"13\", :weight {:num \"80\", :den 100}, :id \"d74aeaea-5b9d-44b5-a0eb-61c60d9532e8\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"7\", :lhs-val \"SPY\", :id \"0cb24ab6-f9b2-4a6a-9a14-9a87d9839400\", :comparator :gt, :rhs-val \"76\", :step :if-child} {:id \"9d8b558a-0a21-48e4-952c-b1014ba700cd\", :step :if-child, :is-else-condition? true, :children [{:id \"3698f968-4859-45db-8a2d-83d332e41141\", :step :wt-cash-equal, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"3bd5e2c3-ef00-463d-8eb5-f88fb765460d\", :exchange \"ARCX\", :price 11.19, :step :asset, :dollar_volume 1275587444.04}]}]}]}]}], :rhs-fn :relative-strength-index, :is-else-condition? false, :rhs-fixed-value? false, :lhs-fn :relative-strength-index, :rhs-window-days \"5\", :lhs-window-days \"5\", :lhs-val \"BIL\", :id \"9da13422-9cb9-4b01-a3be-64edff9d95e3\", :comparator :lt, :rhs-val \"BND\", :step :if-child} {:id \"03f583d0-5608-4618-ba12-c3dff9db75fb\", :step :if-child, :is-else-condition? true, :children [{:id \"7902f025-3e45-4d12-9722-3a587b006e5d\", :step :wt-cash-equal, :children [{:id \"91c6e86e-cafd-4547-b371-b0adc0647062\", :step :if, :children [{:children [{:id \"1a18804f-596c-4979-b968-59862f10eb57\", :step :group, :name \"Extremely oversold S&P (low RSI). Double check with bond mkt before going long\", :children [{:step :wt-cash-equal, :id \"0edc12db-a440-4edb-8f23-84d4979434ac\", :children [{:id \"4e49579c-4407-4ae7-ae74-8eeb1f86d536\", :step :if, :children [{:children [{:name \"Direxion Daily Semiconductor Bear 3x Shares\", :ticker \"SOXS\", :has_marketcap false, :id \"c4f5b6e2-912b-4e6b-8f7c-6a265a1c0799\", :exchange \"ARCX\", :price 53.72, :step :asset, :dollar_volume 871353441.6}], :rhs-fn :relative-strength-index, :is-else-condition? false, :lhs-fn :relative-strength-index, :rhs-window-days \"10\", :lhs-window-days \"10\", :lhs-val \"SHY\", :id \"6fcc22ec-8814-4be5-9cab-afd724c57096\", :comparator :lte, :rhs-val \"HIBL\", :step :if-child} {:id \"93d72d2b-b677-419e-a1f3-d30820a1691c\", :step :if-child, :is-else-condition? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"ef524e04-ef23-473d-a402-bfc070088725\", :exchange \"ARCX\", :price 11.19, :step :asset, :dollar_volume 1275587444.04, :children-count 0}]}]}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"7\", :lhs-val \"SPY\", :id \"3e9c24d0-5a75-47c5-ae3a-59aade04e5d4\", :comparator :lt, :rhs-val \"27\", :step :if-child} {:id \"aca26a7c-582e-4d90-9a30-16c01f15f38e\", :step :if-child, :is-else-condition? true, :children [{:id \"66224477-e983-4217-bea1-8a2ca8f221d1\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bear 3x Shares\", :ticker \"SOXS\", :has_marketcap false, :id \"28b17b5d-af5c-4d9e-8b02-0c757734d8c9\", :exchange \"ARCX\", :price 69.73, :step :asset, :dollar_volume 1365590855.67} {:name \"WisdomTree Bloomberg US Dollar Bullish Fund\", :ticker \"USDU\", :has_marketcap false, :id \"a3d73f2a-e7a3-4c86-aacf-d3212c5759fb\", :exchange \"ARCX\", :price 29.98, :step :asset, :dollar_volume 3695124.94, :children-count 0}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"7\", :id \"36a95a02-701f-4dc0-91a7-2d4f205b544e\", :sort-by? true, :step :filter}]}]}]}]}]}]}], :window-days \"3\"}]}, :ticker-benchmarks [{:color \"#F6609F\", :id \"SPY\", :checked? true, :type :ticker, :ticker \"SPY\"}]}" -------------------------------------------------------------------------------- /lib/get_backtest_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import typing 3 | import csv 4 | import requests 5 | import pandas as pd 6 | import yfinance 7 | import random 8 | import string 9 | 10 | """ 11 | Generate a random string that we can use for folder names to store data in so that harmony can be run in parallel 12 | without different processes interfering with each other. 13 | """ 14 | def random_string(string_length=10): 15 | """Generate a random string of fixed length """ 16 | letters = string.ascii_letters 17 | return ''.join(random.choice(letters) for i in range(string_length)) 18 | 19 | """ 20 | yfinance changed the way they report dates at some point in Jan 2023. They used to return year-month-day, now there's 21 | an added timezone correction. The yfinance python library devs might fix this, so the following hack is meant to be 22 | something that won't break once they do. To do that, clean_date_column() is dead simple, we simply remove everything 23 | after the year-month-day portion that we need for adjusted close prices before feeding the yfinance data to functions 24 | downstream. 25 | """ 26 | def clean_date_column(input_file, output_file): 27 | with open(input_file, 'r') as f_input, open(output_file, 'w', newline='') as f_output: 28 | csv_reader = csv.reader(f_input) 29 | csv_writer = csv.writer(f_output) 30 | headers = next(csv_reader) 31 | csv_writer.writerow(headers) 32 | for row in csv_reader: 33 | date = row[0].split(" ", 1)[0] 34 | row[0] = date 35 | csv_writer.writerow(row) 36 | 37 | def get_backtest_data(tickers: typing.Set[str], use_simulated_data: bool = False) -> pd.DataFrame: 38 | if not os.path.exists("data"): 39 | os.mkdir("data") 40 | 41 | random_folder = random_string() 42 | output_folder = f"work/{random_folder}" 43 | print(f'random_folder : {random_folder}') 44 | print(f'output_folder : {output_folder}') 45 | if not os.path.exists(output_folder): 46 | os.makedirs(output_folder) 47 | 48 | # TODO: make sure all data is adjusted to the same date 49 | # (if writing to disc on Jan 1 but today is Dec 1, that's 11mo where dividends and splits may have invalided everything) 50 | # TODO: if current time is during market hours, then exclude today (yfinance inconsistent about including it) 51 | 52 | tickers_to_fetch = [] 53 | for ticker in tickers: 54 | path = f"{output_folder}/adj-close_{ticker}.csv" 55 | if not os.path.exists(path): 56 | tickers_to_fetch.append(ticker) 57 | 58 | if tickers_to_fetch: 59 | data = yfinance.download(tickers_to_fetch) 60 | # yfinance behaves different depending on number of tickers 61 | if len(tickers_to_fetch) > 1: 62 | for ticker in tickers_to_fetch: 63 | path = f"{output_folder}/adj-close_{ticker}.csv" 64 | 65 | data['Adj Close'][ticker].dropna().sort_index().to_csv(path) 66 | 67 | else: 68 | ticker = tickers_to_fetch[0] 69 | path = f"{output_folder}/adj-close_{ticker}.csv" 70 | d = pd.DataFrame(data['Adj Close']) 71 | d = d.rename(columns={"Adj Close": ticker}) 72 | d.to_csv(path) 73 | 74 | for file_name in os.listdir(output_folder): 75 | if os.path.splitext(file_name)[1] == '.csv': 76 | input_file = f"{output_folder}/{file_name}" 77 | output_file = f"{output_folder}/{file_name.split('.')[0]}_clean.csv" 78 | clean_date_column(input_file, output_file) 79 | 80 | main_dataframe = None 81 | for ticker in tickers: 82 | path = f"{output_folder}/adj-close_{ticker}_clean.csv" 83 | data = pd.read_csv(path, index_col="Date", parse_dates=True) 84 | data = data.sort_index() 85 | 86 | if main_dataframe is None: 87 | main_dataframe = data 88 | else: 89 | main_dataframe = pd.concat([main_dataframe, data], axis=1) 90 | 91 | main_dataframe = typing.cast(pd.DataFrame, main_dataframe) 92 | 93 | if use_simulated_data: 94 | filepath = "data/simulated_data.csv" 95 | if not os.path.exists(filepath): 96 | response = requests.get( 97 | "https://raw.githubusercontent.com/Newtoniano/simulated-leveraged-etf/master/extended-leveraged-etfs.csv") 98 | with open(filepath, 'w') as f: 99 | f.write(response.text) 100 | simulated_data = pd.read_csv(filepath, index_col=0, parse_dates=True) 101 | reconstructed_columns = [] 102 | for ticker in main_dataframe.columns: 103 | if ticker in simulated_data.columns: 104 | combined_series = main_dataframe[ticker].combine_first( 105 | simulated_data[ticker]) 106 | reconstructed_columns.append(combined_series) 107 | else: 108 | reconstructed_columns.append(main_dataframe[ticker]) 109 | 110 | main_simulated_dataframe = pd.concat( 111 | reconstructed_columns, axis=1).astype("float64") 112 | 113 | return typing.cast(pd.DataFrame, main_simulated_dataframe) 114 | 115 | return typing.cast(pd.DataFrame, main_dataframe) 116 | 117 | 118 | def main(): 119 | print(get_backtest_data(set(['SPY', 'UVXY', 'TLT']), True)) 120 | -------------------------------------------------------------------------------- /lib/transpilers.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import typing 3 | 4 | import pandas as pd 5 | import pandas_ta 6 | 7 | from . import human, vectorbt, logic, traversers, quantconnect 8 | 9 | 10 | class Transpiler(): 11 | @abc.abstractstaticmethod 12 | def convert_to_string(cls, root_node: dict) -> str: 13 | raise NotImplementedError() 14 | 15 | class HumanTextTranspiler(): 16 | @staticmethod 17 | def convert_to_string(root_node: dict) -> str: 18 | return human.convert_to_pretty_format(root_node) 19 | 20 | class QuantConnectTranspiler(): 21 | @staticmethod 22 | def convert_to_string(root_node: dict) -> str: 23 | return quantconnect.output_strategy(root_node) 24 | 25 | 26 | # 27 | # TODO: include this inside vectorbt output 28 | # 29 | def precompute_indicator(close_series: pd.Series, indicator: str, window_days: int): 30 | close = close_series.dropna() 31 | if indicator == logic.ComposerIndicatorFunction.CUMULATIVE_RETURN: 32 | # because comparisons will be to whole numbers 33 | return close.pct_change(window_days) * 100 34 | elif indicator == logic.ComposerIndicatorFunction.MOVING_AVERAGE_PRICE: 35 | return pandas_ta.sma(close, window_days) 36 | elif indicator == logic.ComposerIndicatorFunction.RSI: 37 | return pandas_ta.rsi(close, window_days) 38 | elif indicator == logic.ComposerIndicatorFunction.EMA_PRICE: 39 | return pandas_ta.ema(close, window_days) 40 | elif indicator == logic.ComposerIndicatorFunction.CURRENT_PRICE: 41 | return close_series 42 | elif indicator == logic.ComposerIndicatorFunction.STANDARD_DEVIATION_PRICE: 43 | return pandas_ta.stdev(close, window_days) 44 | elif indicator == logic.ComposerIndicatorFunction.STANDARD_DEVIATION_RETURNS: 45 | return pandas_ta.stdev(close.pct_change() * 100, window_days) 46 | elif indicator == logic.ComposerIndicatorFunction.MAX_DRAWDOWN: 47 | # this seems pretty close 48 | maxes = close.rolling(window_days, min_periods=1).max() 49 | downdraws = (close/maxes) - 1.0 50 | return downdraws.rolling(window_days, min_periods=1).min() * -100 51 | elif indicator == logic.ComposerIndicatorFunction.MOVING_AVERAGE_RETURNS: 52 | return close.pct_change().rolling(window_days).mean() * 100 53 | else: 54 | raise NotImplementedError( 55 | "Have not implemented indicator " + indicator) 56 | 57 | 58 | class VectorBTTranspiler(): 59 | @staticmethod 60 | def convert_to_string(root_node: dict) -> str: 61 | return vectorbt.convert_to_vectorbt(root_node) 62 | 63 | @staticmethod 64 | def execute(root_node: dict, closes: pd.DataFrame) -> typing.Tuple[pd.DataFrame, pd.DataFrame]: 65 | code = VectorBTTranspiler.convert_to_string(root_node) 66 | locs = {} 67 | exec(code, None, locs) 68 | build_allocations_matrix = locs['build_allocations_matrix'] 69 | 70 | allocations, branch_tracker = build_allocations_matrix(closes) 71 | 72 | allocateable_tickers = traversers.collect_allocateable_assets( 73 | root_node) 74 | 75 | # remove tickers that were never intended for allocation 76 | for reference_only_ticker in [c for c in allocations.columns if c not in allocateable_tickers]: 77 | del allocations[reference_only_ticker] 78 | 79 | allocations_possible_start = closes[list( 80 | allocateable_tickers)].dropna().index.min().date() 81 | # truncate until allocations possible (branch_tracker is not truncated) 82 | allocations = allocations[allocations.index.date >= 83 | allocations_possible_start] 84 | 85 | return allocations, branch_tracker 86 | 87 | 88 | def main(): 89 | from . import symphony_object, get_backtest_data 90 | 91 | symphony_id = "KvA0KYc57MQSyykdWcFs" 92 | symphony = symphony_object.get_symphony(symphony_id) 93 | root_node = symphony_object.extract_root_node_from_symphony_response( 94 | symphony) 95 | 96 | print(HumanTextTranspiler.convert_to_string(root_node)) 97 | print(VectorBTTranspiler.convert_to_string(root_node)) 98 | 99 | tickers = traversers.collect_referenced_assets(root_node) 100 | 101 | closes = get_backtest_data.get_backtest_data(tickers) 102 | 103 | # 104 | # Execute logic 105 | # 106 | allocations, branch_tracker = VectorBTTranspiler.execute( 107 | root_node, closes) 108 | 109 | backtest_start = allocations.dropna().index.min().date() 110 | 111 | allocations_aligned = allocations[allocations.index.date >= backtest_start] 112 | branch_tracker_aligned = branch_tracker[branch_tracker.index.date >= backtest_start] 113 | 114 | assert len(allocations_aligned) == len(branch_tracker_aligned) 115 | 116 | print(allocations_aligned[( 117 | allocations_aligned.sum(axis=1) - 1).abs() > 0.0001]) 118 | branches_by_failed_allocation_days = branch_tracker_aligned[( 119 | allocations_aligned.sum(axis=1) - 1).abs() > 0.0001].sum(axis=0) 120 | branches_with_failed_allocation_days = branches_by_failed_allocation_days[ 121 | branches_by_failed_allocation_days != 0].index.values 122 | 123 | for branch_id in branches_with_failed_allocation_days: 124 | print(f" -> id={branch_id}") 125 | print(allocations_aligned[branch_tracker_aligned[branch_id] == 1]) 126 | -------------------------------------------------------------------------------- /lib/logic.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | import typing 3 | 4 | 5 | # 6 | # Traversal helpers and documentation 7 | # 8 | def get_node_children(node) -> list: 9 | return node[":children"] if ":children" in node else [] 10 | 11 | 12 | def build_basic_node_type_checker(step: str): 13 | def is_node_of_type(node) -> bool: 14 | return node[":step"] == step 15 | return is_node_of_type 16 | 17 | 18 | is_root_node = build_basic_node_type_checker(":root") 19 | 20 | is_asset_node = build_basic_node_type_checker(":asset") 21 | # ':ticker' 22 | # rest are ephemeral/derived from :ticker: 23 | # ':name' 24 | # ':has_marketcap' 25 | # ':exchange' 26 | # ':price' 27 | # ':dollar_volume' 28 | 29 | is_if_node = build_basic_node_type_checker(":if") 30 | # All :children are definitely if-child 31 | 32 | is_if_child_node = build_basic_node_type_checker(":if-child") 33 | # ':is-else-condition?' 34 | # if not an else block, see `is_conditional_node` 35 | 36 | is_equal_weight_node = build_basic_node_type_checker(":wt-cash-equal") 37 | is_specified_weight_node = build_basic_node_type_checker(":wt-cash-specified") 38 | is_weight_inverse_volatility_node = build_basic_node_type_checker( 39 | ":wt-inverse-vol") 40 | is_weight_marketcap_node = build_basic_node_type_checker(":wt-marketcap") 41 | 42 | 43 | def is_weight_node(node): 44 | return is_equal_weight_node(node) or is_specified_weight_node(node) or is_weight_inverse_volatility_node(node) or is_weight_marketcap_node(node) 45 | 46 | 47 | is_group_node = build_basic_node_type_checker(":group") 48 | # ':name' 49 | # ':collapsed?' 50 | 51 | is_filter_node = build_basic_node_type_checker(":filter") 52 | # ':select?': should always be True... not sure why this is here. 53 | # ':select-fn': :bottom or :top 54 | # ':select-n': str, annoyingly. Always an int 55 | 56 | # ':sort-by?': should always be True... not sure why this is here. 57 | # ':sort-by-fn' 58 | # ':sort-by-window-days': str, annoyingly 59 | 60 | 61 | def is_conditional_node(node) -> bool: 62 | """ 63 | if-child and not else 64 | 65 | # ':lhs-fn' 66 | # ':lhs-window-days' 67 | # ':lhs-val' 68 | 69 | # ':comparator' 70 | 71 | # ':rhs-fn' 72 | # ':rhs-fixed-value?' 73 | # ':rhs-window-days' 74 | # ':rhs-val' 75 | """ 76 | return is_if_child_node(node) and not node[":is-else-condition?"] 77 | 78 | 79 | def get_lhs_ticker(node) -> typing.Optional[str]: 80 | return node[':lhs-val'] 81 | 82 | 83 | def get_rhs_ticker(node) -> typing.Optional[str]: 84 | # yes, rhs behaves differently than lhs. 85 | if not node.get(':rhs-fixed-value?', type(node[':rhs-val']) != str): 86 | return node[":rhs-val"] 87 | 88 | 89 | class ComposerIndicatorFunction: 90 | CURRENT_PRICE = ":current-price" 91 | CUMULATIVE_RETURN = ":cumulative-return" 92 | STANDARD_DEVIATION_PRICE = ":standard-deviation-price" 93 | STANDARD_DEVIATION_RETURNS = ":standard-deviation-return" 94 | MAX_DRAWDOWN = ":max-drawdown" 95 | MOVING_AVERAGE_PRICE = ":moving-average-price" 96 | MOVING_AVERAGE_RETURNS = ":moving-average-return" 97 | EMA_PRICE = ":exponential-moving-average-price" 98 | RSI = ":relative-strength-index" 99 | 100 | 101 | class ComposerComparison: 102 | LTE = ":lte" 103 | LT = ":lt" 104 | GTE = ":gte" 105 | GT = ":gt" 106 | EQ = ":eq" 107 | 108 | 109 | def get_ticker_of_asset_node(node) -> str: 110 | return node[':ticker'] 111 | 112 | 113 | # 114 | # Tree path reducer/state thinker 115 | # - weight 116 | # - branch path 117 | # - parent node type (sans pass-through nodes like :group) 118 | # 119 | @dataclass 120 | class NodeBranchState: 121 | weight: float # do not read this on :wt-* nodes, behavior not guaranteed 122 | branch_path_ids: typing.List[str] 123 | parent_nodes: typing.List[dict] 124 | 125 | def copy(self): 126 | return NodeBranchState(weight=self.weight, branch_path_ids=[i for i in self.branch_path_ids], parent_nodes=[n for n in self.parent_nodes]) 127 | 128 | 129 | 130 | def build_node_branch_state_from_root_node(node) -> NodeBranchState: 131 | return NodeBranchState(1, [node[":id"]], [node]) 132 | 133 | 134 | def extract_weight_factor(parent_node_branch_state: NodeBranchState, node) -> float: 135 | # Do not care about :weight if parent type is not a specific node type (UI leaves this strewn everywhere) 136 | weight = 1 137 | 138 | parent_node_type = parent_node_branch_state.parent_nodes[-1][":step"] 139 | 140 | # :wt-cash-specified parent means :weight is specified on this node 141 | if parent_node_type == ":wt-cash-specified" and ":weight" in node: 142 | weight *= int(node[":weight"][":num"]) / int(node[":weight"][":den"]) 143 | 144 | # :wt-cash-equal parent means apply equal % across all siblings of this node 145 | if parent_node_type == ":wt-cash-equal": 146 | weight /= len(get_node_children( 147 | parent_node_branch_state.parent_nodes[-1])) 148 | 149 | # :filter parent means apply equal % across :select-n of parent 150 | if parent_node_type == ":filter": 151 | weight /= int( 152 | parent_node_branch_state.parent_nodes[-1].get(":select-n", 1)) 153 | 154 | # :wt-inverse-vol cannot be computed here, theoretical max is 100% 155 | # :wt-marketcap cannot be computed here, theoretical max is 100% 156 | 157 | # There are no other blocks which impact weights. 158 | 159 | return weight 160 | 161 | # "reducer" signature / design pattern 162 | 163 | 164 | def advance_branch_state(parent_node_branch_state: NodeBranchState, node) -> NodeBranchState: 165 | current_node_branch_state = parent_node_branch_state.copy() 166 | 167 | current_node_branch_state.parent_nodes.append(node) 168 | 169 | if is_if_child_node(node): 170 | current_node_branch_state.branch_path_ids.append(node[":id"]) 171 | 172 | current_node_branch_state.weight *= extract_weight_factor( 173 | parent_node_branch_state, node) 174 | 175 | return current_node_branch_state 176 | -------------------------------------------------------------------------------- /lib/human.py: -------------------------------------------------------------------------------- 1 | import io 2 | import typing 3 | 4 | from . import logic, manual_testing 5 | 6 | 7 | # TODO: how should a transpiler handle unexpected values? logging.warning? Throw an exception? Leave a TODO comment in the output? 8 | 9 | def main(): 10 | # path = 'inputs/inputFile.edn' 11 | # path = 'inputs/jamestest.edn' 12 | path = 'inputs/tqqq_long_term.edn' 13 | root_node = manual_testing.get_root_node_from_path(path) 14 | print(convert_to_pretty_format(root_node)) 15 | 16 | 17 | def pretty_fn(fn_string: str) -> str: 18 | if fn_string == logic.ComposerIndicatorFunction.RSI: 19 | return "RSI" 20 | if fn_string == logic.ComposerIndicatorFunction.CURRENT_PRICE: 21 | return "Close" 22 | if fn_string == logic.ComposerIndicatorFunction.CUMULATIVE_RETURN: 23 | return "CumulativeReturn" 24 | if fn_string == logic.ComposerIndicatorFunction.MOVING_AVERAGE_PRICE: 25 | return "SMA" 26 | if fn_string == logic.ComposerIndicatorFunction.EMA_PRICE: 27 | return "EMA" 28 | if fn_string == logic.ComposerIndicatorFunction.STANDARD_DEVIATION_PRICE: 29 | return "STDEV" 30 | if fn_string == logic.ComposerIndicatorFunction.STANDARD_DEVIATION_RETURNS: 31 | return "STDEVReturns" 32 | if fn_string == logic.ComposerIndicatorFunction.MAX_DRAWDOWN: 33 | return "MaxDrawdown" 34 | if fn_string == logic.ComposerIndicatorFunction.MOVING_AVERAGE_RETURNS: 35 | return "SMAReturns" 36 | print(f"UNEXPECTED function {fn_string}") 37 | return fn_string 38 | 39 | 40 | def pretty_comparison(comparator_string: str) -> str: 41 | if comparator_string == logic.ComposerComparison.LTE: 42 | return "<=" 43 | if comparator_string == logic.ComposerComparison.LT: 44 | return "<" 45 | if comparator_string == logic.ComposerComparison.GTE: 46 | return ">=" 47 | if comparator_string == logic.ComposerComparison.GT: 48 | return ">" 49 | if comparator_string == logic.ComposerComparison.EQ: 50 | return "=" 51 | print(f"UNEXPECTED comparator {comparator_string}") 52 | return comparator_string 53 | 54 | 55 | def pretty_indicator(fn: str, val, window_days: typing.Optional[int]): 56 | if fn == logic.ComposerIndicatorFunction.CURRENT_PRICE: 57 | return f"{pretty_fn(fn)}({val})" 58 | else: 59 | return f"{pretty_fn(fn)}({val}, {window_days}d)" 60 | 61 | 62 | def pretty_lhs(node) -> str: 63 | if type(node[":lhs-val"]) != str: 64 | return node[":lhs-val"] 65 | else: 66 | return pretty_indicator(node[":lhs-fn"], node[":lhs-val"], node.get(":lhs-window-days")) 67 | 68 | 69 | def pretty_rhs(node) -> str: 70 | if node.get(':rhs-fixed-value?', type(node[':rhs-val']) != str): 71 | return node[':rhs-val'] 72 | else: 73 | return pretty_indicator(node[":rhs-fn"], node[":rhs-val"], node.get(":rhs-window-days")) 74 | 75 | 76 | def pretty_condition(node) -> str: 77 | return f"{pretty_lhs(node)} {pretty_comparison(node[':comparator'])} {pretty_rhs(node)}" 78 | 79 | 80 | def pretty_selector(node) -> str: 81 | if node[":select-fn"] == ":bottom": 82 | return f"bottom {node[':select-n']}" 83 | elif node[":select-fn"] == ":top": 84 | return f"top {node[':select-n']}" 85 | else: 86 | return f"UNEXPECTED :select-fn {node[':select-fn']}" 87 | 88 | 89 | def pretty_filter(node) -> str: 90 | return f"{pretty_selector(node)} by {pretty_indicator(node[':sort-by-fn'], '____', node[':sort-by-window-days'])}" 91 | 92 | 93 | def print_children(node, depth=0, parent_node_branch_state: typing.Optional[logic.NodeBranchState] = None, file=None): 94 | """ 95 | Recursively visits every child node (depth-first) 96 | and pretty-prints it out. 97 | """ 98 | if not parent_node_branch_state: 99 | # current node is :root, there is no higher node 100 | parent_node_branch_state = logic.build_node_branch_state_from_root_node( 101 | node) 102 | parent_node_branch_state = typing.cast( 103 | logic.NodeBranchState, parent_node_branch_state) 104 | 105 | current_node_branch_state = logic.advance_branch_state( 106 | parent_node_branch_state, node) 107 | 108 | def pretty_log(message: str): 109 | s = " " * depth 110 | 111 | weight_factor = logic.extract_weight_factor( 112 | parent_node_branch_state, node) 113 | if not logic.is_weight_node(node) and weight_factor != 1: 114 | s += f"{weight_factor:.0%} => " 115 | 116 | s += message 117 | 118 | if logic.is_asset_node(node): 119 | s += f" (max: {current_node_branch_state.weight:.1%})" 120 | print(s, file=file) 121 | 122 | if logic.is_root_node(node): 123 | pretty_log(node[":name"]) 124 | elif logic.is_equal_weight_node(node): 125 | # sometimes children will have :weight (sometimes inserted as a no-op) 126 | pretty_log("Weight equally:") 127 | elif logic.is_specified_weight_node(node): 128 | # children will have :weight 129 | # ':weight': {':num': 88, ':den': 100}, (numerator and denominator) 130 | pretty_log("Weight accordingly:") 131 | elif logic.is_weight_inverse_volatility_node(node): 132 | pretty_log( 133 | f"Weight inversely to {pretty_indicator(logic.ComposerIndicatorFunction.STANDARD_DEVIATION_RETURNS, '____', node[':window-days'])}") 134 | elif logic.is_if_node(node): 135 | pretty_log("if") 136 | elif logic.is_if_child_node(node): 137 | if not logic.is_conditional_node(node): 138 | pretty_log("else") 139 | else: 140 | pretty_log(f"({pretty_condition(node)})") 141 | elif logic.is_group_node(node): 142 | pretty_log(f"// {node[':name']}") 143 | elif logic.is_filter_node(node): 144 | pretty_log(pretty_filter(node)) 145 | elif logic.is_asset_node(node): 146 | pretty_log(logic.get_ticker_of_asset_node(node)) 147 | else: 148 | pretty_log(f"UNIMPLEMENTED: {node[':step']}") 149 | 150 | for child in logic.get_node_children(node): 151 | print_children(child, depth=depth+1, 152 | parent_node_branch_state=current_node_branch_state, file=file) 153 | 154 | 155 | def convert_to_pretty_format(root_node) -> str: 156 | output = io.StringIO() 157 | print_children(root_node, file=output) 158 | text = output.getvalue() 159 | output.close() 160 | return text 161 | -------------------------------------------------------------------------------- /lib/vectorbt.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import typing 4 | from . import traversers, manual_testing, logic, human 5 | 6 | 7 | def extract_indicator_key_from_indicator(indicator): 8 | return human.pretty_indicator(indicator['fn'], indicator['val'], indicator['window-days']) 9 | 10 | 11 | def express_comparator_in_python(comparator_string: str) -> str: 12 | if comparator_string == logic.ComposerComparison.LTE: 13 | return "<=" 14 | if comparator_string == logic.ComposerComparison.LT: 15 | return "<" 16 | if comparator_string == logic.ComposerComparison.GTE: 17 | return ">=" 18 | if comparator_string == logic.ComposerComparison.GT: 19 | return ">" 20 | if comparator_string == logic.ComposerComparison.EQ: 21 | return "==" 22 | print(f"UNEXPECTED comparator {comparator_string}") 23 | return comparator_string 24 | 25 | 26 | def get_code_to_reference_indicator(indicator) -> str: 27 | key = extract_indicator_key_from_indicator(indicator) 28 | return f"indicators.at[row, '{key}']" 29 | 30 | 31 | def express_condition(child_node) -> str: 32 | lhs_indicator = traversers.extract_lhs_indicator( 33 | child_node) 34 | lhs_expression = get_code_to_reference_indicator( 35 | lhs_indicator) 36 | 37 | rhs_indicator = traversers.extract_rhs_indicator( 38 | child_node) 39 | if not rhs_indicator: 40 | rhs_expression = f"{child_node[':rhs-val']}" 41 | else: 42 | rhs_expression = get_code_to_reference_indicator( 43 | rhs_indicator) 44 | 45 | return f"{lhs_expression} {express_comparator_in_python(child_node[':comparator'])} {rhs_expression}" 46 | 47 | 48 | def print_python_logic(node, parent_node_branch_state: typing.Optional[logic.NodeBranchState] = None, indent: int = 0, indent_size: int = 4, file=None): 49 | """ 50 | Traverses tree and prints out python code for populating allocations dataframe. 51 | """ 52 | if not parent_node_branch_state: 53 | # current node is :root, there is no higher node 54 | parent_node_branch_state = logic.build_node_branch_state_from_root_node( 55 | node) 56 | parent_node_branch_state = typing.cast( 57 | logic.NodeBranchState, parent_node_branch_state) 58 | 59 | current_node_branch_state = logic.advance_branch_state( 60 | parent_node_branch_state, node) 61 | 62 | def indented_print(msg: str, indent_offset=0): 63 | print((" " * indent_size * (indent + indent_offset)) + msg, file=file) 64 | 65 | # :wt-cash-equally and :wt-cash-specified is handled by logic.advance_branch_state logic for us 66 | # TODO: Weight inverse by volatility (similar approach to :filter) 67 | # TODO: weight by market cap dynamically, how to get data? 68 | 69 | if logic.is_if_node(node): 70 | for i, child_node in enumerate(logic.get_node_children(node)): 71 | if i == 0: 72 | indented_print(f"if {express_condition(child_node)}:") 73 | elif logic.is_conditional_node(child_node): 74 | indented_print(f"elif {express_condition(child_node)}:") 75 | else: 76 | indented_print("else:") 77 | print_python_logic( 78 | child_node, parent_node_branch_state=current_node_branch_state, indent=indent+1, indent_size=indent_size, file=file) 79 | return 80 | elif logic.is_asset_node(node): 81 | indented_print( 82 | f"branch_tracker.at[row, '{current_node_branch_state.branch_path_ids[-1]}'] = 1") 83 | indented_print( 84 | f"allocations.at[row, '{logic.get_ticker_of_asset_node(node)}'] += {current_node_branch_state.weight}") 85 | elif logic.is_group_node(node): 86 | indented_print(f"# {node[':name']}") 87 | elif logic.is_filter_node(node): 88 | indented_print( 89 | f"branch_tracker.at[row, '{current_node_branch_state.branch_path_ids[-1]}'] = 1") 90 | 91 | indented_print(f"entries = [") 92 | for filter_indicator in traversers.extract_filter_indicators(node): 93 | fmt = extract_indicator_key_from_indicator(filter_indicator) 94 | indented_print( 95 | f"(indicators.at[row, '{fmt}'], '{filter_indicator['val']}'),", indent_offset=1) 96 | indented_print(f"]") 97 | 98 | indented_print( 99 | f"selected_entries = sorted(entries, reverse={node[':select-fn'] == ':top'})[:{int(node[':select-n'])}]") 100 | indented_print(f"for _sort_value, ticker in selected_entries:") 101 | # use weight of first child (will be same across all children) 102 | weight = logic.advance_branch_state( 103 | current_node_branch_state, logic.get_node_children(node)[0]).weight 104 | indented_print( 105 | f"allocations.at[row, ticker] += {weight}", indent_offset=1) 106 | 107 | # Debugging 108 | # indented_print( 109 | # f"print(entries, '{node[':select-fn']} {node[':select-n']}', selected_entries)") 110 | 111 | return 112 | elif logic.is_weight_inverse_volatility_node(node): 113 | indented_print( 114 | f"branch_tracker.at[row, '{current_node_branch_state.branch_path_ids[-1]}'] = 1") 115 | indented_print(f"entries = [") 116 | for indicator in traversers.extract_inverse_volatility_indicators(node): 117 | fmt = extract_indicator_key_from_indicator(indicator) 118 | indented_print( 119 | f"(1/indicators.at[row, '{fmt}'], '{indicator['val']}'),", indent_offset=1) 120 | indented_print(f"]") 121 | indented_print( 122 | f"overall_inverse_volatility = sum(t[0] for t in entries)") 123 | indented_print(f"for inverse_volatility, ticker in entries:") 124 | # use weight of first child (will be same across all children) 125 | weight = logic.advance_branch_state( 126 | current_node_branch_state, logic.get_node_children(node)[0]).weight 127 | indented_print( 128 | f"allocations.at[row, ticker] += {weight} * (inverse_volatility / overall_inverse_volatility)", indent_offset=1) 129 | 130 | return 131 | 132 | for child_node in logic.get_node_children(node): 133 | print_python_logic( 134 | child_node, parent_node_branch_state=current_node_branch_state, indent=indent, indent_size=indent_size, file=file) 135 | 136 | 137 | def convert_to_vectorbt(root_node) -> str: 138 | assert not traversers.collect_nodes_of_type( 139 | ":wt-marketcap", root_node), "Market cap weighting is not supported." 140 | 141 | output = io.StringIO() 142 | _convert_to_vectorbt(root_node, file=output) 143 | text = output.getvalue() 144 | output.close() 145 | return text 146 | 147 | 148 | def _convert_to_vectorbt(root_node, file=None): 149 | def write(*msgs): 150 | print(*msgs, file=file) 151 | 152 | write(f""" 153 | 154 | def build_allocations_matrix(closes): 155 | indicators = pd.DataFrame(index=closes.index) 156 | """) 157 | for indicator in traversers.collect_indicators(root_node): 158 | write( 159 | f" indicators['{extract_indicator_key_from_indicator(indicator)}'] = precompute_indicator(closes['{indicator['val']}'], '{indicator['fn']}', {indicator['window-days']})") 160 | write(""" 161 | # If any indicator is not available, we cannot compute that day 162 | # (assumes all na's stop at some point and then are continuously available into the future, no skips) 163 | indicators.dropna(axis=0, inplace=True) 164 | """) 165 | 166 | branches_by_path = traversers.collect_branches(root_node) 167 | branches_by_leaf_node_id = { 168 | key.split("/")[-1]: value for key, value in branches_by_path.items()} 169 | write(f""" 170 | # 171 | # Algorithm Logic and instrumentation 172 | # 173 | allocations = pd.DataFrame(index=indicators.index, columns=closes.columns).fillna(0.0) 174 | 175 | # Track branch usage based on :id of "leaf" condition (closest :if-child up the tree to that leaf node) 176 | branch_tracker = pd.DataFrame(index=indicators.index, columns={repr(sorted(branches_by_leaf_node_id.keys()))}).fillna(0) 177 | 178 | for row in indicators.index: 179 | """) 180 | 181 | print_python_logic(root_node, indent=2, indent_size=4, file=file) 182 | 183 | write(""" 184 | return allocations, branch_tracker 185 | """) 186 | 187 | 188 | def main(): 189 | path = 'inputs/tqqq_long_term.edn' 190 | path = 'inputs/betaballer-modified.edn' 191 | # path = 'inputs/simple.edn' 192 | root_node = manual_testing.get_root_node_from_path(path) 193 | 194 | print(convert_to_vectorbt(root_node)) 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # compose_symphony_parser 2 | a text parser that will attempt to export a text encoded composer symphony, to whatever text endpoint you want 3 | 4 | - [x] human readable output 5 | - [ ] vectorbt (WIP) 6 | - [x] quantconnect (using ChatGPT) 7 | - [ ] tradingview 8 | - [ ] thinkscript 9 | 10 | # Requirements 11 | 12 | Use python3. 13 | 14 | ```bash 15 | pip3 install -r requirements.txt 16 | ``` 17 | 18 | ## ChatGPT Transpiling Support 19 | 20 | **As of December 13th, ChatGPT [added auth protections!](https://github.com/acheong08/ChatGPT/wiki/Setup)** 21 | _This requires a DESKTOP environment as the script can no longer be ran headless._ 22 | 23 | Previous versions of this script are broken until you run the following commands: 24 | ``` 25 | pip3 install --upgrade revChatGPT 26 | playwright install 27 | ``` 28 | 29 | Then, when using ChatGPT you will be required to login using your browser. 30 | 31 | Support for QuantConnect strategies is done by leveraging ChatGPT to transpile the human-readable format into a QuantConnect strategy. 32 | `python3 ./symphony_parser.py -m quantconnect -i https://app.composer.trade/symphony/PdgUAAy4GmEQvGyKsYZt/details -u` 33 | 34 | 35 | # Usage 36 | ``` 37 | python3 symphony_parser.py -i infile -o outfile -m quantconnect 38 | saves quantconnect parsed output to "outfile" (NOT TESTED, MAY NOT WORK) 39 | 40 | python3 symphony_parser.py -i inputs/simple.edn -m vector 41 | prints vectorbt output to screen 42 | 43 | python3 symphony_parser.py -i infile 44 | will just print human readable output to the screen 45 | 46 | python3 symphony_parser.py -m vector -i https://app.composer.trade/symphony/PdgUAAy4GmEQvGyKsYZt/details -u 47 | will print a vectorbt formatted output, will download a symphony directly from composer 48 | 49 | python3 symphony_parser.py -m human -u -i https://app.composer.trade/symphony/PdgUAAy4GmEQvGyKsYZt/details -p 50 | prints a human formatted output, download the json formatted edn_encoded symphony directly from composer, AND all symphony parents, printing them directly to the screen 51 | 52 | python3 symphony_parser.py -m human -p -u -b -i inputs/bulk_symphonies.txt 53 | prints a human formatted output, download the json formatted edn_encoded symphony directly from composer, all symphony parents, printing them directly to the screen, AND reads the text file which contains a list of urls which it bulk reads from. can be urls or a list of local file paths for json encoded edn files. 54 | 55 | python3 .symphony_parser.py -m quantconnect -i https://app.composer.trade/symphony/PdgUAAy4GmEQvGyKsYZt/details -u 56 | will print a C# QuantConnect strategy, transpiled by ChatGPT 57 | 58 | infile: the file that contains the text encoded symphony 59 | 60 | you can now use the -u option when specifying an infile. This will cause it to treat the infile as a symphony url and it will then pull the data down from composer. 61 | 62 | outfile: the file you want the parsed output to go to 63 | quantconnect: the output mode style 64 | eventual, hopeful, supported output style modes: quantconnect, vectorbt, tradingview, thinkscript 65 | ``` 66 | 67 | In order for an output style to be supported, it does not have to be 100% complete. it can only be 10% complete to be supported. as long as it helps someone convert the original text, and get them closer from the original encoded text, to the "Other" system, then its a nice, good conversion. 68 | 69 | to get the backtesting syntax: 70 | 71 | "in your composer page, before you change a date on the backtesting graph, and cause the webpage to do another backtest, have the inspect tool open, and on the network tab, so it's monitoring your network traffic. change the date. this will send a request to the backend. as it's monitoring your networking graph, it will endcode the symphony on the page in text, and you should be able to find it in one of those." 72 | 73 | 74 | sample composer syntax as of 10-18 75 | ```edn 76 | [{:id \"a6cea29d-15a3-48d6-900a-e8570fdef804\", :step :wt-cash-equal, :children [{:id \"d4ece5ac-9a5f-4f50-86ee-f2ca82a5b31e\", :step :if, :children [{:children [{:id \"4fcf1c2a-e747-413f-b3b3-75c6004c791b\", :step :wt-cash-equal, :children [{:id \"4a465370-bd84-493a-b4e1-cedc27e00cae\", :step :if, :children [{:children [{:id \"c7303d3f-3a51-422f-ac91-9b349ab798a0\", :step :group, :name \"Overbought S&P. Sell the rip. Buy volatility\", :children [{:step :wt-cash-equal, :id \"5b7f5cdf-4611-4aa2-b277-e03b1fc87a70\", :children [{:select? true, :children [{:name \"ProShares Ultra VIX Short-Term Futures ETF\", :ticker \"UVXY\", :has_marketcap false, :id \"c82bef09-9dd6-498f-aa54-b1920bd52fed\", :exchange \"BATS\", :price 11.45, :step :asset, :dollar_volume 453424499.84999996} {:name \"ProShares VIX Short-Term Futures ETF\", :ticker \"VIXY\", :has_marketcap false, :id \"57f4b706-3cca-4e06-895e-7f435bae9c3a\", :exchange \"BATS\", :price 15.84, :step :asset, :dollar_volume 149119153.92}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"13\", :weight {:num \"80\", :den 100}, :id \"d74aeaea-5b9d-44b5-a0eb-61c60d9532e8\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"7\", :lhs-val \"SPY\", :id \"0cb24ab6-f9b2-4a6a-9a14-9a87d9839400\", :comparator :gt, :rhs-val \"76\", :step :if-child} {:id \"9d8b558a-0a21-48e4-952c-b1014ba700cd\", :step :if-child, :is-else-condition? true, :children [{:id \"3698f968-4859-45db-8a2d-83d332e41141\", :step :wt-cash-equal, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"3bd5e2c3-ef00-463d-8eb5-f88fb765460d\", :exchange \"ARCX\", :price 11.19, :step :asset, :dollar_volume 1275587444.04}]}]}]}]}], :rhs-fn :relative-strength-index, :is-else-condition? false, :rhs-fixed-value? false, :lhs-fn :relative-strength-index, :rhs-window-days \"5\", :lhs-window-days \"5\", :lhs-val \"BIL\", :id \"9da13422-9cb9-4b01-a3be-64edff9d95e3\", :comparator :lt, :rhs-val \"BND\", :step :if-child} {:id \"03f583d0-5608-4618-ba12-c3dff9db75fb\", :step :if-child, :is-else-condition? true, :children [{:id \"7902f025-3e45-4d12-9722-3a587b006e5d\", :step :wt-cash-equal, :children [{:id \"91c6e86e-cafd-4547-b371-b0adc0647062\", :step :if, :children [{:children [{:id \"1a18804f-596c-4979-b968-59862f10eb57\", :step :group, :name \"Extremely oversold S&P (low RSI). Double check with bond mkt before going long\", :children [{:step :wt-cash-equal, :id \"0edc12db-a440-4edb-8f23-84d4979434ac\", :children [{:id \"4e49579c-4407-4ae7-ae74-8eeb1f86d536\", :step :if, :children [{:children [{:name \"Direxion Daily Semiconductor Bear 3x Shares\", :ticker \"SOXS\", :has_marketcap false, :id \"c4f5b6e2-912b-4e6b-8f7c-6a265a1c0799\", :exchange \"ARCX\", :price 53.72, :step :asset, :dollar_volume 871353441.6}], :rhs-fn :relative-strength-index, :is-else-condition? false, :lhs-fn :relative-strength-index, :rhs-window-days \"10\", :lhs-window-days \"10\", :lhs-val \"SHY\", :id \"6fcc22ec-8814-4be5-9cab-afd724c57096\", :comparator :lte, :rhs-val \"HIBL\", :step :if-child} {:id \"93d72d2b-b677-419e-a1f3-d30820a1691c\", :step :if-child, :is-else-condition? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"ef524e04-ef23-473d-a402-bfc070088725\", :exchange \"ARCX\", :price 11.19, :step :asset, :dollar_volume 1275587444.04, :children-count 0}]}]}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"7\", :lhs-val \"SPY\", :id \"3e9c24d0-5a75-47c5-ae3a-59aade04e5d4\", :comparator :lt, :rhs-val \"27\", :step :if-child} {:id \"aca26a7c-582e-4d90-9a30-16c01f15f38e\", :step :if-child, :is-else-condition? true, :children [{:id \"66224477-e983-4217-bea1-8a2ca8f221d1\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bear 3x Shares\", :ticker \"SOXS\", :has_marketcap false, :id \"28b17b5d-af5c-4d9e-8b02-0c757734d8c9\", :exchange \"ARCX\", :price 69.73, :step :asset, :dollar_volume 1365590855.67} {:name \"WisdomTree Bloomberg US Dollar Bullish Fund\", :ticker \"USDU\", :has_marketcap false, :id \"a3d73f2a-e7a3-4c86-aacf-d3212c5759fb\", :exchange \"ARCX\", :price 29.98, :step :asset, :dollar_volume 3695124.94, :children-count 0}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"7\", :id \"36a95a02-701f-4dc0-91a7-2d4f205b544e\", :sort-by? true, :step :filter}]}]}]}]}]}]}], :window-days \"3\"}]}, :ticker-benchmarks [{:color \"#F6609F\", :id \"SPY\", :checked? true, :type :ticker, :ticker \"SPY\"}]}" 77 | ``` 78 | 79 | it's the EDN-format!!! 80 | 81 | https://github.com/edn-format/edn 82 | 83 | 84 | # TODO: 85 | - compare changes from parent/child 86 | - difflib.unified_diff 87 | - print said/show changes 88 | - add line numbers to printed symphonys for easier discussion -------------------------------------------------------------------------------- /inputs/tqqq_long_term.edn: -------------------------------------------------------------------------------- 1 | {:id "2XE43Kcoqa0uLSOBuN3q", :step :root, :name "TQQQ For The Long Term Original (285.1%/45.6%DD) (Invest Copy)", :description "", :rebalance :daily, :children [{:id "8345e902-2baf-4119-b2c5-effa4ac8303e", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id "67e8ca61-be9f-4905-b23f-cd2e8ccb0771", :step :if, :children [{:children [{:id "1f8a7666-6cd4-4c75-8b14-0ad6d44fb7c7", :step :wt-cash-equal, :children [{:id "5fa30ba9-f2c8-47ac-98a5-bcee9f1ca738", :step :if, :children [{:children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "79a1d885-ac42-4ef6-b91e-e4837f164668", :exchange "BATS", :price 10.52, :step :asset, :dollar_volume 1174265281}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "5873b8cd-fa33-4d13-87f4-b41470b281e9", :comparator :gt, :rhs-val "79", :step :if-child, :collapsed? false} {:id "f039f86c-853c-40d1-8c48-45d39ce489e2", :step :if-child, :is-else-condition? true, :children [{:id "6fe5eb7e-9275-4a0f-a4c8-4da11c4fda95", :step :wt-cash-equal, :children [{:id "d59de8e7-9ee2-42a7-8b0e-a277373261f3", :step :if, :children [{:children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "eef73414-0816-40fb-aea5-1ebaf300c610", :exchange "BATS", :price 9.91, :step :asset, :dollar_volume 650525430.03}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SPXL", :id "151b8783-383a-4aba-85ba-fcea5d1293a0", :comparator :gt, :rhs-val "80", :step :if-child} {:id "9a8ae941-4ff3-414e-a150-210b29eccc4a", :step :if-child, :is-else-condition? true, :children [{:id "79dfcf27-428c-406b-af34-aa46e6d475cd", :step :wt-cash-equal, :children [{:id "b0cb3318-a869-42be-89b1-320c40b30867", :step :if, :children [{:children [{:id "baa5a462-d751-4b93-929b-062be3959ecc", :step :wt-cash-equal, :children [{:id "f669489b-b06c-4164-a40a-5d528d5e44e5", :step :if, :children [{:children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "a7f71c31-3cb4-4d19-8ab6-8eeaa1b30879", :exchange "XNAS", :price 21.31, :step :asset, :dollar_volume 5938887884.969999}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "b3008001-2da1-4c3c-8d73-b6b878cf05d7", :comparator :lt, :rhs-val "31", :step :if-child} {:id "ff951ad9-d2f1-48a8-a33d-995d9061fd19", :step :if-child, :is-else-condition? true, :children [{:id "f8bdd39c-2108-4f85-ac9d-de56296d7f2d", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "c163a608-f74c-497b-bcdc-221c286c8d5f", :exchange "BATS", :price 12.85, :step :asset, :dollar_volume 904391596.65} {:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "6952fd06-7584-4524-94e1-47c0af6aec63", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "1955962d-a85f-45d3-b74c-808cc78ec0c5", :sort-by? true, :step :filter}]}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :lhs-window-days "5", :lhs-val "TQQQ", :id "453bddf7-c718-4aa6-ade0-81fbdb5fefbd", :comparator :gt, :rhs-val "20", :step :if-child, :collapsed? false} {:id "a4405ff4-7527-45dc-a20d-89335e04fa25", :step :if-child, :is-else-condition? true, :children [{:id "4bc98db1-96f4-4896-82e1-07bcb69634a4", :step :wt-cash-equal, :children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :weight {:num "40", :den 100}, :id "bdebff71-6249-4e79-8ab2-d731483d2901", :exchange "XNAS", :price 17.57, :step :asset, :dollar_volume 5850985155.33}]}]}]}]}]}]}], :collapsed? false}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "200", :lhs-val "SPY", :id "2a21ed78-76e8-4501-ba8b-52e57851fd65", :comparator :gt, :rhs-val "SPY", :step :if-child, :collapsed? false} {:id "62a48ef3-4178-4d16-ba27-7cb531c433ea", :step :if-child, :is-else-condition? true, :children [{:id "a565dc85-ac28-44af-a8a5-93d726364836", :step :wt-cash-equal, :children [{:id "8013e504-2fd7-413f-8768-05e656de97d7", :step :if, :children [{:children [{:id "30c32d8a-1aba-42e0-912e-429830c5aee7", :step :wt-cash-equal, :children [{:id "0f32ede0-e650-4b2e-ba34-a04a9dfb2052", :step :wt-cash-equal, :children [{:id "a867632f-84a3-42cc-815a-07f65fee04bc", :step :group, :name "Tech", :children [{:step :wt-cash-equal, :id "88730c78-d306-4fc6-9e60-e91a47b8e567", :children [{:name "Direxion Daily Technology Bull 3x Shares", :ticker "TECL", :has_marketcap false, :id "8264631c-7635-47df-a649-9009c1a93612", :exchange "ARCX", :price 21.48, :step :asset, :dollar_volume 103117876.08} {:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "dfa4cd69-2b4d-43ca-a9e7-0edf51630d4f", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005}]}]} {:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "371405a0-e7e2-4b3a-9b80-7c64973a76e1", :exchange "ARCX", :price 8.86, :step :asset, :dollar_volume 956756695.38}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "865d1809-45a9-4517-b189-2e065cf2dc80", :comparator :lt, :rhs-val "31", :step :if-child} {:id "299a682e-ea52-49e9-9d9e-b0f08db7cbb5", :step :if-child, :is-else-condition? true, :children [{:id "cc6be75d-7db5-494f-9ee5-9e7264c1675f", :step :wt-cash-equal, :children [{:id "47c8bae9-a5d4-4dda-803b-a0397c5bbfe9", :step :if, :children [{:children [{:name "ProShares UltraPro S&P500", :ticker "UPRO", :has_marketcap false, :id "db08ecb5-3281-4ef1-9731-46e47ec4549e", :exchange "ARCX", :price 28.41, :step :asset, :dollar_volume 525276865.14}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SPY", :id "8c96da44-2efa-40bd-ba37-e7ea6719fb11", :comparator :lt, :rhs-val "30", :step :if-child} {:id "79e131e3-ce2a-4819-964f-d3b99efd072c", :step :if-child, :is-else-condition? true, :children [{:id "636f9abc-4253-430d-ac46-430cce15d448", :step :wt-cash-equal, :children [{:id "a4a9bb30-44a6-458a-95e3-772e71549278", :step :if, :children [{:children [{:id "b0215dd1-2970-4102-b05b-30d326aada07", :step :wt-cash-equal, :children [{:id "bbb6762b-839a-485d-9c40-3fa9efe12a91", :step :if, :children [{:children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "a69ad012-1768-4998-8dfc-b47ccfec3d5a", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :lhs-window-days "10", :lhs-val "SQQQ", :id "d804b71b-d96f-4fbc-9fff-6c7bd54d7c62", :comparator :gt, :rhs-val "23", :step :if-child} {:id "9e194caa-d8d6-49ab-afe7-174291e2db7c", :step :if-child, :is-else-condition? true, :children [{:id "761d7852-24f8-455f-a53c-2caf7652e535", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "1658b6fa-48e3-40f5-a39e-eddf5f69eda1", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64} {:name "iShares 20+ Year Treasury Bond ETF", :ticker "TLT", :has_marketcap false, :id "2d5ad9e9-c136-4102-916f-db9456d38c52", :exchange "XNAS", :price 98.57, :step :asset, :dollar_volume 1805488454.55, :children-count 0}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "f0f4e5be-024e-445d-9e93-9c0e120bf7db", :sort-by? true, :step :filter} {:select? true, :children [{:name "Direxion Daily Semiconductor Bear 3x Shares", :ticker "SOXS", :has_marketcap false, :id "c673a16e-5669-4afc-82a8-4c942207e670", :exchange "ARCX", :price 70.73, :step :asset, :dollar_volume 971448045.8100001} {:name "iShares 20+ Year Treasury Bond ETF", :ticker "TLT", :has_marketcap false, :id "0f0d2c60-8510-47a1-aba5-25380e0689a3", :exchange "XNAS", :price 98.57, :step :asset, :dollar_volume 1805488454.55, :children-count 0}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "084f66d5-7a7f-49af-b1b8-7e19b481dd27", :sort-by? true, :step :filter}]}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "20", :lhs-val "TQQQ", :id "82a8f8b9-428d-4e9a-bbf0-198e3521989d", :comparator :lt, :rhs-val "TQQQ", :step :if-child} {:id "7fe27be7-60fd-4a65-8910-43417ccebb78", :step :if-child, :is-else-condition? true, :children [{:id "29f6db11-ef7a-4b74-86a9-bf0cb0ae27ad", :step :wt-cash-equal, :children [{:id "cf0a0a39-c459-4fc3-a8fb-6f2dac892a8f", :step :if, :children [{:children [{:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "02041b4f-17c6-4d0d-9126-ef98c343cbe2", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SQQQ", :id "e7fca8be-a7bc-4e4b-a01e-a95cc33df202", :comparator :lt, :rhs-val "31", :step :if-child} {:id "c542cf1f-7fd1-474b-8a9c-e5bbd76aed68", :step :if-child, :is-else-condition? true, :children [{:id "2852ed99-dd73-4161-8af2-b813d0ecd1f1", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "75be29f3-44fa-4d37-937f-f9d34a442fa5", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005} {:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "54fa16e7-ffcd-4402-9591-f58ddf972f46", :exchange "ARCX", :price 8.86, :step :asset, :dollar_volume 956756695.38}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "5916d73e-8bfd-4951-8390-c3dc814d194c", :sort-by? true, :step :filter}]}]}]}]}]}]}]}]}]}]}]}]}]}], :collapsed? false}]}]}]} -------------------------------------------------------------------------------- /inputs/bad_inputs/missing_double_quotes.edn: -------------------------------------------------------------------------------- 1 | {:id "2XE43Kcoqa0uLSOBuN3q", :step :root, :name "TQQQ For The Long Term Original (285.1%/45.6%DD) (Invest Copy)", :description "", :rebalance :daily, :children [{:id "8345e902-2baf-4119-b2c5-effa4ac8303e", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id "67e8ca61-be9f-4905-b23f-cd2e8ccb0771", :step :if, :children [{:children [{:id "1f8a7666-6cd4-4c75-8b14-0ad6d44fb7c7", :step :wt-cash-equal, :children [{:id "5fa30ba9-f2c8-47ac-98a5-bcee9f1ca738", :step :if, :children [{:children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "79a1d885-ac42-4ef6-b91e-e4837f164668", :exchange "BATS", :price 10.52, :step :asset, :dollar_volume 1174265281}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "5873b8cd-fa33-4d13-87f4-b41470b281e9", :comparator :gt, :rhs-val "79", :step :if-child, :collapsed? false} {:id "f039f86c-853c-40d1-8c48-45d39ce489e2", :step :if-child, :is-else-condition? true, :children [{:id "6fe5eb7e-9275-4a0f-a4c8-4da11c4fda95", :step :wt-cash-equal, :children [{:id "d59de8e7-9ee2-42a7-8b0e-a277373261f3", :step :if, :children [{:children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "eef73414-0816-40fb-aea5-1ebaf300c610", :exchange "BATS", :price 9.91, :step :asset, :dollar_volume 650525430.03}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SPXL", :id "151b8783-383a-4aba-85ba-fcea5d1293a0", :comparator :gt, :rhs-val "80", :step :if-child} {:id "9a8ae941-4ff3-414e-a150-210b29eccc4a", :step :if-child, :is-else-condition? true, :children [{:id "79dfcf27-428c-406b-af34-aa46e6d475cd", :step :wt-cash-equal, :children [{:id "b0cb3318-a869-42be-89b1-320c40b30867", :step :if, :children [{:children [{:id "baa5a462-d751-4b93-929b-062be3959ecc", :step :wt-cash-equal, :children [{:id "f669489b-b06c-4164-a40a-5d528d5e44e5", :step :if, :children [{:children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "a7f71c31-3cb4-4d19-8ab6-8eeaa1b30879", :exchange "XNAS", :price 21.31, :step :asset, :dollar_volume 5938887884.969999}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "b3008001-2da1-4c3c-8d73-b6b878cf05d7", :comparator :lt, :rhs-val "31", :step :if-child} {:id "ff951ad9-d2f1-48a8-a33d-995d9061fd19", :step :if-child, :is-else-condition? true, :children [{:id "f8bdd39c-2108-4f85-ac9d-de56296d7f2d", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "c163a608-f74c-497b-bcdc-221c286c8d5f", :exchange "BATS", :price 12.85, :step :asset, :dollar_volume 904391596.65} {:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "6952fd06-7584-4524-94e1-47c0af6aec63", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "1955962d-a85f-45d3-b74c-808cc78ec0c5", :sort-by? true, :step :filter}]}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :lhs-window-days "5", :lhs-val "TQQQ", :id "453bddf7-c718-4aa6-ade0-81fbdb5fefbd", :comparator :gt, :rhs-val "20", :step :if-child, :collapsed? false} {:id "a4405ff4-7527-45dc-a20d-89335e04fa25", :step :if-child, :is-else-condition? true, :children [{:id "4bc98db1-96f4-4896-82e1-07bcb69634a4", :step :wt-cash-equal, :children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :weight {:num "40", :den 100}, :id "bdebff71-6249-4e79-8ab2-d731483d2901", :exchange "XNAS", :price 17.57, :step :asset, :dollar_volume 5850985155.33}]}]}]}]}]}]}], :collapsed? false}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "200", :lhs-val "SPY", :id "2a21ed78-76e8-4501-ba8b-52e57851fd65", :comparator :gt, :rhs-val "SPY", :step :if-child, :collapsed? false} {:id "62a48ef3-4178-4d16-ba27-7cb531c433ea", :step :if-child, :is-else-condition? true, :children [{:id "a565dc85-ac28-44af-a8a5-93d726364836", :step :wt-cash-equal, :children [{:id "8013e504-2fd7-413f-8768-05e656de97d7", :step :if, :children [{:children [{:id "30c32d8a-1aba-42e0-912e-429830c5aee7", :step :wt-cash-equal, :children [{:id "0f32ede0-e650-4b2e-ba34-a04a9dfb2052", :step :wt-cash-equal, :children [{:id "a867632f-84a3-42cc-815a-07f65fee04bc", :step :group, :name "Tech", :children [{:step :wt-cash-equal, :id "88730c78-d306-4fc6-9e60-e91a47b8e567", :children [{:name "Direxion Daily Technology Bull 3x Shares", :ticker "TECL", :has_marketcap false, :id "8264631c-7635-47df-a649-9009c1a93612", :exchange "ARCX", :price 21.48, :step :asset, :dollar_volume 103117876.08} {:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "dfa4cd69-2b4d-43ca-a9e7-0edf51630d4f", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005}]}]} {:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "371405a0-e7e2-4b3a-9b80-7c64973a76e1", :exchange "ARCX", :price 8.86, :step :asset, :dollar_volume 956756695.38}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "865d1809-45a9-4517-b189-2e065cf2dc80", :comparator :lt, :rhs-val "31", :step :if-child} {:id "299a682e-ea52-49e9-9d9e-b0f08db7cbb5", :step :if-child, :is-else-condition? true, :children [{:id "cc6be75d-7db5-494f-9ee5-9e7264c1675f", :step :wt-cash-equal, :children [{:id "47c8bae9-a5d4-4dda-803b-a0397c5bbfe9", :step :if, :children [{:children [{:name "ProShares UltraPro S&P500", :ticker "UPRO", :has_marketcap false, :id "db08ecb5-3281-4ef1-9731-46e47ec4549e", :exchange "ARCX", :price 28.41, :step :asset, :dollar_volume 525276865.14}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SPY", :id "8c96da44-2efa-40bd-ba37-e7ea6719fb11", :comparator :lt, :rhs-val "30", :step :if-child} {:id "79e131e3-ce2a-4819-964f-d3b99efd072c", :step :if-child, :is-else-condition? true, :children [{:id "636f9abc-4253-430d-ac46-430cce15d448", :step :wt-cash-equal, :children [{:id "a4a9bb30-44a6-458a-95e3-772e71549278", :step :if, :children [{:children [{:id "b0215dd1-2970-4102-b05b-30d326aada07", :step :wt-cash-equal, :children [{:id "bbb6762b-839a-485d-9c40-3fa9efe12a91", :step :if, :children [{:children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "a69ad012-1768-4998-8dfc-b47ccfec3d5a", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :lhs-window-days "10", :lhs-val "SQQQ", :id "d804b71b-d96f-4fbc-9fff-6c7bd54d7c62", :comparator :gt, :rhs-val "23", :step :if-child} {:id "9e194caa-d8d6-49ab-afe7-174291e2db7c", :step :if-child, :is-else-condition? true, :children [{:id "761d7852-24f8-455f-a53c-2caf7652e535", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "1658b6fa-48e3-40f5-a39e-eddf5f69eda1", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64} {:name "iShares 20+ Year Treasury Bond ETF", :ticker "TLT", :has_marketcap false, :id "2d5ad9e9-c136-4102-916f-db9456d38c52", :exchange "XNAS", :price 98.57, :step :asset, :dollar_volume 1805488454.55, :children-count 0}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "f0f4e5be-024e-445d-9e93-9c0e120bf7db", :sort-by? true, :step :filter} {:select? true, :children [{:name "Direxion Daily Semiconductor Bear 3x Shares", :ticker "SOXS", :has_marketcap false, :id "c673a16e-5669-4afc-82a8-4c942207e670", :exchange "ARCX", :price 70.73, :step :asset, :dollar_volume 971448045.8100001} {:name "iShares 20+ Year Treasury Bond ETF", :ticker "TLT", :has_marketcap false, :id "0f0d2c60-8510-47a1-aba5-25380e0689a3", :exchange "XNAS", :price 98.57, :step :asset, :dollar_volume 1805488454.55, :children-count 0}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "084f66d5-7a7f-49af-b1b8-7e19b481dd27", :sort-by? true, :step :filter}]}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "20", :lhs-val "TQQQ", :id "82a8f8b9-428d-4e9a-bbf0-198e3521989d", :comparator :lt, :rhs-val "TQQQ", :step :if-child} {:id "7fe27be7-60fd-4a65-8910-43417ccebb78", :step :if-child, :is-else-condition? true, :children [{:id "29f6db11-ef7a-4b74-86a9-bf0cb0ae27ad", :step :wt-cash-equal, :children [{:id "cf0a0a39-c459-4fc3-a8fb-6f2dac892a8f", :step :if, :children [{:children [{:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "02041b4f-17c6-4d0d-9126-ef98c343cbe2", :exchange "XNAS", :price 61.32, :step :asset, :dollar_volume 9655977740.64}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SQQQ", :id "e7fca8be-a7bc-4e4b-a01e-a95cc33df202", :comparator :lt, :rhs-val "31", :step :if-child} {:id "c542cf1f-7fd1-474b-8a9c-e5bbd76aed68", :step :if-child, :is-else-condition? true, :children [{:id "2852ed99-dd73-4161-8af2-b813d0ecd1f1", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "75be29f3-44fa-4d37-937f-f9d34a442fa5", :exchange "XNAS", :price 19.32, :step :asset, :dollar_volume 4901224899.4800005} {:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "54fa16e7-ffcd-4402-9591-f58ddf972f46", :exchange "ARCX", :price 8.86, :step :asset, :dollar_volume 956756695.38}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "5916d73e-8bfd-4951-8390-c3dc814d194c", :sort-by? true, :step :filter}]}]}]}]}]}]}]}]}]}]}]}]}]}], :collapsed? false}]}]}]} -------------------------------------------------------------------------------- /lib/traversers.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import typing 3 | 4 | from . import logic, human, symphony_object 5 | 6 | 7 | def collect_allocateable_assets(node) -> typing.Set[str]: 8 | s = set() 9 | if logic.is_asset_node(node): 10 | s.add(logic.get_ticker_of_asset_node(node)) 11 | return s 12 | 13 | for child in logic.get_node_children(node): 14 | s.update(collect_allocateable_assets(child)) 15 | return s 16 | 17 | 18 | def collect_if_referenced_assets(node) -> typing.Set[str]: 19 | """ 20 | Collects tickers referenced by if-conditions 21 | """ 22 | s = set() 23 | if logic.is_conditional_node(node): 24 | lhs_ticker = logic.get_lhs_ticker(node) 25 | if lhs_ticker: 26 | s.add(lhs_ticker) 27 | rhs_ticker = logic.get_rhs_ticker(node) 28 | if rhs_ticker: 29 | s.add(rhs_ticker) 30 | 31 | for child in logic.get_node_children(node): 32 | s.update(collect_if_referenced_assets(child)) 33 | return s 34 | 35 | 36 | def collect_referenced_assets(node) -> typing.Set[str]: 37 | s = set() 38 | s.update(collect_if_referenced_assets(node)) 39 | s.update(collect_allocateable_assets(node)) 40 | return s 41 | 42 | 43 | def extract_lhs_indicator(node): 44 | return { 45 | "fn": node[":lhs-fn"], 46 | "val": node[":lhs-val"], 47 | "window-days": int(node.get(":lhs-window-days", 0)), 48 | } 49 | 50 | 51 | def extract_rhs_indicator(node) -> typing.Optional[dict]: 52 | if node.get(':rhs-fixed-value?', False): 53 | return 54 | return { 55 | "fn": node[":rhs-fn"], 56 | "val": node[":rhs-val"], 57 | "window-days": int(node.get(":rhs-window-days", 0)), 58 | } 59 | 60 | 61 | def extract_filter_indicators(node) -> typing.List[dict]: 62 | indicators = [] 63 | for ticker in [logic.get_ticker_of_asset_node(child) for child in logic.get_node_children(node)]: 64 | indicators.append({ 65 | "fn": node[":sort-by-fn"], 66 | "val": ticker, 67 | "window-days": int(node[":sort-by-window-days"]), 68 | }) 69 | return indicators 70 | 71 | 72 | def extract_inverse_volatility_indicators(node) -> typing.List[dict]: 73 | indicators = [] 74 | for ticker in [logic.get_ticker_of_asset_node(child) for child in logic.get_node_children(node)]: 75 | indicators.append({ 76 | "fn": logic.ComposerIndicatorFunction.STANDARD_DEVIATION_RETURNS, 77 | "val": ticker, 78 | "window-days": int(node[":window-days"]), 79 | }) 80 | return indicators 81 | 82 | 83 | def collect_indicators(node, parent_node_branch_state=None) -> typing.List[dict]: 84 | """ 85 | Collects indicators referenced 86 | """ 87 | 88 | if not parent_node_branch_state: 89 | # current node is :root, there is no higher node 90 | parent_node_branch_state = logic.build_node_branch_state_from_root_node( 91 | node) 92 | parent_node_branch_state = typing.cast( 93 | logic.NodeBranchState, parent_node_branch_state) 94 | 95 | current_node_branch_state = logic.advance_branch_state( 96 | parent_node_branch_state, node) 97 | 98 | indicators = [] 99 | if logic.is_conditional_node(node): 100 | indicator = extract_lhs_indicator(node) 101 | indicator.update({ 102 | "source": ":if-child lhs", 103 | "branch_path_ids": copy.copy(current_node_branch_state.branch_path_ids), 104 | "weight": current_node_branch_state.weight, 105 | }) 106 | indicators.append(indicator) 107 | 108 | indicator = extract_rhs_indicator(node) 109 | if indicator: 110 | indicator.update({ 111 | "source": ":if-child rhs", 112 | "branch_path_ids": copy.copy(current_node_branch_state.branch_path_ids), 113 | "weight": current_node_branch_state.weight, 114 | }) 115 | indicators.append(indicator) 116 | 117 | if logic.is_filter_node(node): 118 | for indicator in extract_filter_indicators(node): 119 | indicator.update({ 120 | "source": ":filter sort-by", 121 | "branch_path_ids": copy.copy(current_node_branch_state.branch_path_ids), 122 | "weight": current_node_branch_state.weight, 123 | }) 124 | indicators.append(indicator) 125 | 126 | if logic.is_weight_inverse_volatility_node(node): 127 | for indicator in extract_inverse_volatility_indicators(node): 128 | indicator.update({ 129 | "source": ":wt-inverse-vol", 130 | "branch_path_ids": copy.copy(current_node_branch_state.branch_path_ids), 131 | "weight": current_node_branch_state.weight, 132 | }) 133 | indicators.append(indicator) 134 | 135 | for child in logic.get_node_children(node): 136 | indicators.extend(collect_indicators( 137 | child, parent_node_branch_state=current_node_branch_state)) 138 | return indicators 139 | 140 | 141 | def collect_conditions(node) -> typing.List[dict]: 142 | """ 143 | Collects :if-child conditions used 144 | """ 145 | 146 | conditions = [] 147 | if logic.is_conditional_node(node): 148 | copy_node = copy.deepcopy(node) 149 | del copy_node[":children"] 150 | del copy_node[":step"] 151 | copy_node["pretty_text"] = human.pretty_condition(node) 152 | conditions.append(copy_node) 153 | 154 | for child in logic.get_node_children(node): 155 | conditions.extend(collect_conditions(child)) 156 | return conditions 157 | 158 | 159 | def collect_terminal_branch_paths(node, parent_node_branch_state: typing.Optional[logic.NodeBranchState] = None) -> typing.Set[str]: 160 | if not parent_node_branch_state: 161 | # current node is :root, there is no higher node 162 | parent_node_branch_state = logic.build_node_branch_state_from_root_node( 163 | node) 164 | parent_node_branch_state = typing.cast( 165 | logic.NodeBranchState, parent_node_branch_state) 166 | 167 | current_node_branch_state = logic.advance_branch_state( 168 | parent_node_branch_state, node) 169 | 170 | branch_paths = set() 171 | if logic.is_asset_node(node): 172 | branch_paths.add("/".join(current_node_branch_state.branch_path_ids)) 173 | 174 | for child in logic.get_node_children(node): 175 | branch_paths.update(collect_terminal_branch_paths( 176 | child, parent_node_branch_state=current_node_branch_state)) 177 | 178 | return branch_paths 179 | 180 | 181 | def collect_condition_strings_by_id(node, parent_node=None, parent_node_branch_state: typing.Optional[logic.NodeBranchState] = None) -> typing.Mapping[str, str]: 182 | """ 183 | Collects all conditional logics of each path leading out of an :if and :if-child block (including 'else' logic and earlier 'if's) 184 | """ 185 | 186 | if not parent_node_branch_state: 187 | # current node is :root, there is no higher node 188 | parent_node_branch_state = logic.build_node_branch_state_from_root_node( 189 | node) 190 | parent_node_branch_state = typing.cast( 191 | logic.NodeBranchState, parent_node_branch_state) 192 | 193 | current_node_branch_state = logic.advance_branch_state( 194 | parent_node_branch_state, node) 195 | 196 | condition_strings_by_id = {} 197 | if logic.is_if_child_node(node): 198 | siblings = logic.get_node_children(parent_node) 199 | current_index, _ = next(filter( 200 | lambda index_sibling_node_tuple: index_sibling_node_tuple[1][":id"] == node[":id"], enumerate(siblings))) 201 | older_siblings = siblings[:current_index] 202 | 203 | condition_string_sibling_aware = "" 204 | if older_siblings: 205 | condition_string_sibling_aware += " and ".join( 206 | ["(not " + human.pretty_condition(s) + ")" for s in older_siblings]) 207 | if logic.is_conditional_node(node) and older_siblings: 208 | condition_string_sibling_aware += " and " 209 | if logic.is_conditional_node(node): 210 | condition_string_sibling_aware += human.pretty_condition(node) 211 | 212 | condition_strings_by_id[node[":id"]] = condition_string_sibling_aware 213 | 214 | for child in logic.get_node_children(node): 215 | child_condition_strings_by_id = collect_condition_strings_by_id( 216 | child, parent_node=node, parent_node_branch_state=current_node_branch_state) 217 | condition_strings_by_id.update(child_condition_strings_by_id) 218 | return condition_strings_by_id 219 | 220 | 221 | def collect_branches(root_node) -> typing.Mapping[str, str]: 222 | """ 223 | Returns human-readable expressions of all the logic involved to get to an :asset node. 224 | """ 225 | branch_paths = collect_terminal_branch_paths(root_node) 226 | condition_strings_by_id = collect_condition_strings_by_id(root_node) 227 | 228 | branches_by_path = {} 229 | for branch_path in branch_paths: 230 | conditional_ids = branch_path.split("/") 231 | condition_strings = [condition_strings_by_id.get(condition_id, "[always]") 232 | for condition_id in conditional_ids] 233 | branches_by_path[branch_path] = " AND ".join(condition_strings) 234 | return branches_by_path 235 | 236 | 237 | # TODO: collect parameters we might optimize 238 | # - periods 239 | # - if :rhs-fixed-value, then :rhs-val 240 | # - stuff in :filter 241 | 242 | 243 | def collect_nodes_of_type(step: str, node: dict) -> typing.List[dict]: 244 | s = [] 245 | if node[":step"] == step: 246 | s.append(node) 247 | for child_node in logic.get_node_children(node): 248 | s.extend(collect_nodes_of_type(step, child_node)) 249 | return s 250 | 251 | 252 | def main(): 253 | import pprint 254 | 255 | symphony_id = "RspMV6gSM7tX3x6yEseZ" 256 | symphony = symphony_object.get_symphony(symphony_id) 257 | root_node = symphony_object.extract_root_node_from_symphony_response( 258 | symphony) 259 | 260 | assert not collect_nodes_of_type( 261 | ":wt-inverse-vol", root_node), "Inverse volatility weighting is not supported." 262 | assert not collect_nodes_of_type( 263 | ":wt-marketcap", root_node), "Market cap weighting is not supported." 264 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /symphony_parser.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | 4 | ''' 5 | 6 | lets build a text parser that reads in a raw symphony payload, and can export it to whatever endpoint we like 7 | human readable output 8 | quantconnect 9 | vectorbt 10 | tradingview 11 | thinkscript 12 | 13 | 14 | ''' 15 | import edn_format 16 | import traceback 17 | import requests 18 | import argparse 19 | import random 20 | import typing 21 | import string 22 | import time 23 | import copy 24 | import json 25 | import sys 26 | import re 27 | 28 | from lib import edn_syntax, transpilers 29 | 30 | 31 | class InFileReader: 32 | 33 | def __init__(self, filePath :string, resp :dict): 34 | self.filePath = filePath 35 | self.resp = resp 36 | self.data = None 37 | self.root_node = None 38 | return 39 | 40 | 41 | def printHeader(self, url_loaded = False): 42 | # 'latest_backtest_info', 'latest_backtest_edn', 'latest_version', 'hashtag', 'owner', 'description', 'created_at', 'latest_version_edn', 'sparkgraph_url', 'color', 'name', 'share-with-everyone?', 'stats', 'last_updated_at', 'youtube-url', 'cached_rebalance', 'latest_backtest_run_at', 'cached_rebalance_corridor_width', 'copied-from', 'backtest_url']) 43 | 44 | if url_loaded: 45 | 46 | owner = "UNKNOWN" 47 | created = "UNKNOWN" 48 | name = "UNKNOWN" 49 | description = "UNKNOWN" 50 | symph_name = "UNKNOWN" 51 | 52 | 53 | if "owner" in self.resp: 54 | owner = self.resp['owner'] 55 | if "created" in self.resp: 56 | owner = self.resp['created'] 57 | if "name" in self.resp: 58 | owner = self.resp['name'] 59 | if "description" in self.resp: 60 | description = self.resp['description'] 61 | if self.root_node: 62 | symph_name = self.root_node["name"] 63 | 64 | 65 | print("""\r\n========================================================\r\n 66 | owner:......... %s\r\n 67 | creator_name:.. %s\r\n 68 | created at:.... %s \r\n 69 | symphony name:. %s\r\n 70 | description at: %s \r\n 71 | """ % (owner, name, created, symph_name, description )) 72 | 73 | 74 | def readFile(self, url_loaded = False): 75 | try: 76 | # Data is wrapped with "" and has escaped all the "s, this de-escapes 77 | if not url_loaded: 78 | data_with_wrapping_string_removed = json.load( 79 | open(self.filePath, 'r')) 80 | else: 81 | print("I'm fancy and url loaded already\r\n") 82 | data_with_wrapping_string_removed = self.resp['fields']['latest_version_edn']['stringValue'] 83 | root_data_immutable = edn_format.loads( 84 | data_with_wrapping_string_removed) 85 | self.data = typing.cast( 86 | dict, edn_syntax.convert_edn_to_pythonic(root_data_immutable)) 87 | 88 | self.root_node = self.data 89 | if ":symphony" in self.data: 90 | self.root_node = self.data[":symphony"] 91 | 92 | except (NameError, TypeError) as exception_error: 93 | 94 | print(exception_error) 95 | 96 | my_traceback = traceback.format_exc() # returns a str 97 | print(my_traceback) 98 | 99 | root_data_immutable = edn_format.loads(open(self.filePath, 'r').read()) 100 | self.data = typing.cast( 101 | dict, edn_syntax.convert_edn_to_pythonic(root_data_immutable)) 102 | 103 | self.root_node = self.data 104 | if ":symphony" in self.data: 105 | self.root_node = self.data[":symphony"] 106 | ''' 107 | if self.file_contents_string == None: 108 | with open(self.filePath, "r") as infile: 109 | self.file_contents_string = infile.read() 110 | # 'inputFile.edn' 111 | data_with_wrapping_string_removed = json.load(self.file_contents_string) 112 | self.data = edn_format.loads(data_with_wrapping_string_removed) 113 | else: 114 | self.data = edn_format.loads(self.file_contents_string) 115 | ''' 116 | #>>> edn_format.dumps({1, 2, 3}) 117 | #'#{1 2 3}' 118 | 119 | return 120 | 121 | 122 | class OutfileBase: 123 | def __init__(self): 124 | return 125 | 126 | class OutfileHuman(OutfileBase): 127 | def __init__(self, filePath): 128 | super().__init__() 129 | self.filePath = filePath 130 | return 131 | 132 | def show(self, data): 133 | print(transpilers.HumanTextTranspiler.convert_to_string(data)) 134 | 135 | def get_text(self, data): 136 | return transpilers.HumanTextTranspiler.convert_to_string(data) 137 | 138 | class OutfileQuantConnect(OutfileBase): 139 | def __init__(self): 140 | super().__init__() 141 | return 142 | 143 | def show(self, data): 144 | print(transpilers.QuantConnectTranspiler.convert_to_string(data)) 145 | 146 | 147 | #TODO finish 148 | class OutfileVectorBt(OutfileBase): 149 | def __init__(self): 150 | super().__init__() 151 | return 152 | 153 | def show(self, data): 154 | print(transpilers.VectorBTTranspiler.convert_to_string(data)) 155 | 156 | 157 | #TODO arg parser for inputs: input file, output file, output mode 158 | def main()-> int: 159 | parser = argparse.ArgumentParser(description='Composer Symphony text parser') 160 | parser.add_argument('-i','--infile', dest="infile", action="store", help=' input file we read the symphony text from. full path please', required=True) 161 | parser.add_argument('-o','--outfile', dest="outfile", action="store", default="OUTFILE", help=' output file to save the parsed text to. if not given, will use stdout', required=False) 162 | parser.add_argument('-m','--mode', dest="mode", action="store", default="human", help=' output parsing mode to use. if none given, will parse for "human readable output". modes are: quantconnect, vectorbt, tradingview, thinkscript', required=False) 163 | parser.add_argument('-b', '--bulk', action="store_true", dest='bulk', default='False', help="it means the specified input is a filepath, containing a bulk list of urls, or filenames to process. one url or file path per line") 164 | parser.add_argument('-u', '--url', action="store_true", dest='url', default='False', help="specifies that the input file path is actually the url to a shared, public symphony on composer.trade") 165 | parser.add_argument('-p', '--parent', action="store_true", dest='parent', default='False', help="specifies that we should try and look up the parents of this symphony, and get all previous copied information too. only works if the 'infile' given was a url") 166 | 167 | 168 | args = vars(parser.parse_args()) 169 | 170 | if args['url'] == True: 171 | url_list = [] 172 | if args['bulk'] == True: 173 | # read bulk file and add them all to the "list" 174 | with open(args['infile'], 'r') as bulkfile: 175 | for line in bulkfile.readlines(): 176 | url_list.append(line) 177 | else: 178 | url_list.append(args['infile']) 179 | # just add the single one to the list and have it process the "list" anyways 180 | 181 | 182 | #todo move to request this from the composer site instead of hardcoding 183 | composerConfig = { 184 | "projectId" : "leverheads-278521", 185 | "databaseName" : "(default)" 186 | } 187 | total = len(url_list) 188 | for count, current_url in enumerate(url_list): 189 | 190 | print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-") 191 | print("****************************************************************************************************************") 192 | print(" starting a new bulk lookup %s of %s " % (str(count), str(total))) 193 | print("****************************************************************************************************************") 194 | print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-") 195 | 196 | m = re.search('\/symphony\/([^\/]+)', current_url) 197 | symphId = m.groups(1)[0] 198 | 199 | current_symph_id = symphId 200 | response_list = [] 201 | while current_symph_id: 202 | symphReq = requests.get(f'https://firestore.googleapis.com/v1/projects/{composerConfig["projectId"]}/databases/{composerConfig["databaseName"]}/documents/symphony/{symphId}') 203 | resp = json.loads(symphReq.text) 204 | # 'latest_backtest_info', 'latest_backtest_edn', 'latest_version', 'hashtag', 'owner', 'description', 'created_at', 'latest_version_edn', 'sparkgraph_url', 'color', 'name', 'share-with-everyone?', 'stats', 'last_updated_at', 'youtube-url', 'cached_rebalance', 'latest_backtest_run_at', 'cached_rebalance_corridor_width', 'copied-from', 'backtest_url']) 205 | 206 | if 'fields' not in resp: 207 | print("\r\nWas this a private symphony link? response 'object' had no 'fields' key. could not parse\r\n Error 2") 208 | #sys.exit(2) 209 | current_symph_id = None 210 | 211 | if args['parent'] == True: 212 | if 'copied-from' in resp['fields']: 213 | print("old id \r\n %s \r\nnew id \r\n %s\r\n" % (current_symph_id, resp['fields']['copied-from']['stringValue'])) 214 | if current_symph_id == resp['fields']['copied-from']['stringValue']: 215 | print("copied from fields are the same, no more parents, ending the lookups") 216 | # set current id to None, which should end our loop, and let us start looping over all our responses 217 | current_symph_id = None 218 | else: 219 | # different parent id found, copy it, and then lets loop over and grab the next one 220 | current_symph_id = resp['fields']['copied-from']['stringValue'] 221 | else: 222 | current_symph_id = None 223 | sleep_time = 2 + 20 * random.random() 224 | print("-->random delay of %s between requests" % str(sleep_time)) 225 | time.sleep(sleep_time) 226 | 227 | # user did not ask for a "symphony parent lookup, so we will not try to loop over things 228 | else: 229 | print("---> skipping symphony parent check") 230 | current_symph_id = None 231 | # whatever the response was, copy it into our master response list. we'll use all the responses later 232 | response_list.append(copy.deepcopy(resp)) 233 | #import pdb; pdb.set_trace() 234 | 235 | for resp in response_list: 236 | print(json.dumps(resp['fields']['latest_version_edn']['stringValue'], indent=2)) 237 | inFileParser = InFileReader(None, resp) 238 | inFileParser.printHeader(url_loaded = True) 239 | inFileParser.readFile(url_loaded = True) 240 | 241 | if args["mode"] == "human": 242 | humanParser = OutfileHuman(args["infile"]) 243 | humanParser.show(inFileParser.root_node) 244 | 245 | if args["mode"] == "vector": 246 | vectorParser = OutfileVectorBt() 247 | vectorParser.show(inFileParser.data) 248 | 249 | if args["mode"] == "quantconnect": 250 | quantconnectParser = OutfileQuantConnect() 251 | humanParser = OutfileHuman(args["infile"]) 252 | quantconnectParser.show( 253 | humanParser.get_text(inFileParser.root_node) 254 | ) 255 | 256 | else: 257 | file_list = [] 258 | if args['bulk'] == True: 259 | # read bulk file and add them all to the "list" 260 | with open(args['infile'], 'r') as bulkfile: 261 | for line in bulkfile.readlines(): 262 | file_list.append(line) 263 | else: 264 | file_list.append(args['infile']) 265 | # just add the single one to the list and have it process the "list" anyways 266 | 267 | 268 | for file in file_list: 269 | print(file) 270 | inFileParser = InFileReader(file, None) 271 | inFileParser.readFile() 272 | 273 | 274 | if args["mode"] == "human": 275 | humanParser = OutfileHuman(file) 276 | humanParser.show(inFileParser.root_node) 277 | 278 | if args["mode"] == "vector": 279 | vectorParser = OutfileVectorBt() 280 | vectorParser.show(inFileParser.data) 281 | 282 | if args["mode"] == "quantconnect": 283 | quantconnectParser = OutfileQuantConnect() 284 | humanParser = OutfileHuman(args["infile"]) 285 | quantconnectParser.show( 286 | humanParser.show(inFileParser.root_node) 287 | ) 288 | 289 | return 0 290 | #TODO make generic class for output mode 291 | 292 | 293 | 294 | #TODO make child classes for specific output modes 295 | 296 | 297 | #TODO start listing out initial requirements for each specific output mode 298 | 299 | 300 | if __name__ == '__main__': 301 | sys.exit(main()) -------------------------------------------------------------------------------- /inputs/betaballer-modified.edn: -------------------------------------------------------------------------------- 1 | {:id "ZW1grjRSmuu2Ylmpl48p", :step :root, :name "Copy of V 2.2 | ☢️ Beta Baller | Deez, BrianE, HinnomTX, DereckN, Garen | SMH 4 Long Term Mod | AR: 6305%, DD 50.7% - BT date 1DEC19 (Invest Copy)", :description "If you take out the half and half last section from the v1.21 and go full YOLO with the SMH for the long term to keep the bot all semiconductor focused. Drawdown increased but massive gains increase as well. V2.1 tune up by HinnomTX (2201% to 4615% from 12/2019)", :rebalance :daily, :children [{:id "176009ec-b734-4c82-a976-08e6befa03d9", :step :wt-cash-equal, :children [{:id "61cbf381-7a8d-4942-92b1-c6d71b4ee579", :step :group, :name "Beta Baller v2.2 - SMH 4 Long Term Mod | EVO date 11OCT22", :children [{:step :wt-cash-equal, :id "a4891da3-1db4-4173-9e8c-77adcadf95f5", :children [{:id "d9eae6e1-912d-4360-9f5c-5797441820f3", :step :if, :children [{:children [{:id "ab20bef1-8f61-451d-a6c0-1bdcda11dec8", :step :wt-cash-equal, :children [{:id "380f031e-e191-40a1-ba3f-5c9dec42f2f1", :step :if, :children [{:children [{:id "2542f298-8c9b-489f-881c-ca11d0e49ab2", :step :group, :name "Overbought S&P. Sell the rip. Buy volatility.", :children [{:step :wt-cash-equal, :id "80d2c529-ec3c-4d26-8576-76d5d00f52f0", :children [{:select? true, :children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "996a1eaa-eb63-4cd3-8694-6e788f591c53", :exchange "BATS", :price 11.45, :step :asset, :dollar_volume 453424499.84999996} {:name "ProShares VIX Short-Term Futures ETF", :ticker "VIXY", :has_marketcap false, :id "d9ae6f60-dc62-400a-ad9b-dcb8bf3521d0", :exchange "BATS", :price 15.84, :step :asset, :dollar_volume 149119153.92}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "13", :weight {:num "80", :den 100}, :id "126ea19b-6e2f-4085-a6f2-5641ee6da98f", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "6", :lhs-val "SPY", :id "8ec68c96-ad97-4c2f-be83-5018802cb8f7", :comparator :gt, :rhs-val "75", :step :if-child} {:id "1ac10ba4-c94a-4596-a925-5c8a7eba51cb", :step :if-child, :is-else-condition? true, :children [{:id "a7d5ad61-e0d3-47b6-9fa3-54634daf6c84", :step :wt-cash-equal, :children [{:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "612db849-2678-4a4a-bb09-d807a3993245", :exchange "ARCX", :price 11.19, :step :asset, :dollar_volume 1275587444.04} {:name "Direxion Daily Technology Bear 3X Shares", :ticker "TECS", :has_marketcap false, :id "03df8c45-ed11-42c7-8803-c0e46e211fe7", :exchange "ARCX", :price 46.53, :step :asset, :dollar_volume 153539600.94}]}]}]}]}], :rhs-fn :relative-strength-index, :is-else-condition? false, :lhs-fn :relative-strength-index, :rhs-window-days "7", :lhs-window-days "7", :lhs-val "BIL", :id "d30d7ea4-844b-44ab-a3ce-f96e8476c5eb", :comparator :lt, :rhs-val "IEF", :step :if-child, :collapsed? false} {:id "3d91397f-1c10-4b43-b9a7-a2723995fba2", :step :if-child, :is-else-condition? true, :children [{:id "ddb115f9-4ef1-47e1-bead-57fb48490b9b", :step :wt-cash-equal, :children [{:id "b566ab7d-cc77-4c6b-98a2-544035f98a50", :step :if, :children [{:children [{:id "288bc910-94e8-4e6c-a379-ef39e789eebb", :step :group, :name "Extremely oversold S&P (low RSI). Double check with bond mkt before going long", :children [{:step :wt-cash-equal, :id "a3d4cf26-ce7a-488f-9e33-e257ae5c0ad3", :children [{:id "e5c190e2-22ea-41cf-8a18-eb2e9083af16", :step :if, :children [{:children [{:id "3cecb5a5-3f2f-4d06-b9b2-6e9324bb960a", :step :wt-cash-equal, :children [{:select? true, :children [{:name "Direxion Daily Semiconductor Bear 3x Shares", :ticker "SOXS", :has_marketcap false, :id "621ecc5d-6b41-4c9b-b201-966e37b6c95c", :exchange "ARCX", :price 75.59, :step :asset, :dollar_volume 1573440696.99, :children-count 0} {:name "ProShares UltraPro Short QQQ", :ticker "SQQQ", :has_marketcap false, :id "794353b6-46e1-42f6-a527-6601745f0704", :exchange "XNAS", :price 63.2, :step :asset, :dollar_volume 9876036606.4, :children-count 0}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "7", :id "76d65edc-567f-4b1d-b60c-e6d8effee6f6", :sort-by? true, :step :filter}]}], :rhs-fn :relative-strength-index, :is-else-condition? false, :lhs-fn :relative-strength-index, :rhs-window-days "10", :lhs-window-days "10", :lhs-val "SHY", :id "1bc69eca-7996-4c4e-b770-0ced354fd579", :comparator :lt, :rhs-val "HIBL", :step :if-child} {:id "8e49036a-4a99-4a61-ad96-c20b4b256a45", :step :if-child, :is-else-condition? true, :children [{:id "99be4c96-d2f9-4651-aecb-16b42043a2f7", :step :wt-cash-equal, :children [{:select? true, :children [{:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "7ddf6c10-e6ff-43ae-8bc5-c43ac3d19ee4", :exchange "ARCX", :price 8.28, :step :asset, :dollar_volume 1339027904.8799999} {:name "Direxion Daily Technology Bull 3x Shares", :ticker "TECL", :has_marketcap false, :id "aa3e6d2b-3411-4291-ac0d-d26deb33f480", :exchange "ARCX", :price 21.12, :step :asset, :dollar_volume 91771173.12}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "7", :id "6d5aeae4-0f60-487d-995d-b93859734399", :sort-by? true, :step :filter}]}]}]}]}], :collapsed? false}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "6", :lhs-val "SPY", :id "da6478c0-132a-44f4-bdb9-d6bca6f91424", :comparator :lt, :rhs-val "27", :step :if-child} {:id "7dc13884-b653-43d8-a985-354be2014c21", :step :if-child, :is-else-condition? true, :children [{:id "6e95efe0-db8a-4676-bb0e-863c09fa000f", :step :group, :name "SMH for the Long Term [You can plug any full symphony in this spot to most likely increase returns]", :children [{:step :wt-cash-equal, :id "e4caadb9-1009-471e-8280-a41b528ebc42", :children [{:weight {:num 100, :den 100}, :id "b6e8dcef-b1b1-4b1b-a0fc-87fca7245fd4", :step :if, :children [{:children [{:id "870ca9da-02d1-431d-8383-46d25c9ab179", :step :wt-cash-equal, :children [{:id "b2200fba-5dbd-47c9-b3cf-fbc7cbc50d94", :step :if, :children [{:children [{:id "40dc0916-681f-4d7f-95b5-2ddf357ed48c", :step :wt-cash-equal, :children [{:select? true, :children [{:name "ProShares Ultra VIX Short-Term Futures ETF", :ticker "UVXY", :has_marketcap false, :id "1c62e229-0e4e-4a8b-8e4f-d592dd833861", :exchange "BATS", :price 11.45, :step :asset, :dollar_volume 453424499.84999996} {:name "ProShares VIX Short-Term Futures ETF", :ticker "VIXY", :has_marketcap false, :id "603fb30c-7d1e-44d4-9204-fecb90310322", :exchange "BATS", :price 15.84, :step :asset, :dollar_volume 149119153.92}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "13", :weight {:num "80", :den 100}, :id "e3c90863-42e6-4e42-a5a5-b36f32a89c45", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "TQQQ", :id "1f580ca1-38b9-4f4b-a3f6-a899868a9cf8", :comparator :gt, :rhs-val "79", :step :if-child} {:id "4f824043-4fab-468e-8f50-419b08663456", :step :if-child, :is-else-condition? true, :children [{:id "375d2b6a-2833-43ad-8ac9-9e39f3a1947f", :step :group, :name "Bull Market Strategy", :children [{:step :wt-cash-equal, :id "e357a1f8-65f9-4e3b-b080-18964c1130e7", :children [{:id "cb678e48-c047-4920-815e-544a77f3e0e2", :step :group, :name "Fund Surf - Bottom 1 - 20d RSI", :children [{:step :wt-cash-equal, :id "ae479aa6-bcc4-4263-abc6-8a852b2b5147", :children [{:select? true, :children [{:name "iShares Short Treasury Bond ETF", :ticker "SHV", :has_marketcap false, :id "38230148-55da-47e3-91e9-b9bbccd142f2", :exchange "XNAS", :price 109.9, :step :asset, :dollar_volume 297808009.1, :children-count 0} {:name "Direxion Daily Financial Bull 3x Shares", :ticker "FAS", :has_marketcap false, :id "60c77cb6-1f40-4c7a-af95-8b42299b0d30", :exchange "ARCX", :price 53.74, :step :asset, :dollar_volume 113059501.76, :children-count 0} {:name "ProShares UltraPro QQQ", :ticker "TQQQ", :has_marketcap false, :id "9e7e2429-b2c5-4c44-9362-d1564025363f", :exchange "XNAS", :price 18.14, :step :asset, :dollar_volume 4762612194.2, :children-count 0} {:name "ProShares UltraPro S&P500", :ticker "UPRO", :has_marketcap false, :id "c7c98889-a4e9-463c-8714-65e044a95fb2", :exchange "ARCX", :price 28.23, :step :asset, :dollar_volume 490564933.59000003, :children-count 0} {:name "Direxion Daily Technology Bull 3x Shares", :ticker "TECL", :has_marketcap false, :id "bf71e797-f912-4a3c-a7bd-51263fb555f7", :exchange "ARCX", :price 20.14, :step :asset, :dollar_volume 133496499.64, :children-count 0}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "21", :weight {:num 100, :den 100}, :id "6208839e-3546-4b73-ad58-b5936f4328b2", :sort-by? true, :step :filter, :collapsed? false}]}], :collapsed? false}]}], :collapsed? false}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "200", :lhs-val "SPY", :id "08f94631-c928-4573-af77-e416001f45dc", :comparator :gt, :rhs-val "SPY", :step :if-child} {:id "66561c3d-a8e1-483e-b641-41cca9f4159a", :step :if-child, :is-else-condition? true, :children [{:id "ef514580-e9a4-4ecd-a1d1-9f2fccc68cda", :step :group, :name "DN's Simple Bear Market Strategy (SMH)", :children [{:step :wt-cash-equal, :id "68c459e2-3b20-401d-b98d-4e23d00d3fa9", :children [{:id "b3be6e99-040a-4bd7-963d-a106b28fad25", :step :if, :children [{:children [{:id "ff19dd7b-5b4f-4e77-8ab9-4e4179184cb5", :step :wt-cash-equal, :children [{:select? true, :children [{:name "iShares 1-3 Year Treasury Bond ETF", :ticker "SHY", :has_marketcap false, :id "469c13e3-6e76-484c-9087-cfc82b437a73", :exchange "XNAS", :price 81.04, :step :asset, :dollar_volume 310618783.28000003, :children-count 0}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :weight {:num "80", :den 100}, :id "664b72a9-330a-4bb8-9656-48c6654cea5d", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "QQQ", :id "a1412c3f-4c35-44a7-a444-bcc99cb0baf8", :comparator :lt, :rhs-val "30", :step :if-child} {:id "ea49e0ab-a77e-4fef-a4f7-7802f44867a2", :step :if-child, :is-else-condition? true, :children [{:id "cd8100fb-2ee2-4694-827f-3643823a0009", :step :wt-cash-equal, :children [{:id "c4e0e98e-4912-41c5-93ea-f686ccd3f8f8", :step :if, :children [{:children [{:id "808ebb00-b349-45ab-9d69-9c5129740c74", :step :wt-cash-equal, :children [{:select? true, :children [{:name "Direxion Daily S&P 500 Bull 3x Shares", :ticker "SPXL", :has_marketcap false, :id "eab39259-2d3d-4019-b35a-f9240eb7ae68", :exchange "ARCX", :price 53.28, :step :asset, :dollar_volume 964010917.44, :children-count 0} {:name "iShares 1-3 Year Treasury Bond ETF", :ticker "SHY", :has_marketcap false, :id "d2f739c5-8e34-424c-9830-fde3865aeddc", :exchange "XNAS", :price 81.04, :step :asset, :dollar_volume 310618783.28000003}], :select-fn :bottom, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :weight {:num "80", :den 100}, :id "e9f89322-3b1a-4c63-adf2-f00bcac38136", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SPY", :id "b8c2416e-78d8-4349-af5e-24029b43d363", :comparator :lt, :rhs-val "30", :step :if-child} {:id "20eba2ab-bba9-47af-89e8-8a43729204c4", :step :if-child, :is-else-condition? true, :children [{:id "63f63d45-38f8-4f7f-9a0b-3200990e60a6", :step :wt-cash-equal, :children [{:id "a5bd249b-31ed-43e1-a359-6228f7cf185e", :step :if, :children [{:children [{:id "d2627b63-6180-4fc0-9df6-10ace69d5f24", :step :wt-cash-equal, :children [{:id "db54f00d-2429-4ebf-b55b-9953e52c793f", :step :if, :children [{:children [{:id "1e0a1e3d-f133-4280-9977-6e7ce9e909bf", :step :wt-cash-equal, :children [{:select? true, :children [{:name "Direxion Daily Semiconductor Bear 3x Shares", :ticker "SOXS", :has_marketcap false, :id "f040e8c1-18f8-422b-8b14-0c8956363e06", :exchange "ARCX", :price 63.21, :step :asset, :dollar_volume 1329586952.4} {:name "Invesco DB US Dollar Index Bullish Fund", :ticker "UUP", :has_marketcap false, :id "ff869ab3-0437-4740-9cb4-1997ac048fa0", :exchange "ARCX", :price 30.45, :step :asset, :dollar_volume 147174989.85, :children-count 0} {:name "iShares 1-3 Year Treasury Bond ETF", :ticker "SHY", :has_marketcap false, :id "76f110c1-47a5-4aba-b555-0270937d65c9", :exchange "XNAS", :price 80.94, :step :asset, :dollar_volume 419126664.65999997}], :select-fn :bottom, :select-n "2", :sort-by-fn :relative-strength-index, :sort-by-window-days "12", :id "c3b0a495-6753-47ff-9d9f-74f07d2565cf", :sort-by? true, :step :filter}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days "10", :lhs-val "SMH", :id "5c9f3e04-6e37-4022-b482-80bbc0d28796", :comparator :gt, :rhs-val "50", :step :if-child} {:id "20fdec80-f6a9-4a52-a662-8866ab6ab090", :step :if-child, :is-else-condition? true, :children [{:id "dbb90e17-f569-48da-8d6d-35d01c02305b", :step :wt-cash-equal, :children [{:name "Direxion Daily Semiconductor Bull 3x Shares", :ticker "SOXL", :has_marketcap false, :id "2add2c4d-c444-4e82-8529-aa6948843200", :exchange "ARCX", :price 7.57, :step :asset, :dollar_volume 1395274080.63}]}]}]}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days "20", :lhs-val "SMH", :id "2f758ce3-5b3b-4e9f-a060-22ef9f513072", :comparator :gt, :rhs-val "SMH", :step :if-child} {:id "d2c7fafe-1578-4d3a-968c-d83abd813053", :step :if-child, :is-else-condition? true, :children [{:id "cb38b958-1f9d-41c0-aef9-771e91e311fd", :step :wt-cash-equal, :children [{:select? true, :children [{:name "Direxion Daily Semiconductor Bear 3x Shares", :ticker "SOXS", :has_marketcap false, :id "a17b3e8f-a98a-4f28-a902-caa7c0adac81", :exchange "ARCX", :price 63.21, :step :asset, :dollar_volume 1329586952.4} {:name "Vanguard Short-Term Bond ETF", :ticker "BSV", :has_marketcap false, :id "a3ef40fa-34ba-4900-b38b-71e611b9f687", :exchange "ARCX", :price 74.57, :step :asset, :dollar_volume 286166252.64, :children-count 0}], :select-fn :top, :select-n "1", :sort-by-fn :relative-strength-index, :sort-by-window-days "10", :id "8f841b46-fd6d-4535-81fa-b24a72c167e5", :sort-by? true, :step :filter}]}]}]}]}]}]}]}]}]}]}], :collapsed? false}]}]}]}], :collapsed? false}]}]}]}]}]}], :collapsed? false}], :collapsed? false}]}]} -------------------------------------------------------------------------------- /inputs/weird.edn: -------------------------------------------------------------------------------- 1 | {:last-market-day 19289, :latest-feat-vals {{:feature :moving-average-return, :ticker "SPXL", :window-days 5} 0.019494399794121587, {:feature :price, :ticker "AGG"} 93.64, {:feature :min-date, :ticker "SPY"} nil, {:feature :price, :ticker "SQQQ"} 52.24, {:feature :daily-return, :ticker "SPY"} 0.012236501108765951, {:feature :relative-strength-index, :ticker "UPRO", :window-days 10} 57.2128M, {:feature :min-date, :ticker "AGG"} nil, {:feature :standard-deviation-return, :ticker "DBC", :window-days 20} 0.013830412723304127, {:feature :min-date, :ticker "DBC"} nil, {:feature :cum-return-pct, :ticker "USDU", :window-days 5} 0.002679169457468247, {:feature :min-date, :ticker "SPXU"} nil, {:feature :min-date, :ticker "UVXY"} nil, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 10} 53.9421M, {:feature :relative-strength-index, :ticker "UVXY", :window-days 10} 43.9302M, {:feature :relative-strength-index, :ticker "SPY", :window-days 7} 64.4410M, {:feature :daily-return, :ticker "AGG"} -0.0013863709075396802, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 7} 61.0304M, {:feature :daily-return, :ticker "UPRO"} 0.036284016265248686, {:feature :price, :ticker "TMF"} 6.28, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 6} 0.21058622652248146, {:feature :daily-return, :ticker "BND"} -0.0014388489208632116, {:feature :price, :ticker "SPXU"} 18.28, {:feature :min-date, :ticker "SHY"} nil, {:feature :relative-strength-index, :ticker "BND", :window-days 5} 21.6332M, {:feature :daily-return, :ticker "SPXU"} -0.03586497890295359, {:feature :relative-strength-index, :ticker "SQQQ", :window-days 10} 41.5680M, {:feature :relative-strength-index, :ticker "SPHB", :window-days 10} 53.6511M, {:feature :mean-price, :ticker "SPY", :window-days 200} 412.035750M, {:feature :relative-strength-index, :ticker "SQQQ", :window-days 7} 35.7673M, {:feature :price, :ticker "SPXL"} 62.57, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 14} 48.9965M, {:feature :daily-return, :ticker "SPXL"} 0.03695724229366926, {:feature :daily-return, :ticker "TMF"} -0.024844720496894457, {:feature :cum-return-pct, :ticker "SPY", :window-days 2} 0.036835335650365286, {:feature :min-date, :ticker "TMF"} nil, {:feature :cum-return-pct, :ticker "BIL", :window-days 5} 4.3711069828425355E-4, {:feature :min-date, :ticker "SQQQ"} nil, {:feature :price, :ticker "SPY"} 378.87, {:feature :relative-strength-index, :ticker "SHY", :window-days 10} 41.7212M, {:feature :cum-return-pct, :ticker "UPRO", :window-days 3} 0.08161932745674187, {:feature :min-date, :ticker "BIL"} nil, {:feature :price, :ticker "DBC"} 24.95, {:feature :moving-average-return, :ticker "UPRO", :window-days 5} 0.01926019074444828, {:feature :exponential-moving-average-price, :ticker "SPY", :window-days 360} 418.4095824714427, {:feature :min-date, :ticker "BND"} nil, {:feature :moving-average-return, :ticker "TQQQ", :window-days 5} 0.02013503306980498, {:feature :cum-return-pct, :ticker "SQQQ", :window-days 5} -0.09931034482758617, {:feature :price, :ticker "SHY"} 80.92, {:feature :relative-strength-index, :ticker "BIL", :window-days 5} 73.0975M, {:feature :price, :ticker "SPXS"} 24.1, {:feature :mean-price, :ticker "SPY", :window-days 360} 426.6313026M, {:feature :relative-strength-index, :ticker "QQQ", :window-days 10} 56.0822M, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 11} 52.3471M, {:feature :min-date, :ticker "VIXY"} nil, {:feature :moving-average-return, :ticker "SPY", :window-days 210} -8.865978163347357E-4, {:feature :price, :ticker "QQQ"} 278.45, {:feature :standard-deviation-return, :ticker "DBC", :window-days 10} 0.009655811292501685, {:feature :daily-return, :ticker "BIL"} -1.0921799912633201E-4, {:feature :relative-strength-index, :ticker "SPY", :window-days 10} 58.5952M, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 1} 0.034030140982012604, {:feature :min-date, :ticker "SPXL"} nil, {:feature :daily-return, :ticker "SPHB"} 0.007208717518859986, {:feature :mean-price, :ticker "TQQQ", :window-days 20} 20.101500M, {:feature :cum-return-pct, :ticker "AGG", :window-days 5} -0.012861058401855354, {:feature :min-date, :ticker "TQQQ"} nil, {:feature :daily-return, :ticker "USDU"} 0.006048387096774244, {:feature :moving-average-return, :ticker "DBC", :window-days 360} 0.0010292304092838736, {:feature :standard-deviation-return, :ticker "SPY", :window-days 20} 0.018510848153855855, {:feature :min-date, :ticker "SPXS"} nil, {:feature :min-date, :ticker "USDU"} nil, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 2} 0.10723581467985417, {:feature :exponential-moving-average-price, :ticker "SPY", :window-days 210} 405.1510990152073, {:feature :daily-return, :ticker "VIXY"} -0.015834348355663885, {:feature :daily-return, :ticker "QQQ"} 0.011001379710986692, {:feature :price, :ticker "SPHB"} 60.08, {:feature :price, :ticker "USDU"} 29.94, {:feature :cum-return-pct, :ticker "SPXS", :window-days 5} -0.09602400600150032, {:feature :daily-return, :ticker "SHY"} -2.4709661477628764E-4, {:feature :price, :ticker "TQQQ"} 21.27, {:feature :min-date, :ticker "SPHB"} nil, {:feature :price, :ticker "UPRO"} 33.13, {:feature :relative-strength-index, :ticker "UVXY", :window-days 13} 47.1822M, {:feature :min-date, :ticker "UPRO"} nil, {:feature :daily-return, :ticker "SQQQ"} -0.03402366863905315, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 5} 0.10207253886010356, {:feature :price, :ticker "BND"} 69.4, {:feature :min-date, :ticker "QQQ"} nil, {:feature :moving-average-return, :ticker "TMF", :window-days 5} -0.035346383991321575, {:feature :daily-return, :ticker "SPXS"} -0.037155413503795476, {:feature :daily-return, :ticker "DBC"} 0.002813504823151236, {:feature :relative-strength-index, :ticker "VIXY", :window-days 13} 47.7840M, {:feature :price, :ticker "BIL"} 91.55, {:feature :price, :ticker "UVXY"} 11.77, {:feature :cum-return-pct, :ticker "SPXU", :window-days 6} -0.16605839416058396, {:feature :daily-return, :ticker "UVXY"} -0.024855012427506318, {:feature :daily-return, :ticker "TQQQ"} 0.03403014098201251, {:feature :price, :ticker "VIXY"} 16.16}, :latest-rebalance-feat-vals {{:feature :moving-average-return, :ticker "SPXL", :window-days 5} 0.019494399794121587, {:feature :price, :ticker "AGG"} 93.64, {:feature :min-date, :ticker "SPY"} nil, {:feature :price, :ticker "SQQQ"} 52.24, {:feature :daily-return, :ticker "SPY"} 0.012236501108765951, {:feature :relative-strength-index, :ticker "UPRO", :window-days 10} 57.2128M, {:feature :min-date, :ticker "AGG"} nil, {:feature :standard-deviation-return, :ticker "DBC", :window-days 20} 0.013830412723304127, {:feature :min-date, :ticker "DBC"} nil, {:feature :cum-return-pct, :ticker "USDU", :window-days 5} 0.002679169457468247, {:feature :min-date, :ticker "SPXU"} nil, {:feature :min-date, :ticker "UVXY"} nil, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 10} 53.9421M, {:feature :relative-strength-index, :ticker "UVXY", :window-days 10} 43.9302M, {:feature :relative-strength-index, :ticker "SPY", :window-days 7} 64.4410M, {:feature :daily-return, :ticker "AGG"} -0.0013863709075396802, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 7} 61.0304M, {:feature :daily-return, :ticker "UPRO"} 0.036284016265248686, {:feature :price, :ticker "TMF"} 6.28, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 6} 0.21058622652248146, {:feature :daily-return, :ticker "BND"} -0.0014388489208632116, {:feature :price, :ticker "SPXU"} 18.28, {:feature :min-date, :ticker "SHY"} nil, {:feature :relative-strength-index, :ticker "BND", :window-days 5} 21.6332M, {:feature :daily-return, :ticker "SPXU"} -0.03586497890295359, {:feature :relative-strength-index, :ticker "SQQQ", :window-days 10} 41.5680M, {:feature :relative-strength-index, :ticker "SPHB", :window-days 10} 53.6511M, {:feature :mean-price, :ticker "SPY", :window-days 200} 412.035750M, {:feature :relative-strength-index, :ticker "SQQQ", :window-days 7} 35.7673M, {:feature :price, :ticker "SPXL"} 62.57, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 14} 48.9965M, {:feature :daily-return, :ticker "SPXL"} 0.03695724229366926, {:feature :daily-return, :ticker "TMF"} -0.024844720496894457, {:feature :cum-return-pct, :ticker "SPY", :window-days 2} 0.036835335650365286, {:feature :min-date, :ticker "TMF"} nil, {:feature :cum-return-pct, :ticker "BIL", :window-days 5} 4.3711069828425355E-4, {:feature :min-date, :ticker "SQQQ"} nil, {:feature :price, :ticker "SPY"} 378.87, {:feature :relative-strength-index, :ticker "SHY", :window-days 10} 41.7212M, {:feature :cum-return-pct, :ticker "UPRO", :window-days 3} 0.08161932745674187, {:feature :min-date, :ticker "BIL"} nil, {:feature :price, :ticker "DBC"} 24.95, {:feature :moving-average-return, :ticker "UPRO", :window-days 5} 0.01926019074444828, {:feature :exponential-moving-average-price, :ticker "SPY", :window-days 360} 418.4095824714427, {:feature :min-date, :ticker "BND"} nil, {:feature :moving-average-return, :ticker "TQQQ", :window-days 5} 0.02013503306980498, {:feature :cum-return-pct, :ticker "SQQQ", :window-days 5} -0.09931034482758617, {:feature :price, :ticker "SHY"} 80.92, {:feature :relative-strength-index, :ticker "BIL", :window-days 5} 73.0975M, {:feature :price, :ticker "SPXS"} 24.1, {:feature :mean-price, :ticker "SPY", :window-days 360} 426.6313026M, {:feature :relative-strength-index, :ticker "QQQ", :window-days 10} 56.0822M, {:feature :relative-strength-index, :ticker "TQQQ", :window-days 11} 52.3471M, {:feature :min-date, :ticker "VIXY"} nil, {:feature :moving-average-return, :ticker "SPY", :window-days 210} -8.865978163347357E-4, {:feature :price, :ticker "QQQ"} 278.45, {:feature :standard-deviation-return, :ticker "DBC", :window-days 10} 0.009655811292501685, {:feature :daily-return, :ticker "BIL"} -1.0921799912633201E-4, {:feature :relative-strength-index, :ticker "SPY", :window-days 10} 58.5952M, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 1} 0.034030140982012604, {:feature :min-date, :ticker "SPXL"} nil, {:feature :daily-return, :ticker "SPHB"} 0.007208717518859986, {:feature :mean-price, :ticker "TQQQ", :window-days 20} 20.101500M, {:feature :cum-return-pct, :ticker "AGG", :window-days 5} -0.012861058401855354, {:feature :min-date, :ticker "TQQQ"} nil, {:feature :daily-return, :ticker "USDU"} 0.006048387096774244, {:feature :moving-average-return, :ticker "DBC", :window-days 360} 0.0010292304092838736, {:feature :standard-deviation-return, :ticker "SPY", :window-days 20} 0.018510848153855855, {:feature :min-date, :ticker "SPXS"} nil, {:feature :min-date, :ticker "USDU"} nil, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 2} 0.10723581467985417, {:feature :exponential-moving-average-price, :ticker "SPY", :window-days 210} 405.1510990152073, {:feature :daily-return, :ticker "VIXY"} -0.015834348355663885, {:feature :daily-return, :ticker "QQQ"} 0.011001379710986692, {:feature :price, :ticker "SPHB"} 60.08, {:feature :price, :ticker "USDU"} 29.94, {:feature :cum-return-pct, :ticker "SPXS", :window-days 5} -0.09602400600150032, {:feature :daily-return, :ticker "SHY"} -2.4709661477628764E-4, {:feature :price, :ticker "TQQQ"} 21.27, {:feature :min-date, :ticker "SPHB"} nil, {:feature :price, :ticker "UPRO"} 33.13, {:feature :relative-strength-index, :ticker "UVXY", :window-days 13} 47.1822M, {:feature :min-date, :ticker "UPRO"} nil, {:feature :daily-return, :ticker "SQQQ"} -0.03402366863905315, {:feature :cum-return-pct, :ticker "TQQQ", :window-days 5} 0.10207253886010356, {:feature :price, :ticker "BND"} 69.4, {:feature :min-date, :ticker "QQQ"} nil, {:feature :moving-average-return, :ticker "TMF", :window-days 5} -0.035346383991321575, {:feature :daily-return, :ticker "SPXS"} -0.037155413503795476, {:feature :daily-return, :ticker "DBC"} 0.002813504823151236, {:feature :relative-strength-index, :ticker "VIXY", :window-days 13} 47.7840M, {:feature :price, :ticker "BIL"} 91.55, {:feature :price, :ticker "UVXY"} 11.77, {:feature :cum-return-pct, :ticker "SPXU", :window-days 6} -0.16605839416058396, {:feature :daily-return, :ticker "UVXY"} -0.024855012427506318, {:feature :daily-return, :ticker "TQQQ"} 0.03403014098201251, {:feature :price, :ticker "VIXY"} 16.16}, :tdvm-weights {"$USD" {19279 0.0, 19282 0.0, 19283 0.0, 19284 0.0, 19285 0.0, 19286 0.0, 19289 0.0}, "AGG" {19282 0.33322093103812345, 19283 0.33333420517766793, 19284 0.3333303338237516, 19285 0.0, 19286 0.0, 19289 0.0}, "BIL" {19282 0.33322093103812345, 19283 0.3333315896446641, 19284 0.3333348330881243, 19285 0.33344552295678276, 19286 0.0, 19289 0.0}, "SPXS" {19279 0.33333333333333326, 19282 0.0, 19283 0.0, 19284 0.0, 19285 0.3331089540864345, 19286 0.0, 19289 0.0}, "SQQQ" {19279 0.3333333333333333, 19282 0.0, 19283 0.0, 19284 0.0, 19285 0.0, 19286 0.0, 19289 0.0}, "TMF" {19286 0.5, 19289 0.4999913109182033}, "UPRO" {19286 0.5, 19289 0.5000086890817965}, "USDU" {19279 0.33333333333333337, 19282 0.3335581379237531, 19283 0.33333420517766793, 19284 0.3333348330881242, 19285 0.33344552295678265, 19286 0.0, 19289 0.0}}, :latest-rebalance-dnum 19289, :legend {"5Ga56HTbpyOieVSx34hZ" {:name "EU Raekon | Frankenbot by BrianE on discord | BT date 31Dec2013"}}, :costs {:reg-fee {19279 0.00M, 19282 0.04M, 19283 0.01M, 19284 0.01M, 19285 0.02M, 19286 0.05M, 19289 0.01M}, :slippage {19279 5.0, 19282 6.254655411636122, 19283 0.004555902274660184, 19284 0.02217608360115264, 19285 3.1187318703596247, 19286 9.102401165991978, 19289 0.1389641263485828}, :taf-fee {19279 0.00M, 19282 0.03M, 19283 0.01M, 19284 0.01M, 19285 0.01M, 19286 0.04M, 19289 0.01M}}, :maestro-version nil, :dvm-rebalances {19279 true, 19282 true, 19283 true, 19284 true, 19285 true, 19286 true, 19289 true}, :last-market-days-value 9147.348941195782, :last-market-days-holdings {"$USD" 0.0, "AGG" 0.0, "BIL" 0.0, "SPXS" 0.0, "SQQQ" 0.0, "TMF" 728.2795, "UPRO" 138.05475, "USDU" 0.0}, :first-day 19279, :stats {:annualized-rate-of-return -0.9588425819074029, :benchmarks {"SPY" {:min -0.008385345997286228, :mean 0.00975212339463588, :sharpe-ratio 10.491860066245007, :standard-deviation 0.23423254598626986, :size 6, :annualized-rate-of-return 6.980422966788693, :median 0.011993066540425147, :max 0.025696949361071492, :cumulative-return 0.059390990688700636, :max-drawdown 0.015412389189771699, :percent {:alpha -0.6636950903558583, :beta -1.2034622082192095, :r-square 0.4659711507099721, :pearson-r -0.6826207956911158}, :log {:alpha -0.7114955604863692, :beta -1.2424210058429053, :r-square 0.461335352611035, :pearson-r -0.6792167199142222}, :calmar-ratio 452.9098558853673}}, :calmar-ratio -10.654220223922106, :cumulative-return -0.08480750963523945, :max 0.00570217062732975, :max-drawdown 0.08999650483613028, :mean -0.014370022631350017, :median -0.0017644503162165632, :min -0.06173041077094096, :sharpe-ratio -8.769156315972335, :size 6, :standard-deviation 0.41295257749076597}, :tdvm-shares {"$USD" {19279 0, 19282 0, 19283 0, 19284 0, 19285 0, 19286 0, 19289 0}, "AGG" {19282 32.942730397914914, 19283 3129504447081233/95020000000000, 19284 33.170244383903395, 19285 0, 19286 0, 19289 0}, "BIL" {19282 34.14869856350354, 19283 34.19822851250091, 19284 9374037591141901/274530000000000, 19285 4679267622445741/137325000000000, 19286 0, 19289 0}, "SPXS" {19279 115.40237847823575, 19282 0, 19283 0, 19284 0, 19285 115.67791449245736, 19286 0, 19289 0}, "SQQQ" {19279 499750/9657, 19282 0, 19283 0, 19284 0, 19285 0, 19286 0, 19289 0}, "TMF" {19286 706.1711905405961, 19289 728.2794567730442}, "UPRO" {19286 142.2503117635733, 19289 9147507905322127/66260000000000}, "USDU" {19279 999500/9027, 19282 3128109733252027/29860000000000, 19283 3129504447081233/29910000000000, 19284 9374037591141901/90090000000000, 19285 4679267622445741/45075000000000, 19286 0, 19289 0}}, :dvm-capital {"SPY" {19282 366.82, 19279 357.63, 19286 374.29, 19285 365.41, 19283 371.13, 19284 368.5, 19289 378.87}, "5Ga56HTbpyOieVSx34hZ" {19279 9995.0, 19282 9378.004544344445, 19283 9388.488785341424, 19284 9373.9954150583, 19285 9355.386513021123, 19286 9095.484934162878, 19289 9147.348941195782}}} -------------------------------------------------------------------------------- /inputs/protected_leverage3x.json: -------------------------------------------------------------------------------- 1 | "{:uid \"TyDCPJATawgZC6gDzGQCngUGRLj1\", :start-date-in-epoch-days 16730, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [\"I8c6FC2zJ10OdDgXZtXN\"], :slippage-percent 0.0005, :client-commit \"2be0aa38d0a616314bf15086b3d19bd6ce5f64ce\", :apply-reg-fee? true, :symphony {:id \"4VjKbM4tLMWO4z9N9ogl\", :step :root, :name \"10-16-22 v2.4 Protected Leverage 3x S&P 500 / NASDAQ v1.1 + Fund Surfing v2.2 | 137.7% AR | 27.7% DD | Nov 2011\", :description \"\", :rebalance :daily, :children [{:id \"4cff9b4c-c700-42de-bf69-3a46c0aefb0e\", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id \"885c19e4-b86b-4725-8c1c-11d122961cf5\", :step :if, :children [{:children [{:id \"21e17466-7a72-4ca8-870b-f7f8542e07d6\", :step :group, :name \"Risk ON\", :children [{:step :wt-cash-specified, :id \"4ae6b9a2-c09b-45ac-9a00-3af9ae78dd08\", :children [{:id \"53465018-e4d2-48df-bf1f-5ae110a5707a\", :step :group, :name \"Fund Surfing v2.2\", :children [{:step :wt-cash-equal, :id \"5318d8b2-2449-4032-b2a2-9f5c139b20e7\", :children [{:id \"ce911d61-020c-4aee-9d83-da852cb0d249\", :step :if, :children [{:children [{:id \"417a8da9-75a7-459f-b2ae-434f75918ca6\", :step :wt-cash-equal, :children [{:id \"c5c24d83-8bbe-4b12-8aeb-f87ab6d320f9\", :step :group, :name \"Fund Surf - Bottom 4 - 21d RSI\", :children [{:step :wt-cash-equal, :id \"063071a8-9ace-4e84-a856-a3a034b9d05a\", :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"92dbacf5-8a67-4ecf-bc85-f6f9ea05fc3f\", :exchange \"ARCX\", :price 12.26, :step :asset, :dollar_volume 915641263.08} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"400415f8-6e28-4fbd-84b0-bb8241da4092\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"Invesco DB Commodity Index Tracking Fund\", :ticker \"DBC\", :has_marketcap false, :id \"36562d34-3e14-40ef-ae7d-c66d228c83b5\", :exchange \"ARCX\", :price 23.91, :step :asset, :dollar_volume 94194305.76} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"7f671b8e-c2b5-4a97-8268-0ef5ef038f3f\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Direxion Daily Technology Bull 3x Shares\", :ticker \"TECL\", :has_marketcap false, :id \"71b2ce43-9a8f-4cb2-99b6-2a60f7db572b\", :exchange \"ARCX\", :price 28.03, :step :asset, :dollar_volume 81612288.15} {:name \"ProShares UltraPro QQQ\", :ticker \"TQQQ\", :has_marketcap false, :id \"1e4acb26-172b-47fc-adb8-39c2461121c6\", :exchange \"XNAS\", :price 25.19, :step :asset, :dollar_volume 3762218065.65} {:name \"ProShares UltraPro S&P500\", :ticker \"UPRO\", :has_marketcap false, :id \"30307004-65f8-4e21-9902-1e100dde2be4\", :exchange \"ARCX\", :price 36.89, :step :asset, :dollar_volume 358424125.36} {:name \"Direxion Daily Financial Bull 3x Shares\", :ticker \"FAS\", :has_marketcap false, :id \"533333dc-c9cb-4b2a-b931-1d3841d9183e\", :exchange \"ARCX\", :price 54.21, :step :asset, :dollar_volume 100702339.14}], :select-fn :bottom, :select-n \"4\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"21\", :weight {:num 100, :den 100}, :id \"012a200b-c03d-4d4e-9873-79363caa4c1c\", :sort-by? true, :step :filter, :collapsed? false}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"20\", :lhs-val \"UVXY\", :id \"f4644a0a-5414-4584-91a2-2af6dbd584e5\", :comparator :lt, :rhs-val \"65\", :step :if-child, :collapsed? false} {:id \"3f1b9893-8837-4da0-8857-8a93680acc20\", :step :if-child, :is-else-condition? true, :children [{:id \"388939b8-b620-4e47-bea5-5af6e8b8779a\", :step :wt-cash-equal, :children [{:id \"641404a9-33ef-4a65-93f3-a51ca27492bf\", :step :group, :name \"TMV/TMF Selector\", :children [{:step :wt-cash-equal, :id \"8f762a91-556e-4a53-9ed4-53b68e9c2b91\", :children [{:id \"17381635-3e7a-4d92-aabc-f9d4b64f3c70\", :step :if, :children [{:children [{:id \"022870bc-bef0-429b-b6ee-ff83b8a9ae8c\", :step :wt-cash-equal, :children [{:id \"13aa1f10-45fc-4dc6-b6c7-d42bdc848e6b\", :step :if, :children [{:children [{:id \"255bd4a5-b9cc-4af3-8e00-bf3ca5ff0888\", :step :wt-cash-equal, :children [{:id \"18b9a127-af5a-4c67-887f-34359d4a8e22\", :step :group, :name \"Fund Surf w/Commodities - Bottom 2 - 5d RSI\", :children [{:step :wt-cash-equal, :id \"77b2e665-4da8-4745-834d-0b2e9f7ede76\", :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"489dfd72-88b6-4a07-91a4-9f911e8f576c\", :exchange \"ARCX\", :price 12.26, :step :asset, :dollar_volume 915641263.08} {:name \"iShares 1-3 Year Treasury Bond ETF\", :ticker \"SHY\", :has_marketcap false, :id \"e3a735eb-4495-43d0-81da-a6a94919214a\", :exchange \"XNAS\", :price 81.58, :step :asset, :dollar_volume 353214804.92} {:name \"SPDR S&P 500 ETF Trust\", :ticker \"SPY\", :has_marketcap false, :id \"020e58e9-93ad-49dc-89aa-17f39214873b\", :exchange \"ARCX\", :price 388.55, :step :asset, :dollar_volume 27860178114.100002} {:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"0f7455c3-1fbe-46c5-927d-e36118515311\", :exchange \"XNAS\", :price 291.05, :step :asset, :dollar_volume 15033101842.45} {:name \"Direxion Daily Technology Bull 3x Shares\", :ticker \"TECL\", :has_marketcap false, :id \"eab2be5b-ca20-4769-852f-f0070e721301\", :exchange \"ARCX\", :price 28.03, :step :asset, :dollar_volume 81612288.15} {:name \"ProShares UltraPro QQQ\", :ticker \"TQQQ\", :has_marketcap false, :id \"a9722dff-c271-43f4-b136-bb719ce75746\", :exchange \"XNAS\", :price 25.19, :step :asset, :dollar_volume 3762218065.65} {:name \"ProShares UltraPro S&P500\", :ticker \"UPRO\", :has_marketcap false, :id \"b394a531-6eba-4198-ae5f-1a67567ebdb0\", :exchange \"ARCX\", :price 36.89, :step :asset, :dollar_volume 358424125.36} {:name \"Invesco DB Commodity Index Tracking Fund\", :ticker \"DBC\", :has_marketcap false, :id \"de8435d5-629b-42ff-8be4-298da6b6254d\", :exchange \"ARCX\", :price 24.78, :step :asset, :dollar_volume 40828965.24} {:name \"Invesco DB Agriculture Fund\", :ticker \"DBA\", :has_marketcap false, :id \"65ed8713-f090-4564-9680-4e5e64d9600b\", :exchange \"ARCX\", :price 20.65, :step :asset, :dollar_volume 27408063.549999997}], :select-fn :bottom, :select-n \"2\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"1c57a99e-b6b8-494b-9b32-9a9833216252\", :sort-by? true, :step :filter, :collapsed? false}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :max-drawdown, :lhs-window-days \"5\", :lhs-val \"TMF\", :id \"9e5129a6-154a-4c04-a98d-7f654f6ecc25\", :comparator :lt, :rhs-val \"10\", :step :if-child} {:id \"d5072854-e063-4009-a64a-ebc3f7a91a91\", :step :if-child, :is-else-condition? true, :children [{:id \"320e600d-5d51-4711-81ea-04a97fda38b1\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"d467f5b8-11b1-46fd-a6d7-45982b3dd0e4\", :exchange \"ARCX\", :price 10.26, :step :asset, :dollar_volume 31071794.4} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"574edaa7-c95c-4476-8108-ce3e9ab5222d\", :exchange \"ARCX\", :price 115.96, :step :asset, :dollar_volume 22307573.08}], :select-fn :top, :select-n \"1\", :sort-by-fn :max-drawdown, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"3120c33e-6a0e-4dfe-a825-f9fd2777490b\", :sort-by? true, :step :filter}]}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :max-drawdown, :lhs-window-days \"5\", :lhs-val \"TMV\", :id \"d1aed9e7-6ff0-4954-a61f-db9b65b0d5e6\", :comparator :lt, :rhs-val \"10\", :step :if-child} {:id \"ff837b29-df3c-4856-8532-e883bc1366eb\", :step :if-child, :is-else-condition? true, :children [{:id \"04883d15-5673-47c8-844b-bf7311119556\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"92578119-b3b3-4226-a124-35ece55280b2\", :exchange \"ARCX\", :price 10.26, :step :asset, :dollar_volume 31071794.4} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"70a95146-ce4c-4c29-a668-7c9267fc2a66\", :exchange \"ARCX\", :price 115.96, :step :asset, :dollar_volume 22307573.08}], :select-fn :top, :select-n \"1\", :sort-by-fn :max-drawdown, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"cb99fba2-0a3d-4de3-8074-e909f8c67013\", :sort-by? true, :step :filter}]}]}]}]}], :collapsed? false}]}]}]}]}], :collapsed? false, :weight {:num \"100\", :den 100}}]}]}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :rhs-window-days \"60\", :lhs-window-days \"56\", :lhs-val \"BND\", :id \"4caa1fe4-c23e-4c96-8560-f79ac8baa32b\", :comparator :gt, :rhs-val \"0\", :step :if-child} {:id \"20ee73c4-0978-4244-be79-530bd136d2f0\", :step :if-child, :is-else-condition? true, :children [{:id \"f894527d-399e-46a2-8c89-5ef24ed45e78\", :step :wt-cash-equal, :children [{:id \"ec2496bc-d759-4252-98ad-0d03d37ed724\", :step :if, :children [{:children [{:id \"ca418a0c-aff7-438f-b287-be22e9f16fe6\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"5f289dd2-cc2a-4920-a065-ed73339f1fec\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"cedd487e-8438-44dd-b9e8-bd3f6419fe2e\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares UltraPro Short QQQ\", :ticker \"SQQQ\", :has_marketcap false, :id \"0dc00d00-8f0c-435a-976b-a79aacd2b994\", :exchange \"XNAS\", :price 61.32, :step :asset, :dollar_volume 9655977740.64} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"1a69a3c7-f3ff-40c4-9a97-bd5417e512a9\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"62744c90-0989-40a9-b0d6-394f27cd9c63\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"eeba521b-6732-4097-b0ff-da7baf743e51\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"6f417117-fc82-471e-b4fc-7484c801ce44\", :step :if-child, :is-else-condition? true, :children [{:id \"ee66b46a-6ea5-4704-8d9a-f7d74f2c79eb\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-cash-equal, :id \"6fb92cb6-1033-4b46-bf4a-91b305cf6e49\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"9e78fda0-5544-4ec5-9662-7fa78faa8c8a\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"7fcc8c19-6685-44f9-85f0-fda7314ea69f\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"iShares U.S. Consumer Staples ETF\", :ticker \"IYK\", :has_marketcap false, :id \"137e3c40-031e-416b-89cf-5a5b3ea7df94\", :exchange \"ARCX\", :price 180.05, :step :asset, :dollar_volume 16167949.850000001} {:id \"f00b87ac-5ff4-40b5-88f5-66604d922f14\", :step :group, :name \"Safer 20 year treasury (TLT alternative, instead of TMF?)\", :children [{:step :wt-cash-equal, :id \"d04cee51-30e8-403b-8581-cfd4fbbaf9eb\", :children [{:id \"273c9870-5a8c-4ec2-9538-0f00cd4fca20\", :step :group, :name \"Safer 20 Year Treasury Bonds with Leveraged Safety\", :children [{:step :wt-cash-equal, :id \"d0630ade-0772-4194-9d3f-42368573d154\", :children [{:id \"4bf97e37-7845-4989-9312-30d1de10a529\", :step :if, :children [{:children [{:id \"fa378212-48b8-40f7-be66-2105c185d82f\", :step :wt-cash-equal, :children [{:id \"8f9538b7-2089-4afc-8bf9-6779503ff8c7\", :step :group, :name \"If long term TLT is trending up\", :children [{:step :wt-cash-equal, :id \"1a3e2365-f22e-4a0c-9421-b79422bf127c\", :children [{:id \"1da89240-aec4-472b-9372-30d29b574b91\", :step :if, :children [{:children [{:id \"8687e9b1-8e8f-45e6-9b42-3e4b344f0361\", :step :wt-cash-equal, :children [{:id \"aced4f92-951c-4d22-abf4-19db9e2fa8f9\", :step :group, :name \"If medium term TLT is not overbought\", :children [{:step :wt-cash-equal, :id \"4f273c55-1361-41d2-8b02-31eab3ce79d4\", :children [{:id \"59f87092-9867-4bf7-a75a-bb78718ebaac\", :step :if, :children [{:children [{:id \"4bef2b84-a153-4ea9-8b37-42c82d75d16e\", :step :wt-cash-equal, :children [{:id \"559ee635-3afc-49de-b921-d1a887b781b2\", :step :group, :name \"Short term TLT is trending up, buy 3x leveraged bull treasury bonds (TMF)\", :children [{:step :wt-cash-equal, :id \"b3659e41-11a9-43ff-95bd-8945938036d0\", :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"6562fc5a-a959-453d-a7e1-f24cfc3bbbb4\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03}]}], :collapsed? false}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"5\", :lhs-val \"TLT\", :id \"1c607e56-7238-49ca-9f43-e9b7568387d3\", :comparator :gt, :rhs-val \"TLT\", :step :if-child, :collapsed? false} {:id \"a0d049f5-b5b1-4399-9d52-a65c186857d5\", :step :if-child, :is-else-condition? true, :children [{:id \"d5308754-24b1-4dff-ae31-222bf098d01c\", :step :wt-cash-equal, :children [{:id \"255b7668-21aa-4162-a8dc-a1d19960b5a0\", :step :group, :name \"If short term TLT is trending down\", :children [{:step :wt-cash-equal, :id \"c4a469b3-4495-46c9-b7a9-bcac52718d8f\", :children [{:id \"dbd430b9-d360-4e55-b7a5-0a5a2d13fd36\", :step :if, :children [{:children [{:id \"039725ec-9a1c-47f5-acc7-6d6d175b0aaa\", :step :wt-cash-equal, :children [{:id \"05e4b573-49f0-4df4-b667-f10e926bfbec\", :step :group, :name \"Medium term TLT is underbought, buy 3x leveraged bull treasury bonds (TMF)\", :children [{:step :wt-cash-equal, :id \"4440ffed-e138-4ba9-a9e2-fbcebf87a388\", :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"da601dc5-b9c1-4466-bff5-fc9c56d9543b\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"29a1462c-bef0-4cd7-8982-66423c21a6aa\", :comparator :lt, :rhs-val \"20\", :step :if-child, :collapsed? false} {:id \"85bf1317-b496-455b-9d94-82c5bc3a9648\", :step :if-child, :is-else-condition? true, :children [{:id \"2dfcc686-bf85-4a29-98f2-3eb51212a3e6\", :step :wt-cash-specified, :children [{:id \"7e7ad544-0fe7-4f2b-b01e-5d1223b3c7f3\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-equal, :id \"b3e86c55-303e-423a-a5ff-e8781b07a796\", :children [{:id \"934012cc-f6b7-4044-a68e-ecc4d99e302c\", :step :if, :children [{:children [{:id \"8f59ce32-00f4-435f-af1d-53b7bdd5e2ba\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"c2fd2245-89ec-4d63-b6ea-77fdd3d96875\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"636785f0-a5ea-448e-ab1d-1b3637026c32\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares Short QQQ\", :ticker \"PSQ\", :has_marketcap false, :id \"e924c1b0-cc96-437a-a7a0-b0913ad182e2\", :exchange \"ARCX\", :price 14.92, :step :asset, :dollar_volume 256959386.68} {:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"c22e3c51-6559-4e3f-b54b-4c730169359b\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"ProShares UltraPro Short QQQ\", :ticker \"SQQQ\", :has_marketcap false, :id \"9b3b2607-fb93-45bd-b43d-5bfa1a42df5f\", :exchange \"XNAS\", :price 51.61, :step :asset, :dollar_volume 7073637337.13} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"28ce35f1-1c6a-4abf-80b4-3420d5493fb5\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"a8c2095c-a42a-4295-9d1e-c3f251872704\", :sort-by? true, :collapsed-specified-weight? false, :step :filter, :collapsed? false}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"42712747-9ea3-47ea-81e8-f4c9233ccab5\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"fba39480-f193-4850-b22f-4123726b6bd2\", :step :if-child, :is-else-condition? true, :children [{:id \"42deac64-119e-44fa-9050-90f037bec7d5\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-inverse-vol, :id \"a46a8633-5fb0-4c2f-8609-6e3750037a3d\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"72d83ec2-8755-48d7-a307-33b8ea226038\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"22ccd0d0-3d3e-4d67-a3cf-19ddc3c1d880\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"c7c60c35-b0aa-441e-aec3-a5d07b2ef6dc\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"8a808388-337b-402f-a604-10269504805a\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}], :window-days \"14\"}], :collapsed? false}]}]}]}], :weight {:num 100, :den 100}, :collapsed? false}]}]}]}]}]}]}], :collapsed? false}]}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"38858063-4194-40e5-8f37-c72ce2b3ebb7\", :comparator :lt, :rhs-val \"50\", :step :if-child, :collapsed? false} {:id \"fa7aed1a-65d4-4561-a723-202a5e1e5a69\", :step :if-child, :is-else-condition? true, :children [{:id \"aa4b929f-9f2f-4e98-942c-94637618a543\", :step :wt-cash-equal, :children [{:id \"c0a4ae97-edea-4553-930d-26701e87302f\", :step :group, :name \"Medium term TLT may be overbought\", :children [{:step :wt-cash-equal, :id \"f752e79a-5e21-4a95-bbca-c7e37892658f\", :children [{:id \"5a2d5900-748b-40f7-86bc-a7d90028d8f0\", :step :if, :children [{:children [{:id \"c67cb17e-0e2a-44db-8062-3f9d7aeefbe9\", :step :wt-cash-equal, :children [{:id \"dafc759d-5565-4005-8284-25ab12bb0095\", :step :group, :name \"Medium term TLT is overbought, buy 3x leveraged bear treasury bonds (TMV)\", :children [{:step :wt-cash-equal, :id \"80ca8706-ca4b-4393-9a27-14487dea9fb8\", :children [{:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"8e9a959a-ed80-432b-b92b-c3f75404450f\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"9b58709d-ddfc-4256-a339-b81fb84fd52a\", :comparator :gt, :rhs-val \"80\", :step :if-child, :collapsed? false} {:id \"04f9a513-1043-4eb9-9eba-b4ccb3f9205c\", :step :if-child, :is-else-condition? true, :children [{:id \"51965c05-e474-49bb-8241-50d3c2e43d56\", :step :wt-cash-specified, :children [{:id \"56621b6a-0b0f-4b1e-96cd-68d282612640\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-specified, :id \"8d49bd6c-da92-49dc-9d38-7c8e6e1b942b\", :children [{:id \"dcf79687-02c9-4c2e-a377-43430a2b5e4a\", :step :if, :children [{:children [{:id \"7e41ef5c-517f-4dff-a964-be7d39ef4741\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"7d4c0d73-7ba0-459e-8e9e-beba324a1993\", :children [{:select? true, :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"6e6b1da8-8248-4b4b-a845-7761a4fc3d2c\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"Vanguard Short-Term Bond ETF\", :ticker \"BSV\", :has_marketcap false, :id \"82432abf-a860-4c34-a7be-72608871c5d4\", :exchange \"ARCX\", :price 74.97, :step :asset, :dollar_volume 166354381.62} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"14189a70-4f14-49fb-a2de-528999d98783\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"162d30d4-2faa-4301-838e-e12cb51dba62\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"01ff3f81-b930-4515-8813-54af79369af2\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"7824ff39-c743-4ae2-8385-a909f94d3f2f\", :step :if-child, :is-else-condition? true, :children [{:id \"a786a059-775d-40a3-8ac7-502b6f097265\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-inverse-vol, :id \"71b623e9-4348-4ab7-948d-e5ebb0e4495f\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"cdd5690c-d6ba-4067-b6b7-dcf4fdde6468\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"13718438-64e5-4f79-b979-3ffc6cd6d2f2\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"99870cb5-bed5-4d72-8468-a6c607c8191a\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"c9a97002-5d82-4d67-950a-886eb065e771\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}], :window-days \"14\"}], :collapsed? false}]}], :weight {:num 100, :den 100}}]}], :weight {:num 100, :den 100}, :collapsed? false}]}]}]}]}]}]}]}]}]}], :collapsed? false}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"200\", :lhs-val \"TLT\", :id \"a0c55996-a9cd-4487-8ab9-a6466ccaf127\", :comparator :gt, :rhs-val \"TLT\", :step :if-child} {:id \"2706945e-c026-4c67-a375-5f8cb41e99e5\", :step :if-child, :is-else-condition? true, :children [{:id \"64277726-48c2-4c58-9832-fe2fb8cb7884\", :step :group, :name \"Safety: Long Term, 2 Least Volatile\", :children [{:step :wt-cash-equal, :id \"59c9696c-ad62-4b82-9ec1-53a3f86cbfb5\", :children [{:id \"50a1e538-e451-458d-b96d-487cdf3586be\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-specified, :id \"e8bca307-91db-4541-86f2-f5a1540efab9\", :children [{:id \"37255f77-3175-4d05-8a5d-dc2e84a64bdf\", :step :if, :children [{:children [{:id \"8fbab8fc-c115-4fb2-9e91-1291efbc9a43\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"af539aba-8ac4-4a6d-a2d9-bfd5917ff275\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"dcf148b8-45b0-4c4b-91b1-e16f4e73833f\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares Short QQQ\", :ticker \"PSQ\", :has_marketcap false, :id \"f1ed8273-6976-40e4-8033-991e84f1a3a3\", :exchange \"ARCX\", :price 14.92, :step :asset, :dollar_volume 256959386.68} {:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"7d2f923b-b93b-4a8e-bb06-9810e171212b\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"eaa7986d-f80b-44d5-b915-705d43a3b0fb\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"111f8af2-8ce0-41e0-b57a-e937fc806143\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"c1b693e3-7b8e-46c5-8bc6-745e1c1a2764\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"d7592e68-dada-4e21-9b25-4ab8d450d614\", :step :if-child, :is-else-condition? true, :children [{:id \"f34cecf9-d340-416e-8f01-576409da197f\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-cash-equal, :id \"520a38b5-9fb9-4e36-848e-ee6a0bea657c\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"aa8eef0b-2e75-48ae-b803-eaf9f20d9ebc\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"8cc20262-afe1-4549-a17f-7ca3ae10dda7\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"6fa6cb2e-ac97-4230-8799-802fb45bf0d2\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"2bc9f68d-3969-4934-8a3a-f66a24fc8b1e\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}]}], :collapsed? false}]}], :weight {:num 100, :den 100}}]}], :collapsed? false, :weight {:num 50, :den 100}}]}], :collapsed? false}]}]}]}], :collapsed? false}]}]}]}], :collapsed? false}]}]}]}]}]}]}]}, :ticker-benchmarks [{:checked? true, :type :ticker, :color \"#FFBB38\", :id \"SPY\", :ticker \"SPY\"}]}" 2 | -------------------------------------------------------------------------------- /inputs/bad_inputs/no_root_symphony_protected_leverage.json: -------------------------------------------------------------------------------- 1 | "{:uid \"TyDCPJATawgZC6gDzGQCngUGRLj1\", :start-date-in-epoch-days 16730, :capital 10000, :apply-taf-fee? true, :symphony-benchmarks [\"I8c6FC2zJ10OdDgXZtXN\"], :slippage-percent 0.0005, :client-commit \"2be0aa38d0a616314bf15086b3d19bd6ce5f64ce\", :apply-reg-fee? true, :symphony {:id \"4VjKbM4tLMWO4z9N9ogl\", :step :root, :name \"10-16-22 v2.4 Protected Leverage 3x S&P 500 / NASDAQ v1.1 + Fund Surfing v2.2 | 137.7% AR | 27.7% DD | Nov 2011\", :description \"\", :rebalance :daily, :children [{:id \"4cff9b4c-c700-42de-bf69-3a46c0aefb0e\", :step :wt-cash-equal, :children [{:weight {:num 100, :den 100}, :id \"885c19e4-b86b-4725-8c1c-11d122961cf5\", :step :if, :children [{:children [{:id \"21e17466-7a72-4ca8-870b-f7f8542e07d6\", :step :group, :name \"Risk ON\", :children [{:step :wt-cash-specified, :id \"4ae6b9a2-c09b-45ac-9a00-3af9ae78dd08\", :children [{:id \"53465018-e4d2-48df-bf1f-5ae110a5707a\", :step :group, :name \"Fund Surfing v2.2\", :children [{:step :wt-cash-equal, :id \"5318d8b2-2449-4032-b2a2-9f5c139b20e7\", :children [{:id \"ce911d61-020c-4aee-9d83-da852cb0d249\", :step :if, :children [{:children [{:id \"417a8da9-75a7-459f-b2ae-434f75918ca6\", :step :wt-cash-equal, :children [{:id \"c5c24d83-8bbe-4b12-8aeb-f87ab6d320f9\", :step :group, :name \"Fund Surf - Bottom 4 - 21d RSI\", :children [{:step :wt-cash-equal, :id \"063071a8-9ace-4e84-a856-a3a034b9d05a\", :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"92dbacf5-8a67-4ecf-bc85-f6f9ea05fc3f\", :exchange \"ARCX\", :price 12.26, :step :asset, :dollar_volume 915641263.08} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"400415f8-6e28-4fbd-84b0-bb8241da4092\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"Invesco DB Commodity Index Tracking Fund\", :ticker \"DBC\", :has_marketcap false, :id \"36562d34-3e14-40ef-ae7d-c66d228c83b5\", :exchange \"ARCX\", :price 23.91, :step :asset, :dollar_volume 94194305.76} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"7f671b8e-c2b5-4a97-8268-0ef5ef038f3f\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Direxion Daily Technology Bull 3x Shares\", :ticker \"TECL\", :has_marketcap false, :id \"71b2ce43-9a8f-4cb2-99b6-2a60f7db572b\", :exchange \"ARCX\", :price 28.03, :step :asset, :dollar_volume 81612288.15} {:name \"ProShares UltraPro QQQ\", :ticker \"TQQQ\", :has_marketcap false, :id \"1e4acb26-172b-47fc-adb8-39c2461121c6\", :exchange \"XNAS\", :price 25.19, :step :asset, :dollar_volume 3762218065.65} {:name \"ProShares UltraPro S&P500\", :ticker \"UPRO\", :has_marketcap false, :id \"30307004-65f8-4e21-9902-1e100dde2be4\", :exchange \"ARCX\", :price 36.89, :step :asset, :dollar_volume 358424125.36} {:name \"Direxion Daily Financial Bull 3x Shares\", :ticker \"FAS\", :has_marketcap false, :id \"533333dc-c9cb-4b2a-b931-1d3841d9183e\", :exchange \"ARCX\", :price 54.21, :step :asset, :dollar_volume 100702339.14}], :select-fn :bottom, :select-n \"4\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"21\", :weight {:num 100, :den 100}, :id \"012a200b-c03d-4d4e-9873-79363caa4c1c\", :sort-by? true, :step :filter, :collapsed? false}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"20\", :lhs-val \"UVXY\", :id \"f4644a0a-5414-4584-91a2-2af6dbd584e5\", :comparator :lt, :rhs-val \"65\", :step :if-child, :collapsed? false} {:id \"3f1b9893-8837-4da0-8857-8a93680acc20\", :step :if-child, :is-else-condition? true, :children [{:id \"388939b8-b620-4e47-bea5-5af6e8b8779a\", :step :wt-cash-equal, :children [{:id \"641404a9-33ef-4a65-93f3-a51ca27492bf\", :step :group, :name \"TMV/TMF Selector\", :children [{:step :wt-cash-equal, :id \"8f762a91-556e-4a53-9ed4-53b68e9c2b91\", :children [{:id \"17381635-3e7a-4d92-aabc-f9d4b64f3c70\", :step :if, :children [{:children [{:id \"022870bc-bef0-429b-b6ee-ff83b8a9ae8c\", :step :wt-cash-equal, :children [{:id \"13aa1f10-45fc-4dc6-b6c7-d42bdc848e6b\", :step :if, :children [{:children [{:id \"255bd4a5-b9cc-4af3-8e00-bf3ca5ff0888\", :step :wt-cash-equal, :children [{:id \"18b9a127-af5a-4c67-887f-34359d4a8e22\", :step :group, :name \"Fund Surf w/Commodities - Bottom 2 - 5d RSI\", :children [{:step :wt-cash-equal, :id \"77b2e665-4da8-4745-834d-0b2e9f7ede76\", :children [{:select? true, :children [{:name \"Direxion Daily Semiconductor Bull 3x Shares\", :ticker \"SOXL\", :has_marketcap false, :id \"489dfd72-88b6-4a07-91a4-9f911e8f576c\", :exchange \"ARCX\", :price 12.26, :step :asset, :dollar_volume 915641263.08} {:name \"iShares 1-3 Year Treasury Bond ETF\", :ticker \"SHY\", :has_marketcap false, :id \"e3a735eb-4495-43d0-81da-a6a94919214a\", :exchange \"XNAS\", :price 81.58, :step :asset, :dollar_volume 353214804.92} {:name \"SPDR S&P 500 ETF Trust\", :ticker \"SPY\", :has_marketcap false, :id \"020e58e9-93ad-49dc-89aa-17f39214873b\", :exchange \"ARCX\", :price 388.55, :step :asset, :dollar_volume 27860178114.100002} {:name \"Invesco QQQ Trust\", :ticker \"QQQ\", :has_marketcap false, :id \"0f7455c3-1fbe-46c5-927d-e36118515311\", :exchange \"XNAS\", :price 291.05, :step :asset, :dollar_volume 15033101842.45} {:name \"Direxion Daily Technology Bull 3x Shares\", :ticker \"TECL\", :has_marketcap false, :id \"eab2be5b-ca20-4769-852f-f0070e721301\", :exchange \"ARCX\", :price 28.03, :step :asset, :dollar_volume 81612288.15} {:name \"ProShares UltraPro QQQ\", :ticker \"TQQQ\", :has_marketcap false, :id \"a9722dff-c271-43f4-b136-bb719ce75746\", :exchange \"XNAS\", :price 25.19, :step :asset, :dollar_volume 3762218065.65} {:name \"ProShares UltraPro S&P500\", :ticker \"UPRO\", :has_marketcap false, :id \"b394a531-6eba-4198-ae5f-1a67567ebdb0\", :exchange \"ARCX\", :price 36.89, :step :asset, :dollar_volume 358424125.36} {:name \"Invesco DB Commodity Index Tracking Fund\", :ticker \"DBC\", :has_marketcap false, :id \"de8435d5-629b-42ff-8be4-298da6b6254d\", :exchange \"ARCX\", :price 24.78, :step :asset, :dollar_volume 40828965.24} {:name \"Invesco DB Agriculture Fund\", :ticker \"DBA\", :has_marketcap false, :id \"65ed8713-f090-4564-9680-4e5e64d9600b\", :exchange \"ARCX\", :price 20.65, :step :asset, :dollar_volume 27408063.549999997}], :select-fn :bottom, :select-n \"2\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"1c57a99e-b6b8-494b-9b32-9a9833216252\", :sort-by? true, :step :filter, :collapsed? false}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :max-drawdown, :lhs-window-days \"5\", :lhs-val \"TMF\", :id \"9e5129a6-154a-4c04-a98d-7f654f6ecc25\", :comparator :lt, :rhs-val \"10\", :step :if-child} {:id \"d5072854-e063-4009-a64a-ebc3f7a91a91\", :step :if-child, :is-else-condition? true, :children [{:id \"320e600d-5d51-4711-81ea-04a97fda38b1\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"d467f5b8-11b1-46fd-a6d7-45982b3dd0e4\", :exchange \"ARCX\", :price 10.26, :step :asset, :dollar_volume 31071794.4} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"574edaa7-c95c-4476-8108-ce3e9ab5222d\", :exchange \"ARCX\", :price 115.96, :step :asset, :dollar_volume 22307573.08}], :select-fn :top, :select-n \"1\", :sort-by-fn :max-drawdown, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"3120c33e-6a0e-4dfe-a825-f9fd2777490b\", :sort-by? true, :step :filter}]}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :max-drawdown, :lhs-window-days \"5\", :lhs-val \"TMV\", :id \"d1aed9e7-6ff0-4954-a61f-db9b65b0d5e6\", :comparator :lt, :rhs-val \"10\", :step :if-child} {:id \"ff837b29-df3c-4856-8532-e883bc1366eb\", :step :if-child, :is-else-condition? true, :children [{:id \"04883d15-5673-47c8-844b-bf7311119556\", :step :wt-cash-equal, :children [{:select? true, :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"92578119-b3b3-4226-a124-35ece55280b2\", :exchange \"ARCX\", :price 10.26, :step :asset, :dollar_volume 31071794.4} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"70a95146-ce4c-4c29-a668-7c9267fc2a66\", :exchange \"ARCX\", :price 115.96, :step :asset, :dollar_volume 22307573.08}], :select-fn :top, :select-n \"1\", :sort-by-fn :max-drawdown, :sort-by-window-days \"5\", :weight {:num 100, :den 100}, :id \"cb99fba2-0a3d-4de3-8074-e909f8c67013\", :sort-by? true, :step :filter}]}]}]}]}], :collapsed? false}]}]}]}]}], :collapsed? false, :weight {:num \"100\", :den 100}}]}]}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :cumulative-return, :rhs-window-days \"60\", :lhs-window-days \"56\", :lhs-val \"BND\", :id \"4caa1fe4-c23e-4c96-8560-f79ac8baa32b\", :comparator :gt, :rhs-val \"0\", :step :if-child} {:id \"20ee73c4-0978-4244-be79-530bd136d2f0\", :step :if-child, :is-else-condition? true, :children [{:id \"f894527d-399e-46a2-8c89-5ef24ed45e78\", :step :wt-cash-equal, :children [{:id \"ec2496bc-d759-4252-98ad-0d03d37ed724\", :step :if, :children [{:children [{:id \"ca418a0c-aff7-438f-b287-be22e9f16fe6\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"5f289dd2-cc2a-4920-a065-ed73339f1fec\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"cedd487e-8438-44dd-b9e8-bd3f6419fe2e\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares UltraPro Short QQQ\", :ticker \"SQQQ\", :has_marketcap false, :id \"0dc00d00-8f0c-435a-976b-a79aacd2b994\", :exchange \"XNAS\", :price 61.32, :step :asset, :dollar_volume 9655977740.64} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"1a69a3c7-f3ff-40c4-9a97-bd5417e512a9\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"62744c90-0989-40a9-b0d6-394f27cd9c63\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"eeba521b-6732-4097-b0ff-da7baf743e51\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"6f417117-fc82-471e-b4fc-7484c801ce44\", :step :if-child, :is-else-condition? true, :children [{:id \"ee66b46a-6ea5-4704-8d9a-f7d74f2c79eb\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-cash-equal, :id \"6fb92cb6-1033-4b46-bf4a-91b305cf6e49\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"9e78fda0-5544-4ec5-9662-7fa78faa8c8a\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"7fcc8c19-6685-44f9-85f0-fda7314ea69f\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"iShares U.S. Consumer Staples ETF\", :ticker \"IYK\", :has_marketcap false, :id \"137e3c40-031e-416b-89cf-5a5b3ea7df94\", :exchange \"ARCX\", :price 180.05, :step :asset, :dollar_volume 16167949.850000001} {:id \"f00b87ac-5ff4-40b5-88f5-66604d922f14\", :step :group, :name \"Safer 20 year treasury (TLT alternative, instead of TMF?)\", :children [{:step :wt-cash-equal, :id \"d04cee51-30e8-403b-8581-cfd4fbbaf9eb\", :children [{:id \"273c9870-5a8c-4ec2-9538-0f00cd4fca20\", :step :group, :name \"Safer 20 Year Treasury Bonds with Leveraged Safety\", :children [{:step :wt-cash-equal, :id \"d0630ade-0772-4194-9d3f-42368573d154\", :children [{:id \"4bf97e37-7845-4989-9312-30d1de10a529\", :step :if, :children [{:children [{:id \"fa378212-48b8-40f7-be66-2105c185d82f\", :step :wt-cash-equal, :children [{:id \"8f9538b7-2089-4afc-8bf9-6779503ff8c7\", :step :group, :name \"If long term TLT is trending up\", :children [{:step :wt-cash-equal, :id \"1a3e2365-f22e-4a0c-9421-b79422bf127c\", :children [{:id \"1da89240-aec4-472b-9372-30d29b574b91\", :step :if, :children [{:children [{:id \"8687e9b1-8e8f-45e6-9b42-3e4b344f0361\", :step :wt-cash-equal, :children [{:id \"aced4f92-951c-4d22-abf4-19db9e2fa8f9\", :step :group, :name \"If medium term TLT is not overbought\", :children [{:step :wt-cash-equal, :id \"4f273c55-1361-41d2-8b02-31eab3ce79d4\", :children [{:id \"59f87092-9867-4bf7-a75a-bb78718ebaac\", :step :if, :children [{:children [{:id \"4bef2b84-a153-4ea9-8b37-42c82d75d16e\", :step :wt-cash-equal, :children [{:id \"559ee635-3afc-49de-b921-d1a887b781b2\", :step :group, :name \"Short term TLT is trending up, buy 3x leveraged bull treasury bonds (TMF)\", :children [{:step :wt-cash-equal, :id \"b3659e41-11a9-43ff-95bd-8945938036d0\", :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"6562fc5a-a959-453d-a7e1-f24cfc3bbbb4\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03}]}], :collapsed? false}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"5\", :lhs-val \"TLT\", :id \"1c607e56-7238-49ca-9f43-e9b7568387d3\", :comparator :gt, :rhs-val \"TLT\", :step :if-child, :collapsed? false} {:id \"a0d049f5-b5b1-4399-9d52-a65c186857d5\", :step :if-child, :is-else-condition? true, :children [{:id \"d5308754-24b1-4dff-ae31-222bf098d01c\", :step :wt-cash-equal, :children [{:id \"255b7668-21aa-4162-a8dc-a1d19960b5a0\", :step :group, :name \"If short term TLT is trending down\", :children [{:step :wt-cash-equal, :id \"c4a469b3-4495-46c9-b7a9-bcac52718d8f\", :children [{:id \"dbd430b9-d360-4e55-b7a5-0a5a2d13fd36\", :step :if, :children [{:children [{:id \"039725ec-9a1c-47f5-acc7-6d6d175b0aaa\", :step :wt-cash-equal, :children [{:id \"05e4b573-49f0-4df4-b667-f10e926bfbec\", :step :group, :name \"Medium term TLT is underbought, buy 3x leveraged bull treasury bonds (TMF)\", :children [{:step :wt-cash-equal, :id \"4440ffed-e138-4ba9-a9e2-fbcebf87a388\", :children [{:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"da601dc5-b9c1-4466-bff5-fc9c56d9543b\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03}]}]}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"29a1462c-bef0-4cd7-8982-66423c21a6aa\", :comparator :lt, :rhs-val \"20\", :step :if-child, :collapsed? false} {:id \"85bf1317-b496-455b-9d94-82c5bc3a9648\", :step :if-child, :is-else-condition? true, :children [{:id \"2dfcc686-bf85-4a29-98f2-3eb51212a3e6\", :step :wt-cash-specified, :children [{:id \"7e7ad544-0fe7-4f2b-b01e-5d1223b3c7f3\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-equal, :id \"b3e86c55-303e-423a-a5ff-e8781b07a796\", :children [{:id \"934012cc-f6b7-4044-a68e-ecc4d99e302c\", :step :if, :children [{:children [{:id \"8f59ce32-00f4-435f-af1d-53b7bdd5e2ba\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"c2fd2245-89ec-4d63-b6ea-77fdd3d96875\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"636785f0-a5ea-448e-ab1d-1b3637026c32\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares Short QQQ\", :ticker \"PSQ\", :has_marketcap false, :id \"e924c1b0-cc96-437a-a7a0-b0913ad182e2\", :exchange \"ARCX\", :price 14.92, :step :asset, :dollar_volume 256959386.68} {:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"c22e3c51-6559-4e3f-b54b-4c730169359b\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"ProShares UltraPro Short QQQ\", :ticker \"SQQQ\", :has_marketcap false, :id \"9b3b2607-fb93-45bd-b43d-5bfa1a42df5f\", :exchange \"XNAS\", :price 51.61, :step :asset, :dollar_volume 7073637337.13} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"28ce35f1-1c6a-4abf-80b4-3420d5493fb5\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"a8c2095c-a42a-4295-9d1e-c3f251872704\", :sort-by? true, :collapsed-specified-weight? false, :step :filter, :collapsed? false}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"42712747-9ea3-47ea-81e8-f4c9233ccab5\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"fba39480-f193-4850-b22f-4123726b6bd2\", :step :if-child, :is-else-condition? true, :children [{:id \"42deac64-119e-44fa-9050-90f037bec7d5\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-inverse-vol, :id \"a46a8633-5fb0-4c2f-8609-6e3750037a3d\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"72d83ec2-8755-48d7-a307-33b8ea226038\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"22ccd0d0-3d3e-4d67-a3cf-19ddc3c1d880\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"c7c60c35-b0aa-441e-aec3-a5d07b2ef6dc\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"8a808388-337b-402f-a604-10269504805a\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}], :window-days \"14\"}], :collapsed? false}]}]}]}], :weight {:num 100, :den 100}, :collapsed? false}]}]}]}]}]}]}], :collapsed? false}]}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"38858063-4194-40e5-8f37-c72ce2b3ebb7\", :comparator :lt, :rhs-val \"50\", :step :if-child, :collapsed? false} {:id \"fa7aed1a-65d4-4561-a723-202a5e1e5a69\", :step :if-child, :is-else-condition? true, :children [{:id \"aa4b929f-9f2f-4e98-942c-94637618a543\", :step :wt-cash-equal, :children [{:id \"c0a4ae97-edea-4553-930d-26701e87302f\", :step :group, :name \"Medium term TLT may be overbought\", :children [{:step :wt-cash-equal, :id \"f752e79a-5e21-4a95-bbca-c7e37892658f\", :children [{:id \"5a2d5900-748b-40f7-86bc-a7d90028d8f0\", :step :if, :children [{:children [{:id \"c67cb17e-0e2a-44db-8062-3f9d7aeefbe9\", :step :wt-cash-equal, :children [{:id \"dafc759d-5565-4005-8284-25ab12bb0095\", :step :group, :name \"Medium term TLT is overbought, buy 3x leveraged bear treasury bonds (TMV)\", :children [{:step :wt-cash-equal, :id \"80ca8706-ca4b-4393-9a27-14487dea9fb8\", :children [{:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"8e9a959a-ed80-432b-b92b-c3f75404450f\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}]}], :collapsed? false}]}], :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :relative-strength-index, :lhs-window-days \"14\", :lhs-val \"TLT\", :id \"9b58709d-ddfc-4256-a339-b81fb84fd52a\", :comparator :gt, :rhs-val \"80\", :step :if-child, :collapsed? false} {:id \"04f9a513-1043-4eb9-9eba-b4ccb3f9205c\", :step :if-child, :is-else-condition? true, :children [{:id \"51965c05-e474-49bb-8241-50d3c2e43d56\", :step :wt-cash-specified, :children [{:id \"56621b6a-0b0f-4b1e-96cd-68d282612640\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-specified, :id \"8d49bd6c-da92-49dc-9d38-7c8e6e1b942b\", :children [{:id \"dcf79687-02c9-4c2e-a377-43430a2b5e4a\", :step :if, :children [{:children [{:id \"7e41ef5c-517f-4dff-a964-be7d39ef4741\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"7d4c0d73-7ba0-459e-8e9e-beba324a1993\", :children [{:select? true, :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"6e6b1da8-8248-4b4b-a845-7761a4fc3d2c\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"Vanguard Short-Term Bond ETF\", :ticker \"BSV\", :has_marketcap false, :id \"82432abf-a860-4c34-a7be-72608871c5d4\", :exchange \"ARCX\", :price 74.97, :step :asset, :dollar_volume 166354381.62} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"14189a70-4f14-49fb-a2de-528999d98783\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"162d30d4-2faa-4301-838e-e12cb51dba62\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"01ff3f81-b930-4515-8813-54af79369af2\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"7824ff39-c743-4ae2-8385-a909f94d3f2f\", :step :if-child, :is-else-condition? true, :children [{:id \"a786a059-775d-40a3-8ac7-502b6f097265\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-inverse-vol, :id \"71b623e9-4348-4ab7-948d-e5ebb0e4495f\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"cdd5690c-d6ba-4067-b6b7-dcf4fdde6468\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"13718438-64e5-4f79-b979-3ffc6cd6d2f2\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"99870cb5-bed5-4d72-8468-a6c607c8191a\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"c9a97002-5d82-4d67-950a-886eb065e771\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}], :window-days \"14\"}], :collapsed? false}]}], :weight {:num 100, :den 100}}]}], :weight {:num 100, :den 100}, :collapsed? false}]}]}]}]}]}]}]}]}]}], :collapsed? false}]}], :rhs-fn :moving-average-price, :is-else-condition? false, :lhs-fn :current-price, :rhs-window-days \"200\", :lhs-val \"TLT\", :id \"a0c55996-a9cd-4487-8ab9-a6466ccaf127\", :comparator :gt, :rhs-val \"TLT\", :step :if-child} {:id \"2706945e-c026-4c67-a375-5f8cb41e99e5\", :step :if-child, :is-else-condition? true, :children [{:id \"64277726-48c2-4c58-9832-fe2fb8cb7884\", :step :group, :name \"Safety: Long Term, 2 Least Volatile\", :children [{:step :wt-cash-equal, :id \"59c9696c-ad62-4b82-9ec1-53a3f86cbfb5\", :children [{:id \"50a1e538-e451-458d-b96d-487cdf3586be\", :step :group, :name \"Leveraged Safety\", :children [{:step :wt-cash-specified, :id \"e8bca307-91db-4541-86f2-f5a1540efab9\", :children [{:id \"37255f77-3175-4d05-8a5d-dc2e84a64bdf\", :step :if, :children [{:children [{:id \"8fbab8fc-c115-4fb2-9e91-1291efbc9a43\", :step :group, :name \"Risk Off, Rising Rates\", :children [{:step :wt-cash-equal, :id \"af539aba-8ac4-4a6d-a2d9-bfd5917ff275\", :children [{:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"dcf148b8-45b0-4c4b-91b1-e16f4e73833f\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:select? true, :children [{:name \"ProShares Short QQQ\", :ticker \"PSQ\", :has_marketcap false, :id \"f1ed8273-6976-40e4-8033-991e84f1a3a3\", :exchange \"ARCX\", :price 14.92, :step :asset, :dollar_volume 256959386.68} {:name \"Invesco DB US Dollar Index Bullish Fund\", :ticker \"UUP\", :has_marketcap false, :id \"7d2f923b-b93b-4a8e-bb06-9810e171212b\", :exchange \"ARCX\", :price 30.15, :step :asset, :dollar_volume 142925170.5} {:name \"Direxion Daily 20+ Year Treasury Bear 3x Shares\", :ticker \"TMV\", :has_marketcap false, :id \"eaa7986d-f80b-44d5-b915-705d43a3b0fb\", :exchange \"ARCX\", :price 133.65, :step :asset, :dollar_volume 87510010.5}], :select-fn :bottom, :select-n \"1\", :sort-by-fn :relative-strength-index, :sort-by-window-days \"20\", :weight {:num 88, :den 100}, :id \"111f8af2-8ce0-41e0-b57a-e937fc806143\", :sort-by? true, :collapsed-specified-weight? false, :step :filter}]}], :collapsed? false}], :rhs-fn :cumulative-return, :is-else-condition? false, :rhs-fixed-value? true, :lhs-fn :moving-average-return, :rhs-window-days \"20\", :lhs-window-days \"20\", :lhs-val \"TLT\", :id \"c1b693e3-7b8e-46c5-8bc6-745e1c1a2764\", :comparator :lt, :rhs-val \"0\", :step :if-child, :collapsed? false} {:id \"d7592e68-dada-4e21-9b25-4ab8d450d614\", :step :if-child, :is-else-condition? true, :children [{:id \"f34cecf9-d340-416e-8f01-576409da197f\", :step :group, :name \"Risk Off, Falling Rates\", :children [{:step :wt-cash-equal, :id \"520a38b5-9fb9-4e36-848e-ee6a0bea657c\", :children [{:name \"SPDR Gold Shares\", :ticker \"GLD\", :has_marketcap false, :id \"aa8eef0b-2e75-48ae-b803-eaf9f20d9ebc\", :exchange \"ARCX\", :price 155.48, :step :asset, :dollar_volume 948642717.88} {:name \"Direxion Daily 20+ Year Treasury Bull 3X Shares\", :ticker \"TMF\", :has_marketcap false, :id \"8cc20262-afe1-4549-a17f-7ca3ae10dda7\", :exchange \"ARCX\", :price 8.57, :step :asset, :dollar_volume 64579055.03} {:name \"AGFiQ US Market Neutral Anti-Beta Fund\", :ticker \"BTAL\", :has_marketcap false, :id \"6fa6cb2e-ac97-4230-8799-802fb45bf0d2\", :exchange \"ARCX\", :price 20.33, :step :asset, :dollar_volume 7713161.339999999} {:name \"Consumer Staples Select Sector SPDR Fund\", :ticker \"XLP\", :has_marketcap false, :id \"2bc9f68d-3969-4934-8a3a-f66a24fc8b1e\", :exchange \"ARCX\", :price 66.73, :step :asset, :dollar_volume 895695036.0200001}]}], :collapsed? false}]}], :weight {:num 100, :den 100}}]}], :collapsed? false, :weight {:num 50, :den 100}}]}], :collapsed? false}]}]}]}], :collapsed? false}]}]}]}], :collapsed? false}]}]}]}]}]}]}]}, :ticker-benchmarks [{:checked? true, :type :ticker, :color \"#FFBB38\", :id \"SPY\", :ticker \"SPY\"}]}" 2 | --------------------------------------------------------------------------------