diff --git a/README.md b/README.md index cddbea4..eb6c6b9 100755 --- a/README.md +++ b/README.md @@ -144,9 +144,9 @@ timeframe = '1y' df = md.get_ohlc(symbol=symbol, timeframe=timeframe) -holding = hist.buy_and_hold(df[C.CLOSE]) +holding = hist.from_holding(df[C.CLOSE]) signals = hist.get_optimal_signals(df[C.CLOSE]) -my_strat = hist.create_portfolio(df[C.CLOSE], signals) +my_strat = hist.from_signals(df[C.CLOSE], signals) metrics = [ 'Total Return [%]', 'Benchmark Return [%]', diff --git a/encrypted/optimize_portfolio.py.encrypted b/encrypted/optimize_portfolio.py.encrypted index 14d7906..730a133 100644 --- a/encrypted/optimize_portfolio.py.encrypted +++ b/encrypted/optimize_portfolio.py.encrypted @@ -1 +1 @@ -gAAAAABnir3tdXUXt8Dua-T_yCKU2RnCZLha9auJewTmsa9oI4LsER1lSUms9_ZMDsA9jjbugs1zYKGG9q2TPmYdZypWB9n7NWvzvnv1XKV6aeM7t3SJyQwO0SOrzZdzDt4XvpA3GNQ1P4Ai8aJ7wiucY-FMs4oSAvA6YokS7buvDFWvsiNggXEDt2Az0nNLolc6YUxXyrQbF_iq2RthWu-WpluvLUkoc-qLtBlvW0O4m_Sglgz-Yc2ntbgUYgTod-odp18crY3uw6HYsKVaXkunJlc1D2axq9aVc_Y0Vf8ibrL4n2e01LUgLnKXLTq-OFRdBJB7K_G8nm9CV4jyXMDCUHufYwgfgE610ITBcP1JBg8fn8mckT1RKWoyLtIIkZrg1ynMEJAe4tk92iF9jnYpDtoDGAVDoEAALpzywPpOJ2Vywpy_MXq4loBlyOy1EGUPzKvODp_qSl9YV92gr1_uc_3Kh5TtR4hmSHryOuakrdXV6FqRDAXiYRqld1eQieM2GFbDvFsxeOYJwRjWtGdauaukgPHIFsdA0azxBV3V3F3EcSNdacwvowzmj86D0Yw_oj-Tr42i-TFXOil6dbY5nOGYDUaJ823ceWJKLK3HF5Tpz16nGfdWU9zgjf5iAGJFUzrHfLusDrYPWqkLIX-hAHMrWHGJP7V346jtMFbYCk73YN1lv_z4Pfh0ocGL1qHdJeqn8L804VdiznWWKvZHmMIM-7bVLTeOmRCROLe0_oKckb9Bj0PbUXSRiSFl4YLGI16bjLtKneoq_1ENrpz7kn7TaC0gI1cRdTNUmySs-3_PlHqaF7GbnxhSRX7mEx20Csk9Sh5B47LNi_nCQZ2JGkrBruOvyZXCZlxxXQruFuMw6C4Y1X2tjq1OuAyoaf_Pd9oKiyQDV5ctM7OS_i3WNlbhwapI-aAkE0UibusCP33qNKCtDFn2qYSfBlETuN6dhOLK-l-xjLq10vpuLTDWOA_WLSoezobwkk3NmBjp_jaLZYzBkCZTUkq8If3bQQOyPKEJ6TGK3lTq2IWH0-9KGF7Gqj4V6lgZb0nGdzzBB1Vd7uXQDhkwv93JMSGS7k8oT8Tmcaigp2YyncNAjNmOPaBTjG04KiySaUNsvz5efMUYkyxdRNOMyu9OmED13GKQwt15LcEyLZhuzAvR6KAPYgiZRASO8bgEbU258i8rSFUf39ihG-2FSddxWgwd9x69lHUNXeEP4Y-TkRNxuxdhHPtJvJT5msu0wRVawaxE8Iy7-D-TTktEz7lHrUhdePicitJDFzVArB_SJ_Vi4hmZQTZIW5qGsf53C4m9eG2I9lLChTpcg7mp5iclY5o6O7W65qG8OHtHbpX8VZPLhxarhPCUrAT66NYmd2OLXd_Dsm-INdIvwpinPGrpl0sxYule0en_shBPsNRQ4d3RGtMPiMbINM5HhymqPzi-hRkKBpm54-g0p-20ZAM2aOnVgeG8_0nG_9Fyv0yjUqZf8yaMiIaROk2OfqEzoDt4J1Zs5SOg5E1Fv-98EX2QtcvuEGBXxpduB4Ii0sYdkFpASPmea1Dgt_nj3ij2c0rLXjJ4j2nojDnOBoyWrQ519nx9VW889GNCIQCrMZkKpzXpfBMxFeK0p3VMPvVsi75sCfV8xbPdwD4hu4uL0HK5fUPijwAyhESlaDdKqtFuDmZ3oHkEG1t2zefr_QqvC9bu0aMNE1MWzQ3yJlo0SRMk9n6Cxbr8DZCK4qKVvT32WoY1p3fL7WBjYU4lO_bKlKylz3na5IE1UCB_-L96mYXOc9gwxq7q6A9oCYH8nUibncFjWMJyikniSSjXoYSLxqcCuXfKfMuyhIzyl3LV080TTRHxCxi_KEPrLG1772yPM2e9NWc2iwaCjDb4U6m327yu2S-OyZqVvw7pvbzGyL6iu9QgDjX1Jwmvk_8HGL8b6Z7VCChBrmFwGFRu1uoecmYDOdbvguXvRU7aG15H9k543YfZuSHDmSp0OSVQucHq_PViD7l-ynXHGmK_XQeH8pD7VWMHGQmESFFsPpvlE0PfoX0MJZh0_UfhMgnOk0sEDPu-Rv5LtEKFK8ktph8MMAlFae4vbTcP7crhbsK1vQvoJwQfswgRKNWK1XTJ6UX_kKyastawawDnWEEdrpWIqE__-Ayust9GZFN0ujvW13IHRsFuKXyh-ObQYm2RqIqTDjOvsddN60wmFo0WkcHqXv_rdJiHf0OO9ECxOGfiCEmGuJJB55JfTanVQB6DLfbf-l__qHQAyhZEfTKRQlr_H-6wbCC0Sa2d2jM8LD-LzaiOEiJPsqgvhTj3WIDN3xgU3BUm9PggOjVi4lEtCJ--eJ56XU3tes_P421-Z2yG8wufhyvxXWeLQkBWDH-cl5ke4Komt_DqiE4PZDXE8wMWeO5nxtUo-5ib12Ut_1b5yWYLycJT7h82Qn-evxrI58RbXe0idi8vvUQB0gq0CiDQ4_i8C61yhegm6b9MwQ0iJOKhzc0O_hJLuzhX5TvyZfJ2OYPgn9WRmvMhKDb7OTT9pyBLVZuz9fiFFdW80U2ihga5GGkfVgIwHfWopmE5y3fyDIZi-MntsD6v4clrLNSS8eWPmVQDqG3nSMD_GirEKACCo2gJi9mrkHyvNT_P4ysVDMuP_cLwdcnsekrKgFBLAqOZQRJpqsCQcFHUugGSbL0GO44NvSSp43DqZKrfRwrwLJKC3p9LAAdEpd2eqJek9fQVnO9vcDV2Pnyh3Sj7Go9v097QALTraRLFuyWgmiVGMtda65murMKuei7aaaoz-wiDqn_11eCoLEG6yHRglLXaY-HC6nyD78rOYJ3eBvtZ-P5wqRJXzaYr84Q8WE99ZZTp-wYQolFZ7ZWNX-YzyPtCu2nNp8WDSnQZlYOK7hB2iomDpMBlQ5p-5h-FsMkVUw5g1HNkpOmcQUy1aialXejeZnS_wbTTGvCbaHXcQX8y1gqL0YoK7SxkBF0BZ1w99YubpEwY_uXmu4qUsmoerEVJ69ONposfzb3IBFwzDofXlb-Ef7_5OAHexvSVlGfLXNyaqFVjc46yGgKqgEB_noTdjgHSCvjCyYpBqe1xDzqfun-Vj923BMXlB_-aJgTWo7Gdcdtd86R-e88PcQLCkT3absJxKf2V2kPTqEieiEIaqbztnopoBnkiezTO5lim0ZzyVnIeEf43LoupHeW2PcMN01ohI5em3zJRvMTdyMvMko_-UJp5--hvDM_SnvcSxZ6ROj1D59i4JLUwkaOH7xN5D28CNfjYm5AHFBCGyMJ7GYg4DHSlqhiVdcj236Sa18tnP_e1fcsOExOiUIWFbpd51iSXr3Hxn7vm-x4hyifypNCjGntC5LVJIbjnjAAwZxcZJEnEPdekSJ1l25mlHot9BqPQTkuyJQKazVC8nQyo2YzVBch5ThT70O7JQYrNV9iUMpNP0JgWfe_TAzybMHOex0WU2OXP8cVOnQNr7JbUGLXIDINQg02z_I0d00nHoqUaOpaHKI5OJiOMIWSSu60dcAFu2KTz7IlpQGJa_MLb0b21I84L9hKC3ms_kqzy7G_Qiss1UbnR-e9vHW5UF6MqSTZ50NqTD8KUbcNVEhB1h65sJOdzru4o2TeLze5PY-3JHFl6P_GQ6F7aZHRBOOPf6sH27dYN0z25prPZ4y0o94WSugcUG2iCJOekDCK9HQ_xzjxzgnf0icTIqouTxpZAd124ZbLg3wUToy_NZKACj2IGlFdlnICndOx61ROIvXCPgpNNLadFKLJojXX9FJkejU8OqIl17K9TLNreXbCM1Isyr0lvQ8Rf8eAkyJgIAKsyKhwR2n33WvhTxnOZqi2LdC3idK7bASppOUqAmxANvZ_4jkkomWyLJsN6e_eEFv-8aFmKSoKJEHIFUqMuaTd-RSbD4nbCxs1k6oDW6wRkHEzQRfLe58sTxW8TSKVX5m-3XS1iYS2HwUXPaWAy3Tu4Snfqz56PSm3SVvsOKdiCzJof9udeK28j3dVRg8EjIlQTtMXLxe-PuZ-1OT87SYqeONg1o28_8-lgAbv78MBwLScLLLi_iGNpdT8bBlHvIJQElkNJkF0-wW_97AUTC4eir4RKbUslTsgPp1Pn \ No newline at end of file +gAAAAABnk1xQzWSAT0Vf77Vrl7IWhzfgK3L0X4FssVTP-qNrVZMuniiANHgTeRl1RJDmImV3BPA4ZHu7AsOP3QryfS3dE31h0ggfBio4we76ui3VqgJqgzt2P7AA3U7bxdZXQDd6peKShMDJDO6ssYflmGEpgYkpSSHezcYxVq3x5hmew1TYBl0ssOqKID9lrV9ZesO8TWcNGHEo4AE53kBq0K-3vA51MWrGXM9qCS2IuVXoV6yuAKryju97rMu7kFwnKIEgg8DuQqmWSI9f7acUHzuFK4vOSmgryJxGdXvqwshy822n94xLnRPEpQHaeTEVyoJRtA3gnu3Q1FOg6Z7Xe1Ne_lY3dmn8KBrt9ghf5GmbAefKllsW-dDhutL--5vdBgSBu0WcqqXE2LGXM2-g-Vlc4MBmtyJGv1icP6MRFbV0vKd5KlTwD-3hcSxDkqUsU5-xSojeXuXHsvvP0nPIrZJXSJgAkmYtQ4y5dRjPKsg3qQd_g7YXsblzH9GQufcd891k4aAQrQNMjKOlUiNycduxC0bsWuDBsEGBYh7kVh43oOJZOrCX8h-JfKxJWU3k82fJkDhdTGSVVD62BsK77s1FkwbV6NGQk-HCyxFat6j4u8byB-HDXG8xGx5FwH0OdgZHcEuOrDo4JUsagJ_D61Ooo2ysuvVu0V-xTU660nkqg6t14kPl2aXzHWlAjv6mrKgvFYD56XJ1HD7uuRJWdwtDi2ANfnYBWyzYx26R_fCjnaBGb_n_CNZtc1rz933Ssky9WvUu3FduAJ7RV4Y0aqBJ17BXrT_HEpdr7kAIijqp60lDh0U2CZ7I-6gwJ0LhqNWqL_xnH8YIKkPoVh-9Hx7UaIHtmxJrVihsdtZOS6T_b5OduL3rv4K1i6vspoTSqyywlFaTXVLppFTepHoXUR1ep4z9mPfiSrYlyvD9L0OWAXt5sk78yELF0MBQa7wvRHxP3sLAke0kgVZwjdBKB5uECdpDgDlmzS8A9ZxvH2xs4ImgUqm_kDN4EhB45M1Go3UJWeo8HwHBQ1Y7avCORX7OIDYOjXdjVw31lhiUNYlVVgBuUTi2AM4wMgmMgIR_QrYxC414VRPQ8K32R0MoyBD5DrTZH7-ptFa9PxtTrN7D_sMK8khrIKv4HYZ6ysKRNifoQVeucriNt6URRJtGfMlHgk9oqi7uClfRHfObbchw6biXgrJIAlvOIFO7EzO6_SWkqYjTF2zxZL_wTJA-EkNSsIwxpL4gXlIRpNTAaf8EZw17fYpFmy_GA4RL8tbjku3q7Ik55mhsgK_D5eLMuJmj_qy_q4zs5BMAikfPguP-E7NlNS62TIGTwsHOKt6uMgPDAGL3TEwEIYkx9GmJnGJ-mDYD5mJI400BWj_4LIh3e-J1VwcCdzGBa04rVEyXz0YzTiAG4O2gWQrrPnTw1ZeWaqmNG9_pFJF5tuWR1BYk95qM0VBWIkmJPnpDRZRPn3Eso74TgwmlfIRVtjKyWyI_ZGOIJwK3WrAUwG-W-OxEnzdqp-sTjZNF-R2AGD-VlkZev9L_jWNFKibdlmXW1zRmy_A7w-_Jg5cQ5lpr4ggBsjDwx8sHaUlVjd6CLgrM2knJPQJRac2YYOYGGyeCj3IkebDHv9LeckUieNGdajmV6pooIyyXEp0BFWn16YOGmVxC1Rs0H0pnd5eiZq5HdKZ1Vy_FQAufUGKyXn_OZ0DUfeq-Aw5gZF4qqZOsniv-caVc4Du8I8SsFT5SwJ2Sq2RwUfWEiprrR38oOB8TPeaaOhd2veYdn77c9x7AsvsZvQnPjFj3rAIv9Q3g8i79-x4GsaLrhYx12_zs9GlNJPr0kHHFD1a0hpPxjR13azGoNy-N-5Oci7GbaAQDZttq9dfx4Y5M18H3DLupnhU421P8jikJsKIT8f4xns7G8a3lbKRoB2gge3y8H58vHhvmQKjwd_lFeQJaD-Ix2wNH2uyh9H1YKYhTg10c4nIa1IxohyMmyIrfCGdVFM_hRLOv2nd3Dn4Mtd-Ad-4Tksml0t39RX5npcEEvFfzEeoO4A5ci2GGS8mHtNV6_s9KhTd7p_Ch82X6PFz6AetlTeNmjT46xYypjlTHvTp5qqgVs9F-gvy0LeR4oywq3iAL8a-_HmtXXRUqqi5j0gRySIEiF4wsjz4t6F3DPtyrQcKLtdN1d1ZwMi6GUXsZS5dp7grC7l72elEDV66KvvrUx-BVSPAhPN5nN_lyTkXgOu_yWGhR2u7q5fGJQfwZrmhlabNPLNgOn7fee69AGMWvfGeVGwTCaY68NWIWpDOo4Su0VEM11HqTVnhunXt9a-jUNQgFJxWyBmQRbGRUs8nvYLj4InK_uh3hxfbZQwBOqpMFeg3c-TZk-yaksGp57pD1t5PVPCyMjZVJSQhyQewHqwzgMsANsh3ZpeVKVR2-PhouKFKpDRVpnl5gWMovD4aXUhMdyA1xIdVnZ9sSH4bLkCtqixJ-_yvMnAIu-PMAzooPLV9cYpBoUPTNYUYesgya0q5WfKyDvYrCd23yIy98YO4H5I_Yz7EFwj895ca3p1r5pxxO1HxHBUgB3pwlsPkDWh4DfFVKEtTHiO1it3c-mEZVeWCOQArmV-4O6tTsAk6zsPdcYYdPRMjrMtLGqNgDQNY_lNE08TIqIi48-NkN4yZB9UFE6mZdjrxvsMbeyE2yPPbkqVpdQ-IdSECjekXS10v5nUzmu0ZA-JXsev8oItVn8BtmalnbYdx5dQDpUakBhjrAHMbQB2FYp2DxnXop6vBtNTi_9mC6IdkuoKgJda8TChFp7ab7O1yoRlq8m4OK66xseH_gyfmocroDmlIRDl9En7o8Pz4XPOImtkr3gDPA7Pi5aEqxpoRkZxmApPHtTP3Tp6QWS0VQzFYHwQDv-L8rpffvvZ0z7g21G6ozH3g0Y1b5CJlMxFRyx9b2tOn3L4g0iRAZ1EtU-AzVLhc5NEIiVofIcfMXCzsamf0lCGCfOSE7xGgHHfM4vyGo07OvZdEg9kqfOTaIeM6meR9G0W5LR0zwRGuvAL4Weg575rIrq-AQoRkiXJIG55fjJNKwx8s1wzD5Q6vCoBOzfWMVuoSlC3HpUMXWTiItq5fHKU_oSutWFdrdJ7gLSbLRJzdTO7DH00Wnu4lh-KBStDJxbkOjxzuefnT6RHgNbdJUBtBLoMGbtdcjLLjZFrhR2s8BpbT-3Axc91EyqgD23a3GO5reOVnOL89t-g0XknKVplCArvHXI6hjkCvi6BuNna4TVkKVJOp_n9fnDw0sXL1quyH6nBp7OZ-fyl2VskgWbEbZPLpD4xDTo2siKSGcKkM_Dqmzt9njagifVQPj828dI0Fb7YjByqwwDjedUz0Z3Is9tHGAp0EdXJEbQNhxivqgAu8DVd83Ghc5kRXnzs-bxbWBjxiLb_oFqi_yxLevtav8y1_de9sZZ45ZYyVOLvueAGl3T8FajeSqEsGCIK0tF2dIfXDIEG5xwjp8nE0hK8XxAJ7aDp3CILp-TaKVZ7Sgc2HsWlDR5XzweeYIqyicIdYBOJ6uPxZJfRfZqeVm1axSYWlHaUKhFsgCrOVZGWM-EnZoO-AYR3Yh_xI_Oek-Te9tEPUS5JAH3vSlsXH9nh-TfhQI02Qbr3uvIsJGPKnusqk23hW51P_vsXGpHelAE6Ea-gAfvac-7BrcVWmEX98MSm_BLcxGthftfFtOaZNfcdmVn_F5czeXPCohA7fzYOK8-Miavhb47l0lL8zR0fWhrRU0ylCZTXESlZStiC6UbPvzN-QtOq0-BU7S_i5MAdNE5MOH47D3PEscQjIv2p4D8xPRTnRzvFqFM9Rl3M8HU9xbDum3_cg_rCWURwWQxwrvoGy_kQq1Vl2cz03vOmkUfCMmiGuqI0Q2kL8Sex8ZilUFKmMu4-zFvTaOoQy9eSC-yqUNp8CFDG0B7C0zYYCT96NszR4YS-EH-EfCNYBFbbeTA-EANb8ZWitMPS3skqpAwIl3dVcGyRCBqb1vtMiSrbyywQVijlk= \ No newline at end of file diff --git a/hyperdrive/Constants.py b/hyperdrive/Constants.py index d3f3afe..b7a6fe0 100755 --- a/hyperdrive/Constants.py +++ b/hyperdrive/Constants.py @@ -237,6 +237,7 @@ def get_orders_path(self): def get_new_orders_path(self, provider): return os.path.join( DATA_DIR, + 'orders', f'{provider}.csv' ) diff --git a/hyperdrive/Exchange.py b/hyperdrive/Exchange.py index 1b00f81..7abce16 100755 --- a/hyperdrive/Exchange.py +++ b/hyperdrive/Exchange.py @@ -67,7 +67,9 @@ def create_order(self, symbol, side, notional): 'side': side.lower(), 'type': 'market', 'notional': str(notional), - 'time_in_force': 'day' + 'time_in_force': ( + 'gtc' if symbol in C.ALPC_CRYPTO_SYMBOLS else 'day' + ) } return self.make_request('POST', 'orders', payload) diff --git a/hyperdrive/History.py b/hyperdrive/History.py index 5379d7e..b779be0 100755 --- a/hyperdrive/History.py +++ b/hyperdrive/History.py @@ -17,6 +17,8 @@ from sklearn.metrics import classification_report from imblearn.over_sampling import SMOTE from Calculus import Calculator +from collections.abc import Callable +import Constants as C class Historian: @@ -26,19 +28,86 @@ def __init__(self): # add fx to perform calculations on columns # takes calc.fx, df, and column names as args, fx args - def buy_and_hold(self, close, init_cash=1000): + def from_holding(self, close, init_cash=1000): # returns a portfolio based on buy and hold strategy portfolio = vbt.Portfolio.from_holding( close, init_cash=init_cash, freq='D') return portfolio - def create_portfolio(self, close, signals, init_cash=1000, fee=0): + def from_signals(self, close, signals, init_cash=1000, fee=0): # returns a portfolio based on signals portfolio = vbt.Portfolio.from_signals( close, signals, ~signals, init_cash=init_cash, freq='D', fees=fee ) return portfolio + def optimize_portfolio( + self, + close: pd.DataFrame, + indicator: Callable, + top_n: int, + period: str, + init_cash: float, + **kwargs: dict[str, any] + ) -> vbt.Portfolio: + if C.CLOSE in close.columns: + close = close.set_index(C.CLOSE) + signals = close.apply(indicator, **kwargs) + close = close.dropna() + positions = pd.DataFrame( + 0, index=close.index, columns=close.columns) + holdings = {"cash": init_cash} + prev_period = None + prev_symbols = set() + for day in close.index: + curr_period = getattr(day, period) + # if is first of the period + if prev_period != curr_period: + # Rank symbols by indicator and select top_n + top_symbols = set(signals.loc[day].nlargest(top_n).index) + # Sell old positions for the top symbols + minus = prev_symbols.difference(top_symbols) + for symbol in minus: + size = holdings[symbol] + positions.loc[day, symbol] = - size + holdings["cash"] += close.loc[day][symbol] * size + del holdings[symbol] + # Buy new positions for the top symbols + plus = top_symbols.difference(prev_symbols) + notional = holdings["cash"] / len(plus) + for symbol in plus: + size = notional / close.loc[day][symbol] + positions.loc[day, symbol] = size + holdings[symbol] = size + holdings["cash"] -= notional + # Update prev values + prev_period = curr_period + prev_symbols = top_symbols + + # Forward fill positions to maintain holdings + positions = positions.ffill().fillna(0) + + # Convert to orders format + portfolio = vbt.Portfolio.from_orders( + close=close, + size=positions, + freq='D', + init_cash=0, + group_by=True + ) + return portfolio + + def from_orders( + self, + close: pd.DataFrame, + size: pd.DataFrame, + fee: float = 0 + ) -> vbt.Portfolio: + portfolio = vbt.Portfolio.from_orders( + close, size, freq='D', fees=fee, init_cash=0, group_by=True + ) + return portfolio + def fill(self, arr, method='ffill', type='bool'): # forward fills or nearest fills an array df = pd.DataFrame(arr) diff --git a/requirements.txt b/requirements.txt index 2b99ced..425578d 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -python-dotenv == 1.0.0 +python-dotenv == 1.0.1 pandas == 1.5.3 -robin-stocks == 3.3.0 -boto3 == 1.26.165 +git+https://github.com/bhyman67/robin_stocks_bh67.git@0d4574fb11ba4f1ff0acc8046174924bfd7ec532 +boto3 == 1.36.5 polygon-api-client == 1.10.1 -pytz == 2023.3 +pytz == 2024.2 vectorbt == 0.25.4 scipy == 1.11.1 scikit-learn == 0.24.2 auto-sklearn == 0.15.0 cryptography == 41.0.1 ta == 0.10.2 -python-binance == 1.0.17 +python-binance == 1.0.27 imbalanced-learn == 0.8.1 icosphere == 0.1.3 pynisher == 0.6.4 diff --git a/scripts/update_api.py b/scripts/update_api.py index f8752b9..47770e7 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -49,7 +49,7 @@ def create_portfolio_preview(close, signals, invert): holding_signals = np.full(len(signals), not invert) - holding_pf = hist.create_portfolio(close, holding_signals, init_cash) + holding_pf = hist.from_signals(close, holding_signals, init_cash) if C.PREF_EXCHANGE == C.BINANCE: fee = C.BINANCE_FEE else: @@ -57,7 +57,7 @@ def create_portfolio_preview(close, signals, invert): quote = C.KRAKEN_SYMBOLS['USD'] pair = kr.create_pair(base, quote) fee = kr.get_fee(pair) / 100 - hyper_pf = hist.create_portfolio( + hyper_pf = hist.from_signals( close, ~signals if invert else signals, init_cash, fee) holding_values = holding_pf.value() diff --git a/test/test_Exchange.py b/test/test_Exchange.py index ec08942..4d7b2e1 100755 --- a/test/test_Exchange.py +++ b/test/test_Exchange.py @@ -33,8 +33,8 @@ def test_get_positions(self): def test_close_position(self): positions = alpc.get_positions() - if any([position['symbol'] == 'QQQ' for position in positions]): - order = alpc.close_position('QQQ') + if any([position['symbol'] == 'LTC/USD' for position in positions]): + order = alpc.close_position('LTC/USD') assert 'id' in order def test_get_order(self): @@ -48,9 +48,9 @@ def test_get_account(self): def test_create_order(self): positions = alpc.get_positions() side = 'buy' - if 'QQQ' in [position['symbol'] for position in positions]: + if 'LTC/USD' in [position['symbol'] for position in positions]: side = 'sell' - order = alpc.create_order('QQQ', side, 1) + order = alpc.create_order('LTC/USD', side, 10) assert 'id' in order diff --git a/test/test_History.py b/test/test_History.py index 72d4dd6..1502d8d 100755 --- a/test/test_History.py +++ b/test/test_History.py @@ -3,6 +3,7 @@ import pandas as pd sys.path.append('hyperdrive') from History import Historian # noqa autopep8 +import Constants as C # noqa autopep8 hist = Historian() @@ -27,14 +28,35 @@ X = pd.DataFrame({'i': data, 'j': data}) y = np.array([True] * majority + [False] * minority) +orders_index = pd.to_datetime( + pd.Series(['2025-01-01', '2025-01-02'], name=C.TIME)) +orders_close = pd.DataFrame({ + 'AAPL': [200, 100], + 'META': [25, 50] +}, index=orders_index) + class TestHistorian: - def test_buy_and_hold(self): - stats = hist.buy_and_hold(close).stats() + def test_from_holding(self): + stats = hist.from_holding(close).stats() + assert 'Sortino Ratio' in stats + + def test_from_signals(self): + stats = hist.from_signals(close, test_ffill).stats() + assert 'Sortino Ratio' in stats + + def test_from_orders(self): + size = pd.DataFrame({ + 'AAPL': [1, 0], + 'META': [0, 1] + }, index=orders_index) + stats = hist.from_orders(orders_close, size).stats() assert 'Sortino Ratio' in stats - def test_create_portfolio(self): - stats = hist.create_portfolio(close, test_ffill).stats() + def test_optimize_portfolio(self): + indicator = pd.Series.diff + stats = hist.optimize_portfolio( + orders_close, indicator, 1, 'day', 225).stats() assert 'Sortino Ratio' in stats def test_fill(self):