aW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHBhbmRhc190YSBhcyB0YQppbXBvcnQgY2N4dAppbXBvcnQgZ3ltCmZyb20gZ3ltIGltcG9ydCBzcGFjZXMKZnJvbSBzdGFibGVfYmFzZWxpbmVzMyBpbXBvcnQgUFBPCmZyb20gc3RhYmxlX2Jhc2VsaW5lczMuY29tbW9uLnZlY19lbnYgaW1wb3J0IER1bW15VmVjRW52CmZyb20gc3RhYmxlX2Jhc2VsaW5lczMuY29tbW9uLmNhbGxiYWNrcyBpbXBvcnQgQmFzZUNhbGxiYWNrCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBNaW5NYXhTY2FsZXIsIFN0YW5kYXJkU2NhbGVyCmZyb20gc2tsZWFybi5mZWF0dXJlX3NlbGVjdGlvbiBpbXBvcnQgU2VsZWN0S0Jlc3QsIGZfcmVncmVzc2lvbgpmcm9tIHNrbGVhcm4uZW5zZW1ibGUgaW1wb3J0IFJhbmRvbUZvcmVzdFJlZ3Jlc3NvcgppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CmltcG9ydCBxdWFudHN0YXRzIGFzIHFzCmltcG9ydCB0aW1lCmltcG9ydCBvcwppbXBvcnQgd2FybmluZ3MKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCBqc29uCmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lLCB0aW1lZGVsdGEKaW1wb3J0IHJhbmRvbQoKIyDnpoHnlKjorablkYoKd2FybmluZ3MuZmlsdGVyd2FybmluZ3MoJ2lnbm9yZScpCgojID09PT09PT09PT09PT09PT09PT09PT0gQVBJIOmFjee9riA9PT09PT09PT09PT09PT09PT09PT09CkFQSV9LRVkgPSAmcXVvdDs0MjMwZDkzNmEyNTNlODVlMzQ5YzlhZTIyMWU0MTdlZSZxdW90OwpTRUNSRVRfS0VZID0gJnF1b3Q7MjRmMzhhMWVkN2Q3NjBhNDNhYWEwNmVlNDlkNjA3NTYzZjRjMjg4MmQ1OGJlNzI2ZmJmMTNhM2M3NzNiYmVjMCZxdW90OwoKIyDnroDljJbniYjpk77kuIrmlbDmja5BUEnphY3nva4KT05DSEFJTl9BUEkgPSB7CiAgICAmcXVvdDtQRVBFJnF1b3Q7OiAmcXVvdDtodHRwczovL2EuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLm4uaW8vYXBpJnF1b3Q7LAogICAgJnF1b3Q7U09MJnF1b3Q7OiAmcXVvdDtodHRwczovL2EuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLm4uaW8mcXVvdDsKfQoKIyA9PT09PT09PT09PT09PT09PT09PT09IOeugOWMlueJiOWboOWtkOW3peeoiyA9PT09PT09PT09PT09PT09PT09PT09CmRlZiBjYWxjdWxhdGVfZmVhdHVyZXMoZGYsIHN5bWJvbCk6CiAgICAmcXVvdDsmcXVvdDsmcXVvdDvorqHnrpfmioDmnK/mjIfmoIflkozpk77kuIrlm6DlrZAmcXVvdDsmcXVvdDsmcXVvdDsKICAgICMg5Z+656GA5Lu35qC854m55b6BCiAgICBkZlsncmV0dXJucyddID0gZGZbJ2Nsb3NlJ10ucGN0X2NoYW5nZSgpCiAgICAKICAgICMg5Yqo6YeP5oyH5qCHCiAgICBkZlsncnNpJ10gPSB0YS5yc2koZGZbJ2Nsb3NlJ10sIGxlbmd0aD0xNCkKICAgIGRmWydtYWNkJ10gPSB0YS5tYWNkKGRmWydjbG9zZSddLCBmYXN0PTEyLCBzbG93PTI2KVsnTUFDRF8xMl8yNl85J10KICAgIGRmWydtYWNkX3NpZ25hbCddID0gdGEubWFjZChkZlsnY2xvc2UnXSwgZmFzdD0xMiwgc2xvdz0yNilbJ01BQ0RzXzEyXzI2XzknXQogICAgCiAgICAjIOazouWKqOeOh+aMh+aghwogICAgZGZbJ2F0ciddID0gdGEuYXRyKGRmWydoaWdoJ10sIGRmWydsb3cnXSwgZGZbJ2Nsb3NlJ10sIGxlbmd0aD0xNCkKICAgIGRmWydiYl91cHBlciddID0gdGEuYmJhbmRzKGRmWydjbG9zZSddLCBsZW5ndGg9MjApWydCQlVfMjBfMi4wJ10KICAgIGRmWydiYl9sb3dlciddID0gdGEuYmJhbmRzKGRmWydjbG9zZSddLCBsZW5ndGg9MjApWydCQkxfMjBfMi4wJ10KICAgIAogICAgIyDmiJDkuqTph4/mjIfmoIcKICAgIGRmWydvYnYnXSA9IHRhLm9idihkZlsnY2xvc2UnXSwgZGZbJ3ZvbHVtZSddKQogICAgCiAgICAjIOeJueW+geW3peeoiwogICAgZGZbJ2JiX3dpZHRoJ10gPSAoZGZbJ2JiX3VwcGVyJ10gLSBkZlsnYmJfbG93ZXInXSkgLyBkZlsnY2xvc2UnXQogICAgZGZbJ21hY2RfZGlmZiddID0gZGZbJ21hY2QnXSAtIGRmWydtYWNkX3NpZ25hbCddCiAgICBkZlsndm9sdW1lX2NoYW5nZSddID0gZGZbJ3ZvbHVtZSddLnBjdF9jaGFuZ2UoKQogICAgCiAgICAjIOebruagh+WPmOmHjwogICAgZGZbJ3RhcmdldCddID0gZGZbJ2Nsb3NlJ10uc2hpZnQoLTEpIC8gZGZbJ2Nsb3NlJ10gLSAxCiAgICAKICAgICMg5re75Yqg6ZO+5LiK5pWw5o2uICjnroDljJbniYgpCiAgICBpZiAmcXVvdDtQRVBFJnF1b3Q7IGluIHN5bWJvbDoKICAgICAgICBkZlsncGVwZV9idXJuJ10gPSBucC5yYW5kb20ucG9pc3NvbigxMDAwMDAwMCwgbGVuKGRmKSkKICAgICAgICBkZlsncGVwZV9sYXJnZV90eCddID0gbnAucmFuZG9tLnBvaXNzb24oNSwgbGVuKGRmKSkKICAgIGVsaWYgJnF1b3Q7U09MJnF1b3Q7IGluIHN5bWJvbDoKICAgICAgICBkZlsnc29sX3RwcyddID0gbnAucmFuZG9tLm5vcm1hbCgzMDAwLCA1MDAsIGxlbihkZikpCiAgICAgICAgZGZbJ3NvbF9zdGFrZWQnXSA9IG5wLnJhbmRvbS51bmlmb3JtKDcwLCA4MCwgbGVuKGRmKSkKICAgIAogICAgcmV0dXJuIGRmLmRyb3BuYSgpCgojID09PT09PT09PT09PT09PT09PT09PT0g54m55b6B6YCJ5oupID09PT09PT09PT09PT09PT09PT09PT0KZGVmIHNlbGVjdF9mZWF0dXJlcyhmZWF0dXJlcywgdGFyZ2V0LCBrPTgpOgogICAgJnF1b3Q7JnF1b3Q7JnF1b3Q76YCJ5oup5pyA6YeN6KaB55qEa+S4queJueW+gSZxdW90OyZxdW90OyZxdW90OwogICAgc2VsZWN0b3IgPSBTZWxlY3RLQmVzdChzY29yZV9mdW5jPWZfcmVncmVzc2lvbiwgaz1rKQogICAgc2VsZWN0b3IuZml0KGZlYXR1cmVzLCB0YXJnZXQpCiAgICBzZWxlY3RlZF9mZWF0dXJlcyA9IGZlYXR1cmVzLmNvbHVtbnNbc2VsZWN0b3IuZ2V0X3N1cHBvcnQoKV0KICAgIAogICAgcHJpbnQoJnF1b3Q7U2VsZWN0ZWQgRmVhdHVyZXM6JnF1b3Q7KQogICAgcHJpbnQoc2VsZWN0ZWRfZmVhdHVyZXMudG9saXN0KCkpCiAgICAKICAgIHJldHVybiBmZWF0dXJlc1tzZWxlY3RlZF9mZWF0dXJlc10KCiMgPT09PT09PT09PT09PT09PT09PT09PSDlvLrljJblrabkuaDkuqTmmJPnjq/looMgPT09PT09PT09PT09PT09PT09PT09PQpjbGFzcyBDcnlwdG9NYXJrZXRFbnYoZ3ltLkVudik6CiAgICAmcXVvdDsmcXVvdDsmcXVvdDvliqDlr4botKfluIHkuqTmmJPlvLrljJblrabkuaDnjq/looMmcXVvdDsmcXVvdDsmcXVvdDsKICAgIG1ldGFkYXRhID0geydyZW5kZXIubW9kZXMnOiBbJ2h1bWFuJ119CiAgICAKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBkZiwgaW5pdGlhbF9iYWxhbmNlPTEwMDAwLCB0cmFuc2FjdGlvbl9jb3N0PTAuMDAxLCBtYXhfZHJhd2Rvd249MC4yKToKICAgICAgICBzdXBlcihDcnlwdG9NYXJrZXRFbnYsIHNlbGYpLl9faW5pdF9fKCkKICAgICAgICAKICAgICAgICBzZWxmLmRmID0gZGYKICAgICAgICBzZWxmLmN1cnJlbnRfc3RlcCA9IDAKICAgICAgICBzZWxmLmluaXRpYWxfYmFsYW5jZSA9IGluaXRpYWxfYmFsYW5jZQogICAgICAgIHNlbGYudHJhbnNhY3Rpb25fY29zdCA9IHRyYW5zYWN0aW9uX2Nvc3QKICAgICAgICBzZWxmLm1heF9kcmF3ZG93biA9IG1heF9kcmF3ZG93bgogICAgICAgIAogICAgICAgICMg54q25oCB56m66Ze0OiDmioDmnK/mjIfmoIcgKyDmjIHku5Pkv6Hmga8KICAgICAgICBzZWxmLm9ic2VydmF0aW9uX3NwYWNlID0gc3BhY2VzLkJveCgKICAgICAgICAgICAgbG93PS1ucC5pbmYsIGhpZ2g9bnAuaW5mLCAKICAgICAgICAgICAgc2hhcGU9KDgsKSAgIyDnroDljJbnirbmgIHnqbrpl7QKICAgICAgICApCiAgICAgICAgCiAgICAgICAgIyDliqjkvZznqbrpl7Q6IFvkubDlhaUsIOWNluWHuiwg5oyB5pyJXSArIOS7k+S9jeavlOS+iwogICAgICAgIHNlbGYuYWN0aW9uX3NwYWNlID0gc3BhY2VzLkJveChsb3c9bnAuYXJyYXkoWy0xLCAwXSksIGhpZ2g9bnAuYXJyYXkoWzEsIDFdKSwgZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAKICAgICAgICBzZWxmLnJlc2V0KCkKICAgIAogICAgZGVmIHJlc2V0KHNlbGYpOgogICAgICAgIHNlbGYuYmFsYW5jZSA9IHNlbGYuaW5pdGlhbF9iYWxhbmNlCiAgICAgICAgc2VsZi5jcnlwdG9faGVsZCA9IDAKICAgICAgICBzZWxmLmN1cnJlbnRfdmFsdWUgPSBzZWxmLmluaXRpYWxfYmFsYW5jZQogICAgICAgIHNlbGYucHJldmlvdXNfdmFsdWUgPSBzZWxmLmluaXRpYWxfYmFsYW5jZQogICAgICAgIHNlbGYucGVha192YWx1ZSA9IHNlbGYuaW5pdGlhbF9iYWxhbmNlCiAgICAgICAgc2VsZi5jdXJyZW50X3N0ZXAgPSAwCiAgICAgICAgc2VsZi50cmFkZXMgPSBbXQogICAgICAgIHNlbGYucG9ydGZvbGlvX2hpc3RvcnkgPSBbc2VsZi5pbml0aWFsX2JhbGFuY2VdCiAgICAgICAgc2VsZi5kcmF3ZG93biA9IDAuMAogICAgICAgIHNlbGYudHJhZGVfY291bnQgPSAwCiAgICAgICAgCiAgICAgICAgcmV0dXJuIHNlbGYuX25leHRfb2JzZXJ2YXRpb24oKQogICAgCiAgICBkZWYgX25leHRfb2JzZXJ2YXRpb24oc2VsZik6CiAgICAgICAgIyDojrflj5blvZPliY3nirbmgIHnmoTmioDmnK/mjIfmoIcKICAgICAgICBmZWF0dXJlcyA9IHNlbGYuZGYuaWxvY1tzZWxmLmN1cnJlbnRfc3RlcF1bWydyc2knLCAnbWFjZCcsICdhdHInLCAnYmJfd2lkdGgnLCAnbWFjZF9kaWZmJ11dLnZhbHVlcwogICAgICAgIHN0YXRlID0gZmVhdHVyZXMuYXN0eXBlKG5wLmZsb2F0MzIpCiAgICAgICAgCiAgICAgICAgIyDmt7vliqDmjIHku5Pkv6Hmga8KICAgICAgICBzdGF0ZSA9IG5wLmFwcGVuZChzdGF0ZSwgWwogICAgICAgICAgICBzZWxmLmJhbGFuY2UgLyBzZWxmLmluaXRpYWxfYmFsYW5jZSwKICAgICAgICAgICAgc2VsZi5jcnlwdG9faGVsZCAqIHNlbGYuX2dldF9jdXJyZW50X3ByaWNlKCkgLyBzZWxmLmluaXRpYWxfYmFsYW5jZSwKICAgICAgICAgICAgc2VsZi5jcnlwdG9faGVsZAogICAgICAgIF0pCiAgICAgICAgCiAgICAgICAgcmV0dXJuIHN0YXRlCiAgICAKICAgIGRlZiBfZ2V0X2N1cnJlbnRfcHJpY2Uoc2VsZik6CiAgICAgICAgcmV0dXJuIHNlbGYuZGYuaWxvY1tzZWxmLmN1cnJlbnRfc3RlcF1bJ2Nsb3NlJ10KICAgIAogICAgZGVmIHN0ZXAoc2VsZiwgYWN0aW9uKToKICAgICAgICAjIOaJp+ihjOS6pOaYk+WKqOS9nAogICAgICAgIGN1cnJlbnRfcHJpY2UgPSBzZWxmLl9nZXRfY3VycmVudF9wcmljZSgpCiAgICAgICAgYWN0aW9uX3R5cGUgPSBhY3Rpb25bMF0gICMgWy0xLCAxXSDkubDlhaUv5Y2W5Ye65L+h5Y+3CiAgICAgICAgcG9zaXRpb25fc2l6ZSA9IGFjdGlvblsxXSAgIyBbMCwgMV0g5LuT5L2N5q+U5L6LCiAgICAgICAgCiAgICAgICAgIyDorqHnrpfkuqTmmJPph48KICAgICAgICBpZiBhY3Rpb25fdHlwZSAmZ3Q7IDAuMjogICMg5Lmw5YWlCiAgICAgICAgICAgIHRyYWRlX2Ftb3VudCA9IG1pbigKICAgICAgICAgICAgICAgIHNlbGYuYmFsYW5jZSAqIHBvc2l0aW9uX3NpemUgLyBjdXJyZW50X3ByaWNlLAogICAgICAgICAgICAgICAgc2VsZi5iYWxhbmNlIC8gY3VycmVudF9wcmljZQogICAgICAgICAgICApCiAgICAgICAgICAgIGNvc3QgPSB0cmFkZV9hbW91bnQgKiBjdXJyZW50X3ByaWNlICogc2VsZi50cmFuc2FjdGlvbl9jb3N0CiAgICAgICAgICAgIHNlbGYuYmFsYW5jZSAtPSAodHJhZGVfYW1vdW50ICogY3VycmVudF9wcmljZSArIGNvc3QpCiAgICAgICAgICAgIHNlbGYuY3J5cHRvX2hlbGQgKz0gdHJhZGVfYW1vdW50CiAgICAgICAgICAgIHNlbGYudHJhZGVfY291bnQgKz0gMQogICAgICAgIAogICAgICAgIGVsaWYgYWN0aW9uX3R5cGUgJmx0OyAtMC4yOiAgIyDljZblh7oKICAgICAgICAgICAgdHJhZGVfYW1vdW50ID0gbWluKHNlbGYuY3J5cHRvX2hlbGQsIHNlbGYuY3J5cHRvX2hlbGQgKiBwb3NpdGlvbl9zaXplKQogICAgICAgICAgICBjb3N0ID0gdHJhZGVfYW1vdW50ICogY3VycmVudF9wcmljZSAqIHNlbGYudHJhbnNhY3Rpb25fY29zdAogICAgICAgICAgICBzZWxmLmJhbGFuY2UgKz0gKHRyYWRlX2Ftb3VudCAqIGN1cnJlbnRfcHJpY2UgLSBjb3N0KQogICAgICAgICAgICBzZWxmLmNyeXB0b19oZWxkIC09IHRyYWRlX2Ftb3VudAogICAgICAgICAgICBzZWxmLnRyYWRlX2NvdW50ICs9IDEKICAgICAgICAKICAgICAgICAjIOabtOaWsOWIsOS4i+S4gOatpQogICAgICAgIHNlbGYuY3VycmVudF9zdGVwICs9IDEKICAgICAgICBpZiBzZWxmLmN1cnJlbnRfc3RlcCAmZ3Q7PSBsZW4oc2VsZi5kZikgLSAxOgogICAgICAgICAgICBkb25lID0gVHJ1ZQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGRvbmUgPSBGYWxzZQogICAgICAgIAogICAgICAgICMg6K6h566X5paw5Lu35YC8CiAgICAgICAgc2VsZi5wcmV2aW91c192YWx1ZSA9IHNlbGYuY3VycmVudF92YWx1ZQogICAgICAgIGN1cnJlbnRfcHJpY2UgPSBzZWxmLl9nZXRfY3VycmVudF9wcmljZSgpCiAgICAgICAgc2VsZi5jdXJyZW50X3ZhbHVlID0gc2VsZi5iYWxhbmNlICsgc2VsZi5jcnlwdG9faGVsZCAqIGN1cnJlbnRfcHJpY2UKICAgICAgICAKICAgICAgICAjIOabtOaWsOWzsOWAvOWSjOWbnuaSpAogICAgICAgIGlmIHNlbGYuY3VycmVudF92YWx1ZSAmZ3Q7IHNlbGYucGVha192YWx1ZToKICAgICAgICAgICAgc2VsZi5wZWFrX3ZhbHVlID0gc2VsZi5jdXJyZW50X3ZhbHVlCiAgICAgICAgc2VsZi5kcmF3ZG93biA9IChzZWxmLnBlYWtfdmFsdWUgLSBzZWxmLmN1cnJlbnRfdmFsdWUpIC8gc2VsZi5wZWFrX3ZhbHVlCiAgICAgICAgCiAgICAgICAgIyDnhpTmlq3mnLrliLYKICAgICAgICBpZiBzZWxmLmRyYXdkb3duICZndDsgc2VsZi5tYXhfZHJhd2Rvd246CiAgICAgICAgICAgIGRvbmUgPSBUcnVlCiAgICAgICAgICAgIHByaW50KGYmcXVvdDvnhpTmlq3op6blj5EhIOWbnuaSpOi2hei/h3tzZWxmLm1heF9kcmF3ZG93bioxMDA6LjBmfSUmcXVvdDspCiAgICAgICAgCiAgICAgICAgc2VsZi5wb3J0Zm9saW9faGlzdG9yeS5hcHBlbmQoc2VsZi5jdXJyZW50X3ZhbHVlKQogICAgICAgIAogICAgICAgICMg6K6h566X5aWW5YqxCiAgICAgICAgcmV3YXJkID0gc2VsZi5jdXJyZW50X3ZhbHVlIC0gc2VsZi5wcmV2aW91c192YWx1ZQogICAgICAgIAogICAgICAgICMg6I635Y+W5paw54q25oCBCiAgICAgICAgbmV4dF9zdGF0ZSA9IHNlbGYuX25leHRfb2JzZXJ2YXRpb24oKQogICAgICAgIAogICAgICAgIHJldHVybiBuZXh0X3N0YXRlLCByZXdhcmQsIGRvbmUsIHt9CiAgICAKICAgIGRlZiByZW5kZXIoc2VsZiwgbW9kZT0naHVtYW4nKToKICAgICAgICBwcm9maXRfcGN0ID0gKHNlbGYuY3VycmVudF92YWx1ZSAtIHNlbGYuaW5pdGlhbF9iYWxhbmNlKSAvIHNlbGYuaW5pdGlhbF9iYWxhbmNlICogMTAwCiAgICAgICAgcHJpbnQoZiZxdW90O1N0ZXA6IHtzZWxmLmN1cnJlbnRfc3RlcH0ve2xlbihzZWxmLmRmKX0gfCAmcXVvdDsKICAgICAgICAgICAgICBmJnF1b3Q7VmFsdWU6ICR7c2VsZi5jdXJyZW50X3ZhbHVlOiwuMmZ9IHwgJnF1b3Q7CiAgICAgICAgICAgICAgZiZxdW90O1Byb2ZpdDoge3Byb2ZpdF9wY3Q6LjJmfSUgfCAmcXVvdDsKICAgICAgICAgICAgICBmJnF1b3Q7RHJhd2Rvd246IHtzZWxmLmRyYXdkb3duKjEwMDouMmZ9JSB8ICZxdW90OwogICAgICAgICAgICAgIGYmcXVvdDtUcmFkZXM6IHtzZWxmLnRyYWRlX2NvdW50fSZxdW90OykKCiMgPT09PT09PT09PT09PT09PT09PT09PSDkuLvkuqTmmJPns7vnu58gPT09PT09PT09PT09PT09PT09PT09PQpjbGFzcyBDcnlwdG9EUkxUcmFkaW5nU3lzdGVtOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHN5bWJvbHM9WydQRVBFL1VTRFQnLCAnU09ML1VTRFQnXSwgaW5pdGlhbF9iYWxhbmNlPTEwMDAwKToKICAgICAgICBzZWxmLnN5bWJvbHMgPSBzeW1ib2xzCiAgICAgICAgc2VsZi5pbml0aWFsX2JhbGFuY2UgPSBpbml0aWFsX2JhbGFuY2UKICAgICAgICBzZWxmLmV4Y2hhbmdlID0gc2VsZi5fc2V0dXBfZXhjaGFuZ2UoKQogICAgICAgIHNlbGYubW9kZWxzID0ge30KICAgICAgICBzZWxmLnNjYWxlcnMgPSB7fQogICAgICAgIHNlbGYucG9ydGZvbGlvID0gewogICAgICAgICAgICAndmFsdWVzJzogW2luaXRpYWxfYmFsYW5jZV0sCiAgICAgICAgICAgICdyZXR1cm5zJzogW10sCiAgICAgICAgICAgICdjdXJyZW50X3ZhbHVlJzogaW5pdGlhbF9iYWxhbmNlLAogICAgICAgICAgICAnZGFpbHlfcmV0dXJuJzogMC4wCiAgICAgICAgfQogICAgICAgIHNlbGYuYmFsYW5jZSA9IGluaXRpYWxfYmFsYW5jZQogICAgICAgIHNlbGYuY3J5cHRvX2hlbGQgPSAwCiAgICAgICAgCiAgICBkZWYgX3NldHVwX2V4Y2hhbmdlKHNlbGYpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+iuvue9ruS6pOaYk+aJgOi/nuaOpSZxdW90OyZxdW90OyZxdW90OwogICAgICAgIGV4Y2hhbmdlID0gY2N4dC5iaW5hbmNlKHsKICAgICAgICAgICAgJ2FwaUtleSc6IEFQSV9LRVksCiAgICAgICAgICAgICdzZWNyZXQnOiBTRUNSRVRfS0VZLAogICAgICAgICAgICAncmF0ZUxpbWl0JzogMTAwLAogICAgICAgICAgICAnZW5hYmxlUmF0ZUxpbWl0JzogVHJ1ZSwKICAgICAgICAgICAgJ29wdGlvbnMnOiB7CiAgICAgICAgICAgICAgICAnZGVmYXVsdFR5cGUnOiAnc3BvdCcsICAjIOS9v+eUqOeOsOi0p+S6pOaYkwogICAgICAgICAgICB9CiAgICAgICAgfSkKICAgICAgICAKICAgICAgICAjIOajgOafpei/nuaOpQogICAgICAgIHRyeToKICAgICAgICAgICAgbWFya2V0cyA9IGV4Y2hhbmdlLmxvYWRfbWFya2V0cygpCiAgICAgICAgICAgIHByaW50KCZxdW90O+S6pOaYk+aJgOi/nuaOpeaIkOWKnyEg5Y+v55So5Lqk5piT5a+55pWw6YePOiZxdW90OywgbGVuKG1hcmtldHMpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgcHJpbnQoZiZxdW90O+S6pOaYk+aJgOi/nuaOpeWksei0pToge3N0cihlKX0mcXVvdDspCiAgICAgICAgICAgIHByaW50KCZxdW90O+S9v+eUqOaooeaLn+aooeW8j+i/kOihjC4uLiZxdW90OykKICAgICAgICAKICAgICAgICByZXR1cm4gZXhjaGFuZ2UKICAgIAogICAgZGVmIGZldGNoX2hpc3RvcmljYWxfZGF0YShzZWxmLCBzeW1ib2wsIHRpbWVmcmFtZT0nMWQnLCBsaW1pdD0xMDApOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+iOt+WPluWOhuWPsuaVsOaNriZxdW90OyZxdW90OyZxdW90OwogICAgICAgIHByaW50KGYmcXVvdDvojrflj5bljoblj7LmlbDmja46IHtzeW1ib2x9Li4uJnF1b3Q7KQogICAgICAgIAogICAgICAgIHRyeToKICAgICAgICAgICAgIyDlsJ3or5Xku47kuqTmmJPmiYDojrflj5bmlbDmja4KICAgICAgICAgICAgb2hsY3YgPSBzZWxmLmV4Y2hhbmdlLmZldGNoX29obGN2KHN5bWJvbCwgdGltZWZyYW1lLCBsaW1pdD1saW1pdCkKICAgICAgICAgICAgZGYgPSBwZC5EYXRhRnJhbWUob2hsY3YsIGNvbHVtbnM9Wyd0aW1lc3RhbXAnLCAnb3BlbicsICdoaWdoJywgJ2xvdycsICdjbG9zZScsICd2b2x1bWUnXSkKICAgICAgICAgICAgZGZbJ3RpbWVzdGFtcCddID0gcGQudG9fZGF0ZXRpbWUoZGZbJ3RpbWVzdGFtcCddLCB1bml0PSdtcycpCiAgICAgICAgICAgIGRmLnNldF9pbmRleCgndGltZXN0YW1wJywgaW5wbGFjZT1UcnVlKQogICAgICAgICAgICByZXR1cm4gZGYKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHByaW50KGYmcXVvdDvojrflj5bmlbDmja7lpLHotKU6IHtzdHIoZSl9LCDkvb/nlKjmqKHmi5/mlbDmja4mcXVvdDspCiAgICAgICAgICAgICMg55Sf5oiQ5qih5ouf5pWw5o2uCiAgICAgICAgICAgIGRhdGVzID0gcGQuZGF0ZV9yYW5nZShlbmQ9cGQuVGltZXN0YW1wLm5vdygpLCBwZXJpb2RzPWxpbWl0LCBmcmVxPSdEJykKICAgICAgICAgICAgcHJpY2VzID0gbnAuY3VtcHJvZCgxICsgbnAucmFuZG9tLm5vcm1hbCgwLjAwNSwgMC4wMywgbGltaXQpKQogICAgICAgICAgICB2b2x1bWVzID0gbnAucmFuZG9tLnJhbmRpbnQoMTAwMDAsIDUwMDAwLCBsaW1pdCkKICAgICAgICAgICAgCiAgICAgICAgICAgIGRmID0gcGQuRGF0YUZyYW1lKHsKICAgICAgICAgICAgICAgICd0aW1lc3RhbXAnOiBkYXRlcywKICAgICAgICAgICAgICAgICdvcGVuJzogcHJpY2VzICogMC45OSwKICAgICAgICAgICAgICAgICdoaWdoJzogcHJpY2VzICogMS4wMSwKICAgICAgICAgICAgICAgICdsb3cnOiBwcmljZXMgKiAwLjk4LAogICAgICAgICAgICAgICAgJ2Nsb3NlJzogcHJpY2VzLAogICAgICAgICAgICAgICAgJ3ZvbHVtZSc6IHZvbHVtZXMKICAgICAgICAgICAgfSkKICAgICAgICAgICAgCiAgICAgICAgICAgIGRmWyd0aW1lc3RhbXAnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWyd0aW1lc3RhbXAnXSkKICAgICAgICAgICAgZGYuc2V0X2luZGV4KCd0aW1lc3RhbXAnLCBpbnBsYWNlPVRydWUpCiAgICAgICAgICAgIHJldHVybiBkZgogICAgCiAgICBkZWYgcHJlcGFyZV9kYXRhc2V0KHNlbGYpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+WHhuWkh+aVsOaNrumbhiZxdW90OyZxdW90OyZxdW90OwogICAgICAgIGFsbF9kYXRhID0ge30KICAgICAgICAKICAgICAgICBmb3Igc3ltYm9sIGluIHNlbGYuc3ltYm9sczoKICAgICAgICAgICAgIyDojrflj5bljoblj7LmlbDmja4KICAgICAgICAgICAgZGYgPSBzZWxmLmZldGNoX2hpc3RvcmljYWxfZGF0YShzeW1ib2wsIGxpbWl0PTIwMCkKICAgICAgICAgICAgCiAgICAgICAgICAgICMg6K6h566X54m55b6BCiAgICAgICAgICAgIGRmID0gY2FsY3VsYXRlX2ZlYXR1cmVzKGRmLCBzeW1ib2wpCiAgICAgICAgICAgIAogICAgICAgICAgICAjIOeJueW+gemAieaLqQogICAgICAgICAgICBmZWF0dXJlcyA9IGRmLmRyb3AoY29sdW1ucz1bJ29wZW4nLCAnaGlnaCcsICdsb3cnLCAnY2xvc2UnLCAndm9sdW1lJywgJ3RhcmdldCddKQogICAgICAgICAgICB0YXJnZXQgPSBkZlsndGFyZ2V0J10KICAgICAgICAgICAgCiAgICAgICAgICAgICMg6YCJ5oup5pyA6YeN6KaB55qE54m55b6BCiAgICAgICAgICAgIHNlbGVjdGVkX2ZlYXR1cmVzID0gc2VsZWN0X2ZlYXR1cmVzKGZlYXR1cmVzLCB0YXJnZXQsIGs9NSkKICAgICAgICAgICAgCiAgICAgICAgICAgICMg5b2S5LiA5YyWCiAgICAgICAgICAgIHNjYWxlciA9IFN0YW5kYXJkU2NhbGVyKCkKICAgICAgICAgICAgc2NhbGVkX2ZlYXR1cmVzID0gc2NhbGVyLmZpdF90cmFuc2Zvcm0oc2VsZWN0ZWRfZmVhdHVyZXMpCiAgICAgICAgICAgIGRmW3NlbGVjdGVkX2ZlYXR1cmVzLmNvbHVtbnNdID0gc2NhbGVkX2ZlYXR1cmVzCiAgICAgICAgICAgIAogICAgICAgICAgICAjIOWtmOWCqOaVsOaNrgogICAgICAgICAgICBhbGxfZGF0YVtzeW1ib2xdID0gewogICAgICAgICAgICAgICAgJ2ZlYXR1cmVzJzogc2VsZWN0ZWRfZmVhdHVyZXMsCiAgICAgICAgICAgICAgICAndGFyZ2V0JzogdGFyZ2V0LAogICAgICAgICAgICAgICAgJ29yaWdpbmFsJzogZGYsCiAgICAgICAgICAgICAgICAnc2NhbGVyJzogc2NhbGVyCiAgICAgICAgICAgIH0KICAgICAgICAgICAgc2VsZi5zY2FsZXJzW3N5bWJvbF0gPSBzY2FsZXIKICAgICAgICAKICAgICAgICByZXR1cm4gYWxsX2RhdGEKICAgIAogICAgZGVmIHRyYWluX2RybF9hZ2VudChzZWxmLCBkYXRhLCBzeW1ib2wsIHRpbWVzdGVwcz0yMDAwMCk6CiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q76K6t57uD5rex5bqm5by65YyW5a2m5Lmg5Luj55CGJnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgcHJpbnQoZiZxdW90O+iuree7g0RSTOS7o+eQhjoge3N5bWJvbH0uLi4mcXVvdDspCiAgICAgICAgCiAgICAgICAgIyDliJvlu7rnjq/looMKICAgICAgICBlbnYgPSBDcnlwdG9NYXJrZXRFbnYoZGF0YVtzeW1ib2xdWydvcmlnaW5hbCddLCBzZWxmLmluaXRpYWxfYmFsYW5jZSkKICAgICAgICBlbnYgPSBEdW1teVZlY0VudihbbGFtYmRhOiBlbnZdKQogICAgICAgIAogICAgICAgICMg5Yid5aeL5YyWUFBP5qih5Z6LCiAgICAgICAgcG9saWN5X2t3YXJncyA9IGRpY3QobmV0X2FyY2g9WzY0LCA2NF0pCiAgICAgICAgCiAgICAgICAgbW9kZWwgPSBQUE8oCiAgICAgICAgICAgICZxdW90O01scFBvbGljeSZxdW90OywgCiAgICAgICAgICAgIGVudiwgCiAgICAgICAgICAgIHZlcmJvc2U9MSwgCiAgICAgICAgICAgIHRlbnNvcmJvYXJkX2xvZz1mJnF1b3Q7Li90ZW5zb3Jib2FyZC97c3ltYm9sfS8mcXVvdDssCiAgICAgICAgICAgIHBvbGljeV9rd2FyZ3M9cG9saWN5X2t3YXJncywKICAgICAgICAgICAgbGVhcm5pbmdfcmF0ZT0zZS00LAogICAgICAgICAgICBnYW1tYT0wLjk5LAogICAgICAgICAgICBlbnRfY29lZj0wLjAxCiAgICAgICAgKQogICAgICAgIAogICAgICAgICMg6K6t57uD5qih5Z6LCiAgICAgICAgbW9kZWwubGVhcm4odG90YWxfdGltZXN0ZXBzPXRpbWVzdGVwcykKICAgICAgICAKICAgICAgICAjIOS/neWtmOaooeWeiwogICAgICAgIG9zLm1ha2VkaXJzKCZxdW90O21vZGVscyZxdW90OywgZXhpc3Rfb2s9VHJ1ZSkKICAgICAgICBtb2RlbC5zYXZlKGYmcXVvdDttb2RlbHMvZHJsX21vZGVsX3tzeW1ib2wucmVwbGFjZSgnLycsICdfJyl9JnF1b3Q7KQogICAgICAgIHNlbGYubW9kZWxzW3N5bWJvbF0gPSBtb2RlbAogICAgICAgIAogICAgICAgIHJldHVybiBtb2RlbAogICAgCiAgICBkZWYgYmFja3Rlc3Qoc2VsZiwgZGF0YSwgc3ltYm9sKToKICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDvlm57mtYvkuqTmmJPnrZbnlaUmcXVvdDsmcXVvdDsmcXVvdDsKICAgICAgICBpZiBzeW1ib2wgbm90IGluIHNlbGYubW9kZWxzOgogICAgICAgICAgICBwcmludChmJnF1b3Q7e3N5bWJvbH3mnKrmib7liLDmqKHlnovvvIzmraPlnKjorq3nu4MuLi4mcXVvdDspCiAgICAgICAgICAgIHNlbGYudHJhaW5fZHJsX2FnZW50KGRhdGEsIHN5bWJvbCwgdGltZXN0ZXBzPTEwMDAwKQogICAgICAgIAogICAgICAgICMg5Yib5bu6546v5aKDCiAgICAgICAgZW52ID0gQ3J5cHRvTWFya2V0RW52KGRhdGFbc3ltYm9sXVsnb3JpZ2luYWwnXSwgc2VsZi5pbml0aWFsX2JhbGFuY2UpCiAgICAgICAgbW9kZWwgPSBzZWxmLm1vZGVsc1tzeW1ib2xdCiAgICAgICAgCiAgICAgICAgIyDov5DooYzlm57mtYsKICAgICAgICBvYnMgPSBlbnYucmVzZXQoKQogICAgICAgIGRvbmUgPSBGYWxzZQogICAgICAgIHN0ZXBfY291bnQgPSAwCiAgICAgICAgd2hpbGUgbm90IGRvbmU6CiAgICAgICAgICAgIGFjdGlvbiwgXyA9IG1vZGVsLnByZWRpY3Qob2JzKQogICAgICAgICAgICBvYnMsIF8sIGRvbmUsIF8gPSBlbnYuc3RlcChhY3Rpb24pCiAgICAgICAgICAgIGlmIGRvbmUgb3Igc3RlcF9jb3VudCAlIDIwID09IDA6CiAgICAgICAgICAgICAgICBlbnYucmVuZGVyKCkKICAgICAgICAgICAgc3RlcF9jb3VudCArPSAxCiAgICAgICAgCiAgICAgICAgIyDliIbmnpDnu5PmnpwKICAgICAgICBwb3J0Zm9saW9faGlzdG9yeSA9IGVudi5wb3J0Zm9saW9faGlzdG9yeQogICAgICAgIHJldHVybnMgPSBwZC5TZXJpZXMocG9ydGZvbGlvX2hpc3RvcnkpLnBjdF9jaGFuZ2UoKS5kcm9wbmEoKQogICAgICAgIAogICAgICAgICMg5pu05paw5oqV6LWE57uE5ZCI6K6w5b2VCiAgICAgICAgc2VsZi5wb3J0Zm9saW9bJ3ZhbHVlcyddID0gcG9ydGZvbGlvX2hpc3RvcnkKICAgICAgICBzZWxmLnBvcnRmb2xpb1sncmV0dXJucyddID0gcmV0dXJucy50b2xpc3QoKQogICAgICAgIHNlbGYucG9ydGZvbGlvWydjdXJyZW50X3ZhbHVlJ10gPSBwb3J0Zm9saW9faGlzdG9yeVstMV0KICAgICAgICBzZWxmLnBvcnRmb2xpb1snZGFpbHlfcmV0dXJuJ10gPSByZXR1cm5zLmlsb2NbLTFdIGlmIGxlbihyZXR1cm5zKSAmZ3Q7IDAgZWxzZSAwCiAgICAgICAgCiAgICAgICAgIyDmgKfog73miqXlkYoKICAgICAgICBwcmludCgmcXVvdDtcbiZxdW90OyArICZxdW90Oz0mcXVvdDsqNjApCiAgICAgICAgcHJpbnQoZiZxdW90O3tzeW1ib2x95Zue5rWL57uT5p6cJnF1b3Q7KQogICAgICAgIHByaW50KCZxdW90Oz0mcXVvdDsqNjApCiAgICAgICAgcHJpbnQoZiZxdW90O+WIneWni+i1hOmHkTogJHtzZWxmLmluaXRpYWxfYmFsYW5jZTosLjJmfSZxdW90OykKICAgICAgICBwcmludChmJnF1b3Q75pyA57uI6LWE5LqnOiAke3BvcnRmb2xpb19oaXN0b3J5Wy0xXTosLjJmfSZxdW90OykKICAgICAgICBwcmludChmJnF1b3Q75oC75pS255uKOiB7KHBvcnRmb2xpb19oaXN0b3J5Wy0xXSAtIHNlbGYuaW5pdGlhbF9iYWxhbmNlKSAvIHNlbGYuaW5pdGlhbF9iYWxhbmNlICogMTAwOi4yZn0lJnF1b3Q7KQogICAgICAgIHByaW50KGYmcXVvdDvmnIDlpKflm57mkqQ6IHtzZWxmLmNhbGN1bGF0ZV9tYXhfZHJhd2Rvd24ocG9ydGZvbGlvX2hpc3RvcnkpICogMTAwOi4yZn0lJnF1b3Q7KQogICAgICAgIHByaW50KGYmcXVvdDvkuqTmmJPmrKHmlbA6IHtlbnYudHJhZGVfY291bnR9JnF1b3Q7KQogICAgICAgIAogICAgICAgICMg57uY5Yi257uT5p6cCiAgICAgICAgc2VsZi5wbG90X3Jlc3VsdHMoc3ltYm9sLCBkYXRhW3N5bWJvbF1bJ29yaWdpbmFsJ10sIHBvcnRmb2xpb19oaXN0b3J5KQogICAgICAgIAogICAgICAgIHJldHVybiByZXR1cm5zCiAgICAKICAgIGRlZiBjYWxjdWxhdGVfbWF4X2RyYXdkb3duKHNlbGYsIHZhbHVlcyk6CiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q76K6h566X5pyA5aSn5Zue5pKkJnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgcGVhayA9IHZhbHVlc1swXQogICAgICAgIG1heF9kcmF3ZG93biA9IDAKICAgICAgICBmb3IgdmFsdWUgaW4gdmFsdWVzOgogICAgICAgICAgICBpZiB2YWx1ZSAmZ3Q7IHBlYWs6CiAgICAgICAgICAgICAgICBwZWFrID0gdmFsdWUKICAgICAgICAgICAgZHJhd2Rvd24gPSAocGVhayAtIHZhbHVlKSAvIHBlYWsKICAgICAgICAgICAgaWYgZHJhd2Rvd24gJmd0OyBtYXhfZHJhd2Rvd246CiAgICAgICAgICAgICAgICBtYXhfZHJhd2Rvd24gPSBkcmF3ZG93bgogICAgICAgIHJldHVybiBtYXhfZHJhd2Rvd24KICAgIAogICAgZGVmIHBsb3RfcmVzdWx0cyhzZWxmLCBzeW1ib2wsIHByaWNlX2RhdGEsIHBvcnRmb2xpb19oaXN0b3J5KToKICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDvnu5jliLblm57mtYvnu5PmnpwmcXVvdDsmcXVvdDsmcXVvdDsKICAgICAgICBwbHQuZmlndXJlKGZpZ3NpemU9KDE0LCAxMCkpCiAgICAgICAgCiAgICAgICAgIyDku7fmoLzlm77ooagKICAgICAgICBwbHQuc3VicGxvdCgyLCAxLCAxKQogICAgICAgIHBsdC5wbG90KHByaWNlX2RhdGEuaW5kZXgsIHByaWNlX2RhdGFbJ2Nsb3NlJ10sIGxhYmVsPSfku7fmoLwnLCBjb2xvcj0nYmx1ZScpCiAgICAgICAgcGx0LnRpdGxlKGYmcXVvdDt7c3ltYm9sfSDku7fmoLzotbDlir8mcXVvdDssIGZvbnRzaXplPTE0KQogICAgICAgIHBsdC55bGFiZWwoJ+S7t+agvCAoVVNEVCknLCBmb250c2l6ZT0xMikKICAgICAgICBwbHQuZ3JpZChUcnVlLCBsaW5lc3R5bGU9Jy0tJywgYWxwaGE9MC43KQogICAgICAgIHBsdC5sZWdlbmQoKQogICAgICAgIAogICAgICAgICMg6LWE5Lqn5Zu+6KGoCiAgICAgICAgcGx0LnN1YnBsb3QoMiwgMSwgMikKICAgICAgICBwbHQucGxvdChwcmljZV9kYXRhLmluZGV4LCBwb3J0Zm9saW9faGlzdG9yeVs6bGVuKHByaWNlX2RhdGEpXSwgbGFiZWw9J+i1hOS6p+aAu+WAvCcsIGNvbG9yPSdncmVlbicpCiAgICAgICAgcGx0LnRpdGxlKCZxdW90O+i1hOS6p+WPmOWMliZxdW90OywgZm9udHNpemU9MTQpCiAgICAgICAgcGx0LnhsYWJlbCgn5pel5pyfJywgZm9udHNpemU9MTIpCiAgICAgICAgcGx0LnlsYWJlbCgn6LWE5LqnIChVU0RUKScsIGZvbnRzaXplPTEyKQogICAgICAgIHBsdC5ncmlkKFRydWUsIGxpbmVzdHlsZT0nLS0nLCBhbHBoYT0wLjcpCiAgICAgICAgcGx0LmxlZ2VuZCgpCiAgICAgICAgCiAgICAgICAgcGx0LnRpZ2h0X2xheW91dCgpCiAgICAgICAgcGx0LnNhdmVmaWcoZidiYWNrdGVzdF97c3ltYm9sLnJlcGxhY2UoJnF1b3Q7LyZxdW90OywgJnF1b3Q7XyZxdW90Oyl9LnBuZycpCiAgICAgICAgcGx0LnNob3coKQogICAgCiAgICBkZWYgbGl2ZV90cmFkaW5nKHNlbGYsIHN5bWJvbCk6CiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q75a6e55uY5Lqk5piTJnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgaWYgc3ltYm9sIG5vdCBpbiBzZWxmLm1vZGVsczoKICAgICAgICAgICAgcHJpbnQoZiZxdW90O3tzeW1ib2x95pyq5om+5Yiw5qih5Z6L77yM5q2j5Zyo6K6t57uDLi4uJnF1b3Q7KQogICAgICAgICAgICBkYXRhID0gc2VsZi5wcmVwYXJlX2RhdGFzZXQoKQogICAgICAgICAgICBzZWxmLnRyYWluX2RybF9hZ2VudChkYXRhLCBzeW1ib2wpCiAgICAgICAgCiAgICAgICAgbW9kZWwgPSBzZWxmLm1vZGVsc1tzeW1ib2xdCiAgICAgICAgc2NhbGVyID0gc2VsZi5zY2FsZXJzW3N5bWJvbF0KICAgICAgICAKICAgICAgICBwcmludChmJnF1b3Q75ZCv5Yqoe3N5bWJvbH3lrp7nm5jkuqTmmJMuLi4mcXVvdDspCiAgICAgICAgdHJhZGVfY291bnQgPSAwCiAgICAgICAgCiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgIyDojrflj5bmnIDmlrDmlbDmja4KICAgICAgICAgICAgICAgIG5vdyA9IGRhdGV0aW1lLnV0Y25vdygpCiAgICAgICAgICAgICAgICBzdGFydF90aW1lID0gbm93IC0gdGltZWRlbHRhKGRheXM9MzApCiAgICAgICAgICAgICAgICBvaGxjdiA9IHNlbGYuZXhjaGFuZ2UuZmV0Y2hfb2hsY3Yoc3ltYm9sLCAnMWQnLCBzaW5jZT1pbnQoc3RhcnRfdGltZS50aW1lc3RhbXAoKSAqIDEwMDApKQogICAgICAgICAgICAgICAgZGYgPSBwZC5EYXRhRnJhbWUob2hsY3YsIGNvbHVtbnM9Wyd0aW1lc3RhbXAnLCAnb3BlbicsICdoaWdoJywgJ2xvdycsICdjbG9zZScsICd2b2x1bWUnXSkKICAgICAgICAgICAgICAgIGRmWyd0aW1lc3RhbXAnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWyd0aW1lc3RhbXAnXSwgdW5pdD0nbXMnKQogICAgICAgICAgICAgICAgZGYuc2V0X2luZGV4KCd0aW1lc3RhbXAnLCBpbnBsYWNlPVRydWUpCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICMg6K6h566X54m55b6BCiAgICAgICAgICAgICAgICBkZiA9IGNhbGN1bGF0ZV9mZWF0dXJlcyhkZiwgc3ltYm9sKQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAjIOmAieaLqeeJueW+geW5tuW9kuS4gOWMlgogICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBkZi5kcm9wKGNvbHVtbnM9WydvcGVuJywgJ2hpZ2gnLCAnbG93JywgJ2Nsb3NlJywgJ3ZvbHVtZScsICd0YXJnZXQnXSkKICAgICAgICAgICAgICAgIHNlbGVjdGVkX2ZlYXR1cmVzID0gZmVhdHVyZXNbc2NhbGVyLmZlYXR1cmVfbmFtZXNfaW5fXQogICAgICAgICAgICAgICAgc2NhbGVkX2ZlYXR1cmVzID0gc2NhbGVyLnRyYW5zZm9ybShzZWxlY3RlZF9mZWF0dXJlcykKICAgICAgICAgICAgICAgIGRmW3NlbGVjdGVkX2ZlYXR1cmVzLmNvbHVtbnNdID0gc2NhbGVkX2ZlYXR1cmVzCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICMg5Yib5bu65b2T5YmN54q25oCBCiAgICAgICAgICAgICAgICBjdXJyZW50X3N0YXRlID0gc2VsZi5fY3JlYXRlX2N1cnJlbnRfc3RhdGUoZGYuaWxvY1stMV0pCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICMg6aKE5rWL5Yqo5L2cCiAgICAgICAgICAgICAgICBhY3Rpb24sIF8gPSBtb2RlbC5wcmVkaWN0KGN1cnJlbnRfc3RhdGUpCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICMg5omn6KGM5Lqk5piTCiAgICAgICAgICAgICAgICBzZWxmLmV4ZWN1dGVfdHJhZGUoc3ltYm9sLCBhY3Rpb24sIGRmLmlsb2NbLTFdWydjbG9zZSddKQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAjIOabtOaWsOaKlei1hOe7hOWQiAogICAgICAgICAgICAgICAgc2VsZi51cGRhdGVfcG9ydGZvbGlvKHN5bWJvbCwgYWN0aW9uLCBkZi5pbG9jWy0xXVsnY2xvc2UnXSkKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIyDlrp7ml7bnm5HmjqcKICAgICAgICAgICAgICAgIHNlbGYucmVhbF90aW1lX21vbml0b3JpbmcoKQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAjIOavj+WkqeS6pOaYk+S4gOasoQogICAgICAgICAgICAgICAgbmV4dF9ydW4gPSBub3cgKyB0aW1lZGVsdGEoZGF5cz0xKQogICAgICAgICAgICAgICAgc2xlZXBfdGltZSA9IChuZXh0X3J1biAtIGRhdGV0aW1lLnV0Y25vdygpKS50b3RhbF9zZWNvbmRzKCkKICAgICAgICAgICAgICAgIHByaW50KGYmcXVvdDvkuIvkuIDmrKHkuqTmmJPlnKg6IHtuZXh0X3J1bn0gKOetieW+hXtzbGVlcF90aW1lLzM2MDA6LjFmfeWwj+aXtikmcXVvdDspCiAgICAgICAgICAgICAgICB0aW1lLnNsZWVwKG1heChzbGVlcF90aW1lLCA2MCkpICAjIOiHs+WwkeetieW+hTYw56eSCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIHRyYWRlX2NvdW50ICs9IDEKICAgICAgICAgICAgICAgIAogICAgICAgICAgICBleGNlcHQgS2V5Ym9hcmRJbnRlcnJ1cHQ6CiAgICAgICAgICAgICAgICBwcmludCgmcXVvdDvkuqTmmJPlt7LlgZzmraImcXVvdDspCiAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgICAgICBwcmludChmJnF1b3Q75Y+R55Sf6ZSZ6K+vOiB7c3RyKGUpfSZxdW90OykKICAgICAgICAgICAgICAgIHRpbWUuc2xlZXAoNjApCiAgICAKICAgIGRlZiBfY3JlYXRlX2N1cnJlbnRfc3RhdGUoc2VsZiwgZGF0YV9yb3cpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+WIm+W7uuW9k+WJjeeKtuaAgeWQkemHjyZxdW90OyZxdW90OyZxdW90OwogICAgICAgICMg6I635Y+W54m55b6B5YC8CiAgICAgICAgZmVhdHVyZXMgPSBkYXRhX3Jvd1tbJ3JzaScsICdtYWNkJywgJ2F0cicsICdiYl93aWR0aCcsICdtYWNkX2RpZmYnXV0udmFsdWVzCiAgICAgICAgc3RhdGUgPSBmZWF0dXJlcy5hc3R5cGUobnAuZmxvYXQzMikKICAgICAgICAKICAgICAgICAjIOa3u+WKoOaMgeS7k+S/oeaBrwogICAgICAgIHN0YXRlID0gbnAuYXBwZW5kKHN0YXRlLCBbCiAgICAgICAgICAgIHNlbGYuYmFsYW5jZSAvIHNlbGYuaW5pdGlhbF9iYWxhbmNlLAogICAgICAgICAgICBzZWxmLmNyeXB0b19oZWxkICogZGF0YV9yb3dbJ2Nsb3NlJ10gLyBzZWxmLmluaXRpYWxfYmFsYW5jZSwKICAgICAgICAgICAgc2VsZi5jcnlwdG9faGVsZAogICAgICAgIF0pCiAgICAgICAgCiAgICAgICAgcmV0dXJuIHN0YXRlLnJlc2hhcGUoMSwgLTEpCiAgICAKICAgIGRlZiBleGVjdXRlX3RyYWRlKHNlbGYsIHN5bWJvbCwgYWN0aW9uLCBwcmljZSk6CiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q75omn6KGM5Lqk5piTJnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgYWN0aW9uX3R5cGUgPSAmcXVvdDvkubDlhaUmcXVvdDsgaWYgYWN0aW9uWzBdICZndDsgMC4yIGVsc2UgJnF1b3Q75Y2W5Ye6JnF1b3Q7IGlmIGFjdGlvblswXSAmbHQ7IC0wLjIgZWxzZSAmcXVvdDvmjIHmnIkmcXVvdDsKICAgICAgICBzaXplID0gYWN0aW9uWzFdCiAgICAgICAgCiAgICAgICAgcHJpbnQoZiZxdW90O1xu5omn6KGM5Lqk5piTOiB7YWN0aW9uX3R5cGV9IHtzeW1ib2x9IHtzaXplKjEwMDouMWZ9JSDku5PkvY0gQCAke3ByaWNlOi42Zn0mcXVvdDspCiAgICAgICAgCiAgICAgICAgIyDmqKHmi5/kuqTmmJPpgLvovpEKICAgICAgICBpZiBhY3Rpb25fdHlwZSA9PSAmcXVvdDvkubDlhaUmcXVvdDs6CiAgICAgICAgICAgICMg6K6h566X6LSt5Lmw6YePCiAgICAgICAgICAgIGFtb3VudCA9IChzZWxmLmJhbGFuY2UgKiBzaXplKSAvIHByaWNlCiAgICAgICAgICAgIHNlbGYuY3J5cHRvX2hlbGQgKz0gYW1vdW50CiAgICAgICAgICAgIHNlbGYuYmFsYW5jZSAtPSBhbW91bnQgKiBwcmljZQogICAgICAgICAgICBwcmludChmJnF1b3Q75qih5ouf5Lmw5YWlOiB7YW1vdW50Oi4yZn0ge3N5bWJvbH0gQCAke3ByaWNlOi42Zn0mcXVvdDspCiAgICAgICAgICAgICAgICAKICAgICAgICBlbGlmIGFjdGlvbl90eXBlID09ICZxdW90O+WNluWHuiZxdW90OzoKICAgICAgICAgICAgIyDorqHnrpflh7rllK7ph48KICAgICAgICAgICAgYW1vdW50ID0gc2VsZi5jcnlwdG9faGVsZCAqIHNpemUKICAgICAgICAgICAgc2VsZi5jcnlwdG9faGVsZCAtPSBhbW91bnQKICAgICAgICAgICAgc2VsZi5iYWxhbmNlICs9IGFtb3VudCAqIHByaWNlCiAgICAgICAgICAgIHByaW50KGYmcXVvdDvmqKHmi5/ljZblh7o6IHthbW91bnQ6LjJmfSB7c3ltYm9sfSBAICR7cHJpY2U6LjZmfSZxdW90OykKICAgIAogICAgZGVmIHVwZGF0ZV9wb3J0Zm9saW8oc2VsZiwgc3ltYm9sLCBhY3Rpb24sIHByaWNlKToKICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDvmm7TmlrDmipXotYTnu4TlkIjku7flgLwmcXVvdDsmcXVvdDsmcXVvdDsKICAgICAgICAjIOabtOaWsOW9k+WJjeS7t+WAvAogICAgICAgIHNlbGYucG9ydGZvbGlvWydjdXJyZW50X3ZhbHVlJ10gPSBzZWxmLmJhbGFuY2UgKyBzZWxmLmNyeXB0b19oZWxkICogcHJpY2UKICAgICAgICAKICAgICAgICAjIOiuoeeul+aXpeaUtuebiueOhwogICAgICAgIGlmIGxlbihzZWxmLnBvcnRmb2xpb1sndmFsdWVzJ10pICZndDsgMDoKICAgICAgICAgICAgbGFzdF92YWx1ZSA9IHNlbGYucG9ydGZvbGlvWyd2YWx1ZXMnXVstMV0KICAgICAgICAgICAgZGFpbHlfcmV0dXJuID0gKHNlbGYucG9ydGZvbGlvWydjdXJyZW50X3ZhbHVlJ10gLSBsYXN0X3ZhbHVlKSAvIGxhc3RfdmFsdWUKICAgICAgICAgICAgc2VsZi5wb3J0Zm9saW9bJ2RhaWx5X3JldHVybiddID0gZGFpbHlfcmV0dXJuCiAgICAgICAgICAgIHNlbGYucG9ydGZvbGlvWydyZXR1cm5zJ10uYXBwZW5kKGRhaWx5X3JldHVybikKICAgICAgICAKICAgICAgICAjIOa3u+WKoOW9k+WJjeS7t+WAvAogICAgICAgIHNlbGYucG9ydGZvbGlvWyd2YWx1ZXMnXS5hcHBlbmQoc2VsZi5wb3J0Zm9saW9bJ2N1cnJlbnRfdmFsdWUnXSkKICAgICAgICAKICAgICAgICBwcmludChmJnF1b3Q75oqV6LWE57uE5ZCI5pu05pawOiAke3NlbGYucG9ydGZvbGlvWydjdXJyZW50X3ZhbHVlJ106LC4yZn0gfCAmcXVvdDsKICAgICAgICAgICAgICBmJnF1b3Q75pel5pS255uK546HOiB7c2VsZi5wb3J0Zm9saW9bJ2RhaWx5X3JldHVybiddKjEwMDouMmZ9JSZxdW90OykKICAgIAogICAgZGVmIHJlYWxfdGltZV9tb25pdG9yaW5nKHNlbGYpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+WunuaXtuebkeaOp+S7quihqOebmCZxdW90OyZxdW90OyZxdW90OwogICAgICAgIHByaW50KGYmcXVvdDtcbj09PT09IOWunuaXtuebkeaOp+S7quihqOebmCA9PT09PSZxdW90OykKICAgICAgICBwcmludChmJnF1b3Q75b2T5YmN6LWE5LqnOiAke3NlbGYucG9ydGZvbGlvWydjdXJyZW50X3ZhbHVlJ106LC4yZn0mcXVvdDspCiAgICAgICAgcHJpbnQoZiZxdW90O+aXpeaUtuebiueOhzoge3NlbGYucG9ydGZvbGlvWydkYWlseV9yZXR1cm4nXSoxMDA6LjJmfSUmcXVvdDspCiAgICAgICAgCiAgICAgICAgIyDorqHnrpfmnIDlpKflm57mkqQKICAgICAgICBpZiBsZW4oc2VsZi5wb3J0Zm9saW9bJ3ZhbHVlcyddKSAmZ3Q7IDEwOgogICAgICAgICAgICBtYXhfZHJhd2Rvd24gPSBzZWxmLmNhbGN1bGF0ZV9tYXhfZHJhd2Rvd24oc2VsZi5wb3J0Zm9saW9bJ3ZhbHVlcyddKQogICAgICAgICAgICBwcmludChmJnF1b3Q75pyA5aSn5Zue5pKkOiB7bWF4X2RyYXdkb3duKjEwMDouMmZ9JSZxdW90OykKICAgICAgICAKICAgICAgICBwcmludCgmcXVvdDs9JnF1b3Q7ICogMzApCiAgICAKICAgIGRlZiBydW5fZnVsbF9zeXN0ZW0oc2VsZiwgYmFja3Rlc3Rfb25seT1UcnVlKToKICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDvov5DooYzlrozmlbTkuqTmmJPns7vnu58mcXVvdDsmcXVvdDsmcXVvdDsKICAgICAgICBwcmludCgmcXVvdDs9JnF1b3Q7KjYwKQogICAgICAgIHByaW50KCZxdW90O+WKoOWvhui0p+W4gea3seW6puW8uuWMluWtpuS5oOS6pOaYk+ezu+e7nyZxdW90OykKICAgICAgICBwcmludChmJnF1b3Q75Lqk5piT5a+5OiB7JywgJy5qb2luKHNlbGYuc3ltYm9scyl9JnF1b3Q7KQogICAgICAgIHByaW50KGYmcXVvdDvliJ3lp4votYTph5E6ICR7c2VsZi5pbml0aWFsX2JhbGFuY2U6LC4yZn0mcXVvdDspCiAgICAgICAgcHJpbnQoJnF1b3Q7PSZxdW90Oyo2MCkKICAgICAgICAKICAgICAgICAjIOWHhuWkh+aVsOaNrgogICAgICAgIHByaW50KCZxdW90O1xuWzEvM10g5YeG5aSH5pWw5o2uLi4uJnF1b3Q7KQogICAgICAgIGRhdGEgPSBzZWxmLnByZXBhcmVfZGF0YXNldCgpCiAgICAgICAgCiAgICAgICAgIyDorq3nu4PmqKHlnosKICAgICAgICBwcmludCgmcXVvdDtcblsyLzNdIOiuree7g+W8uuWMluWtpuS5oOaooeWeiy4uLiZxdW90OykKICAgICAgICBmb3Igc3ltYm9sIGluIHNlbGYuc3ltYm9sczoKICAgICAgICAgICAgc2VsZi50cmFpbl9kcmxfYWdlbnQoZGF0YSwgc3ltYm9sLCB0aW1lc3RlcHM9MTUwMDApCiAgICAgICAgCiAgICAgICAgIyDlm57mtYsKICAgICAgICBwcmludCgmcXVvdDtcblszLzNdIOi/kOihjOWbnua1iy4uLiZxdW90OykKICAgICAgICBmb3Igc3ltYm9sIGluIHNlbGYuc3ltYm9sczoKICAgICAgICAgICAgc2VsZi5iYWNrdGVzdChkYXRhLCBzeW1ib2wpCiAgICAgICAgCiAgICAgICAgaWYgbm90IGJhY2t0ZXN0X29ubHk6CiAgICAgICAgICAgIHByaW50KCZxdW90O1xu57O757uf5YeG5aSH5bCx57uqISDlkK/liqjlrp7nm5jkuqTmmJPjgIImcXVvdDspCiAgICAgICAgICAgIGZvciBzeW1ib2wgaW4gc2VsZi5zeW1ib2xzOgogICAgICAgICAgICAgICAgc2VsZi5saXZlX3RyYWRpbmcoc3ltYm9sKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHByaW50KCZxdW90O1xu5Zue5rWL5a6M5oiQISDmn6XnnIvlm77ooajkuobop6PnrZbnlaXooajnjrDjgIImcXVvdDspCgojIOS4u+WHveaVsAppZiBfX25hbWVfXyA9PSAmcXVvdDtfX21haW5fXyZxdW90OzoKICAgICMg5Yib5bu65Lqk5piT55uu5b2VCiAgICBvcy5tYWtlZGlycygmcXVvdDttb2RlbHMmcXVvdDssIGV4aXN0X29rPVRydWUpCiAgICBvcy5tYWtlZGlycygmcXVvdDt0ZW5zb3Jib2FyZCZxdW90OywgZXhpc3Rfb2s9VHJ1ZSkKICAgIAogICAgcHJpbnQoJnF1b3Q7PSZxdW90Oyo2MCkKICAgIHByaW50KCZxdW90O1BFUEUvU09M5biB5rex5bqm5by65YyW5a2m5Lmg5Lqk5piT57O757ufJnF1b3Q7KQogICAgcHJpbnQoJnF1b3Q7PSZxdW90Oyo2MCkKICAgIHByaW50KCZxdW90OzEuIOi/kOihjOWbnua1iyZxdW90OykKICAgIHByaW50KCZxdW90OzIuIOWQr+WKqOWunuebmOS6pOaYkyZxdW90OykKICAgIAogICAgY2hvaWNlID0gaW5wdXQoJnF1b3Q76K+36YCJ5oup5pON5L2cICgxLzIpOiAmcXVvdDspCiAgICAKICAgICMg5Yid5aeL5YyW5Lqk5piT57O757ufCiAgICB0cmFkaW5nX3N5c3RlbSA9IENyeXB0b0RSTFRyYWRpbmdTeXN0ZW0oCiAgICAgICAgc3ltYm9scz1bJ1BFUEUvVVNEVCcsICdTT0wvVVNEVCddLAogICAgICAgIGluaXRpYWxfYmFsYW5jZT0xMDAwMAogICAgKQogICAgCiAgICBpZiBjaG9pY2UgPT0gJnF1b3Q7MSZxdW90OzoKICAgICAgICAjIOi/kOihjOWbnua1iwogICAgICAgIHRyYWRpbmdfc3lzdGVtLnJ1bl9mdWxsX3N5c3RlbShiYWNrdGVzdF9vbmx5PVRydWUpCiAgICBlbGlmIGNob2ljZSA9PSAmcXVvdDsyJnF1b3Q7OgogICAgICAgICMg6L+Q6KGM5a6e55uY5Lqk5piTCiAgICAgICAgcHJpbnQoJnF1b3Q76K2m5ZGKOiDlrp7nm5jkuqTmmJPmtonlj4rnnJ/lrp7otYTph5Hpo47pmakhJnF1b3Q7KQogICAgICAgIGNvbmZpcm0gPSBpbnB1dCgmcXVvdDvnoa7lrpropoHlkK/liqjlrp7nm5jkuqTmmJPlkJc/ICh5L24pOiAmcXVvdDspCiAgICAgICAgaWYgY29uZmlybS5sb3dlcigpID09ICd5JzoKICAgICAgICAgICAgdHJhZGluZ19zeXN0ZW0ucnVuX2Z1bGxfc3lzdGVtKGJhY2t0ZXN0X29ubHk9RmFsc2UpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHJpbnQoJnF1b3Q75Y+W5raI5a6e55uY5Lqk5piTJnF1b3Q7KQogICAgZWxzZToKICAgICAgICBwcmludCgmcXVvdDvml6DmlYjpgInmi6kmcXVvdDsp
import numpy as np
import pandas as pd
import pandas_ta as ta
import ccxt
import gym
from gym import spaces
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import DummyVecEnv
from stable_baselines3.common.callbacks import BaseCallback
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.feature_selection import SelectKBest, f_regression
from sklearn.ensemble import RandomForestRegressor
import matplotlib.pyplot as plt
import quantstats as qs
import time
import os
import warnings
import requests
import json
from datetime import datetime, timedelta
import random
# 禁用警告
warnings.filterwarnings('ignore')
# ====================== API 配置 ======================
API_KEY = "4230d936a253e85e349c9ae221e417ee"
SECRET_KEY = "24f38a1ed7d760a43aaa06ee49d607563f4c2882d58be726fbf13a3c773bbec0"
# 简化版链上数据API配置
ONCHAIN_API = {
"PEPE": "https://a...content-available-to-author-only...n.io/api",
"SOL": "https://a...content-available-to-author-only...n.io"
}
# ====================== 简化版因子工程 ======================
def calculate_features(df, symbol):
"""计算技术指标和链上因子"""
# 基础价格特征
df['returns'] = df['close'].pct_change()
# 动量指标
df['rsi'] = ta.rsi(df['close'], length=14)
df['macd'] = ta.macd(df['close'], fast=12, slow=26)['MACD_12_26_9']
df['macd_signal'] = ta.macd(df['close'], fast=12, slow=26)['MACDs_12_26_9']
# 波动率指标
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
df['bb_upper'] = ta.bbands(df['close'], length=20)['BBU_20_2.0']
df['bb_lower'] = ta.bbands(df['close'], length=20)['BBL_20_2.0']
# 成交量指标
df['obv'] = ta.obv(df['close'], df['volume'])
# 特征工程
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['close']
df['macd_diff'] = df['macd'] - df['macd_signal']
df['volume_change'] = df['volume'].pct_change()
# 目标变量
df['target'] = df['close'].shift(-1) / df['close'] - 1
# 添加链上数据 (简化版)
if "PEPE" in symbol:
df['pepe_burn'] = np.random.poisson(10000000, len(df))
df['pepe_large_tx'] = np.random.poisson(5, len(df))
elif "SOL" in symbol:
df['sol_tps'] = np.random.normal(3000, 500, len(df))
df['sol_staked'] = np.random.uniform(70, 80, len(df))
return df.dropna()
# ====================== 特征选择 ======================
def select_features(features, target, k=8):
"""选择最重要的k个特征"""
selector = SelectKBest(score_func=f_regression, k=k)
selector.fit(features, target)
selected_features = features.columns[selector.get_support()]
print("Selected Features:")
print(selected_features.tolist())
return features[selected_features]
# ====================== 强化学习交易环境 ======================
class CryptoMarketEnv(gym.Env):
"""加密货币交易强化学习环境"""
metadata = {'render.modes': ['human']}
def __init__(self, df, initial_balance=10000, transaction_cost=0.001, max_drawdown=0.2):
super(CryptoMarketEnv, self).__init__()
self.df = df
self.current_step = 0
self.initial_balance = initial_balance
self.transaction_cost = transaction_cost
self.max_drawdown = max_drawdown
# 状态空间: 技术指标 + 持仓信息
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf,
shape=(8,) # 简化状态空间
)
# 动作空间: [买入, 卖出, 持有] + 仓位比例
self.action_space = spaces.Box(low=np.array([-1, 0]), high=np.array([1, 1]), dtype=np.float32)
self.reset()
def reset(self):
self.balance = self.initial_balance
self.crypto_held = 0
self.current_value = self.initial_balance
self.previous_value = self.initial_balance
self.peak_value = self.initial_balance
self.current_step = 0
self.trades = []
self.portfolio_history = [self.initial_balance]
self.drawdown = 0.0
self.trade_count = 0
return self._next_observation()
def _next_observation(self):
# 获取当前状态的技术指标
features = self.df.iloc[self.current_step][['rsi', 'macd', 'atr', 'bb_width', 'macd_diff']].values
state = features.astype(np.float32)
# 添加持仓信息
state = np.append(state, [
self.balance / self.initial_balance,
self.crypto_held * self._get_current_price() / self.initial_balance,
self.crypto_held
])
return state
def _get_current_price(self):
return self.df.iloc[self.current_step]['close']
def step(self, action):
# 执行交易动作
current_price = self._get_current_price()
action_type = action[0] # [-1, 1] 买入/卖出信号
position_size = action[1] # [0, 1] 仓位比例
# 计算交易量
if action_type > 0.2: # 买入
trade_amount = min(
self.balance * position_size / current_price,
self.balance / current_price
)
cost = trade_amount * current_price * self.transaction_cost
self.balance -= (trade_amount * current_price + cost)
self.crypto_held += trade_amount
self.trade_count += 1
elif action_type < -0.2: # 卖出
trade_amount = min(self.crypto_held, self.crypto_held * position_size)
cost = trade_amount * current_price * self.transaction_cost
self.balance += (trade_amount * current_price - cost)
self.crypto_held -= trade_amount
self.trade_count += 1
# 更新到下一步
self.current_step += 1
if self.current_step >= len(self.df) - 1:
done = True
else:
done = False
# 计算新价值
self.previous_value = self.current_value
current_price = self._get_current_price()
self.current_value = self.balance + self.crypto_held * current_price
# 更新峰值和回撤
if self.current_value > self.peak_value:
self.peak_value = self.current_value
self.drawdown = (self.peak_value - self.current_value) / self.peak_value
# 熔断机制
if self.drawdown > self.max_drawdown:
done = True
print(f"熔断触发! 回撤超过{self.max_drawdown*100:.0f}%")
self.portfolio_history.append(self.current_value)
# 计算奖励
reward = self.current_value - self.previous_value
# 获取新状态
next_state = self._next_observation()
return next_state, reward, done, {}
def render(self, mode='human'):
profit_pct = (self.current_value - self.initial_balance) / self.initial_balance * 100
print(f"Step: {self.current_step}/{len(self.df)} | "
f"Value: ${self.current_value:,.2f} | "
f"Profit: {profit_pct:.2f}% | "
f"Drawdown: {self.drawdown*100:.2f}% | "
f"Trades: {self.trade_count}")
# ====================== 主交易系统 ======================
class CryptoDRLTradingSystem:
def __init__(self, symbols=['PEPE/USDT', 'SOL/USDT'], initial_balance=10000):
self.symbols = symbols
self.initial_balance = initial_balance
self.exchange = self._setup_exchange()
self.models = {}
self.scalers = {}
self.portfolio = {
'values': [initial_balance],
'returns': [],
'current_value': initial_balance,
'daily_return': 0.0
}
self.balance = initial_balance
self.crypto_held = 0
def _setup_exchange(self):
"""设置交易所连接"""
exchange = ccxt.binance({
'apiKey': API_KEY,
'secret': SECRET_KEY,
'rateLimit': 100,
'enableRateLimit': True,
'options': {
'defaultType': 'spot', # 使用现货交易
}
})
# 检查连接
try:
markets = exchange.load_markets()
print("交易所连接成功! 可用交易对数量:", len(markets))
except Exception as e:
print(f"交易所连接失败: {str(e)}")
print("使用模拟模式运行...")
return exchange
def fetch_historical_data(self, symbol, timeframe='1d', limit=100):
"""获取历史数据"""
print(f"获取历史数据: {symbol}...")
try:
# 尝试从交易所获取数据
ohlcv = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
return df
except Exception as e:
print(f"获取数据失败: {str(e)}, 使用模拟数据")
# 生成模拟数据
dates = pd.date_range(end=pd.Timestamp.now(), periods=limit, freq='D')
prices = np.cumprod(1 + np.random.normal(0.005, 0.03, limit))
volumes = np.random.randint(10000, 50000, limit)
df = pd.DataFrame({
'timestamp': dates,
'open': prices * 0.99,
'high': prices * 1.01,
'low': prices * 0.98,
'close': prices,
'volume': volumes
})
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)
return df
def prepare_dataset(self):
"""准备数据集"""
all_data = {}
for symbol in self.symbols:
# 获取历史数据
df = self.fetch_historical_data(symbol, limit=200)
# 计算特征
df = calculate_features(df, symbol)
# 特征选择
features = df.drop(columns=['open', 'high', 'low', 'close', 'volume', 'target'])
target = df['target']
# 选择最重要的特征
selected_features = select_features(features, target, k=5)
# 归一化
scaler = StandardScaler()
scaled_features = scaler.fit_transform(selected_features)
df[selected_features.columns] = scaled_features
# 存储数据
all_data[symbol] = {
'features': selected_features,
'target': target,
'original': df,
'scaler': scaler
}
self.scalers[symbol] = scaler
return all_data
def train_drl_agent(self, data, symbol, timesteps=20000):
"""训练深度强化学习代理"""
print(f"训练DRL代理: {symbol}...")
# 创建环境
env = CryptoMarketEnv(data[symbol]['original'], self.initial_balance)
env = DummyVecEnv([lambda: env])
# 初始化PPO模型
policy_kwargs = dict(net_arch=[64, 64])
model = PPO(
"MlpPolicy",
env,
verbose=1,
tensorboard_log=f"./tensorboard/{symbol}/",
policy_kwargs=policy_kwargs,
learning_rate=3e-4,
gamma=0.99,
ent_coef=0.01
)
# 训练模型
model.learn(total_timesteps=timesteps)
# 保存模型
os.makedirs("models", exist_ok=True)
model.save(f"models/drl_model_{symbol.replace('/', '_')}")
self.models[symbol] = model
return model
def backtest(self, data, symbol):
"""回测交易策略"""
if symbol not in self.models:
print(f"{symbol}未找到模型,正在训练...")
self.train_drl_agent(data, symbol, timesteps=10000)
# 创建环境
env = CryptoMarketEnv(data[symbol]['original'], self.initial_balance)
model = self.models[symbol]
# 运行回测
obs = env.reset()
done = False
step_count = 0
while not done:
action, _ = model.predict(obs)
obs, _, done, _ = env.step(action)
if done or step_count % 20 == 0:
env.render()
step_count += 1
# 分析结果
portfolio_history = env.portfolio_history
returns = pd.Series(portfolio_history).pct_change().dropna()
# 更新投资组合记录
self.portfolio['values'] = portfolio_history
self.portfolio['returns'] = returns.tolist()
self.portfolio['current_value'] = portfolio_history[-1]
self.portfolio['daily_return'] = returns.iloc[-1] if len(returns) > 0 else 0
# 性能报告
print("\n" + "="*60)
print(f"{symbol}回测结果")
print("="*60)
print(f"初始资金: ${self.initial_balance:,.2f}")
print(f"最终资产: ${portfolio_history[-1]:,.2f}")
print(f"总收益: {(portfolio_history[-1] - self.initial_balance) / self.initial_balance * 100:.2f}%")
print(f"最大回撤: {self.calculate_max_drawdown(portfolio_history) * 100:.2f}%")
print(f"交易次数: {env.trade_count}")
# 绘制结果
self.plot_results(symbol, data[symbol]['original'], portfolio_history)
return returns
def calculate_max_drawdown(self, values):
"""计算最大回撤"""
peak = values[0]
max_drawdown = 0
for value in values:
if value > peak:
peak = value
drawdown = (peak - value) / peak
if drawdown > max_drawdown:
max_drawdown = drawdown
return max_drawdown
def plot_results(self, symbol, price_data, portfolio_history):
"""绘制回测结果"""
plt.figure(figsize=(14, 10))
# 价格图表
plt.subplot(2, 1, 1)
plt.plot(price_data.index, price_data['close'], label='价格', color='blue')
plt.title(f"{symbol} 价格走势", fontsize=14)
plt.ylabel('价格 (USDT)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
# 资产图表
plt.subplot(2, 1, 2)
plt.plot(price_data.index, portfolio_history[:len(price_data)], label='资产总值', color='green')
plt.title("资产变化", fontsize=14)
plt.xlabel('日期', fontsize=12)
plt.ylabel('资产 (USDT)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.tight_layout()
plt.savefig(f'backtest_{symbol.replace("/", "_")}.png')
plt.show()
def live_trading(self, symbol):
"""实盘交易"""
if symbol not in self.models:
print(f"{symbol}未找到模型,正在训练...")
data = self.prepare_dataset()
self.train_drl_agent(data, symbol)
model = self.models[symbol]
scaler = self.scalers[symbol]
print(f"启动{symbol}实盘交易...")
trade_count = 0
while True:
try:
# 获取最新数据
now = datetime.utcnow()
start_time = now - timedelta(days=30)
ohlcv = self.exchange.fetch_ohlcv(symbol, '1d', since=int(start_time.timestamp() * 1000))
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
# 计算特征
df = calculate_features(df, symbol)
# 选择特征并归一化
features = df.drop(columns=['open', 'high', 'low', 'close', 'volume', 'target'])
selected_features = features[scaler.feature_names_in_]
scaled_features = scaler.transform(selected_features)
df[selected_features.columns] = scaled_features
# 创建当前状态
current_state = self._create_current_state(df.iloc[-1])
# 预测动作
action, _ = model.predict(current_state)
# 执行交易
self.execute_trade(symbol, action, df.iloc[-1]['close'])
# 更新投资组合
self.update_portfolio(symbol, action, df.iloc[-1]['close'])
# 实时监控
self.real_time_monitoring()
# 每天交易一次
next_run = now + timedelta(days=1)
sleep_time = (next_run - datetime.utcnow()).total_seconds()
print(f"下一次交易在: {next_run} (等待{sleep_time/3600:.1f}小时)")
time.sleep(max(sleep_time, 60)) # 至少等待60秒
trade_count += 1
except KeyboardInterrupt:
print("交易已停止")
break
except Exception as e:
print(f"发生错误: {str(e)}")
time.sleep(60)
def _create_current_state(self, data_row):
"""创建当前状态向量"""
# 获取特征值
features = data_row[['rsi', 'macd', 'atr', 'bb_width', 'macd_diff']].values
state = features.astype(np.float32)
# 添加持仓信息
state = np.append(state, [
self.balance / self.initial_balance,
self.crypto_held * data_row['close'] / self.initial_balance,
self.crypto_held
])
return state.reshape(1, -1)
def execute_trade(self, symbol, action, price):
"""执行交易"""
action_type = "买入" if action[0] > 0.2 else "卖出" if action[0] < -0.2 else "持有"
size = action[1]
print(f"\n执行交易: {action_type} {symbol} {size*100:.1f}% 仓位 @ ${price:.6f}")
# 模拟交易逻辑
if action_type == "买入":
# 计算购买量
amount = (self.balance * size) / price
self.crypto_held += amount
self.balance -= amount * price
print(f"模拟买入: {amount:.2f} {symbol} @ ${price:.6f}")
elif action_type == "卖出":
# 计算出售量
amount = self.crypto_held * size
self.crypto_held -= amount
self.balance += amount * price
print(f"模拟卖出: {amount:.2f} {symbol} @ ${price:.6f}")
def update_portfolio(self, symbol, action, price):
"""更新投资组合价值"""
# 更新当前价值
self.portfolio['current_value'] = self.balance + self.crypto_held * price
# 计算日收益率
if len(self.portfolio['values']) > 0:
last_value = self.portfolio['values'][-1]
daily_return = (self.portfolio['current_value'] - last_value) / last_value
self.portfolio['daily_return'] = daily_return
self.portfolio['returns'].append(daily_return)
# 添加当前价值
self.portfolio['values'].append(self.portfolio['current_value'])
print(f"投资组合更新: ${self.portfolio['current_value']:,.2f} | "
f"日收益率: {self.portfolio['daily_return']*100:.2f}%")
def real_time_monitoring(self):
"""实时监控仪表盘"""
print(f"\n===== 实时监控仪表盘 =====")
print(f"当前资产: ${self.portfolio['current_value']:,.2f}")
print(f"日收益率: {self.portfolio['daily_return']*100:.2f}%")
# 计算最大回撤
if len(self.portfolio['values']) > 10:
max_drawdown = self.calculate_max_drawdown(self.portfolio['values'])
print(f"最大回撤: {max_drawdown*100:.2f}%")
print("=" * 30)
def run_full_system(self, backtest_only=True):
"""运行完整交易系统"""
print("="*60)
print("加密货币深度强化学习交易系统")
print(f"交易对: {', '.join(self.symbols)}")
print(f"初始资金: ${self.initial_balance:,.2f}")
print("="*60)
# 准备数据
print("\n[1/3] 准备数据...")
data = self.prepare_dataset()
# 训练模型
print("\n[2/3] 训练强化学习模型...")
for symbol in self.symbols:
self.train_drl_agent(data, symbol, timesteps=15000)
# 回测
print("\n[3/3] 运行回测...")
for symbol in self.symbols:
self.backtest(data, symbol)
if not backtest_only:
print("\n系统准备就绪! 启动实盘交易。")
for symbol in self.symbols:
self.live_trading(symbol)
else:
print("\n回测完成! 查看图表了解策略表现。")
# 主函数
if __name__ == "__main__":
# 创建交易目录
os.makedirs("models", exist_ok=True)
os.makedirs("tensorboard", exist_ok=True)
print("="*60)
print("PEPE/SOL币深度强化学习交易系统")
print("="*60)
print("1. 运行回测")
print("2. 启动实盘交易")
choice = input("请选择操作 (1/2): ")
# 初始化交易系统
trading_system = CryptoDRLTradingSystem(
symbols=['PEPE/USDT', 'SOL/USDT'],
initial_balance=10000
)
if choice == "1":
# 运行回测
trading_system.run_full_system(backtest_only=True)
elif choice == "2":
# 运行实盘交易
print("警告: 实盘交易涉及真实资金风险!")
confirm = input("确定要启动实盘交易吗? (y/n): ")
if confirm.lower() == 'y':
trading_system.run_full_system(backtest_only=False)
else:
print("取消实盘交易")
else:
print("无效选择")