We recently added a flag to the screen run, screen backtest and screen rolling backtest API endpoints so the output can be returned as a Pandas DataFrame. The ranks, data_universe and data endpoints already had this flag.
The most recent build is required. Run “pip install --upgrade p123api” to get latest the version.
Example:
import p123api
import pandas as pd
try:
client = p123api.Client(api_id='<your id>', api_key='<your key>')
data = client.screen_backtest(
{
'screen': 1094, #P123 public Piotrosk screen
'startDt': "2022-01-02",
}, True) # True = output to Pandas DataFrame, False = to JSON.
pd.set_option('display.width', None)
#Print the entire result.
print(data)
#Print them one section at a time.
print("\nSTATS")
print(pd.DataFrame(data['stats']))
print("\nRESULTS")
print(pd.DataFrame(data['results']))
print("\nCHART")
print(pd.DataFrame(data['chart']))
except p123api.ClientException as e:
print(e)
The True flag for the dataframe should work. I cant copy/paste your code since the formatting gets all messed up. Here is example. Dont forget the import pandas line.
import p123api
import pandas as pd
try:
client = p123api.Client(api_id='insert yourid', api_key='yourkey')
data = client.screen_backtest(
{
'screen': 1094, # P123 public Piotrosk screen
'startDt': '2020-12-05',
'endDt': '2022-12-14',
#'frequency': 'Every Week',
'rebalFreq': 'Every Week',
#'holdingPeriod': 180, #na for Screen Backtest endppoint. This is only for Rolling Screen Backtest.
'slippage':0.25,
'pitMethod': 'Complete'
}, True) # True = output to Pandas DataFrame, False = to JSON.
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
print(data)
except p123api.ClientException as e:
print(e)
To see the info for the p123api package, run this at a command prompt: pip show p123api
Mine shows Version: 1.3.4. I have never ran that before and I dont know how often the version # is updated.
The issue with the latest version of the script you posted is that you need to switch it back to have ‘frequency’ instead of rebalFreq since this script is for screen_rolling_backtest. Each of the API endpoints have their own definitions and you need to check the API schema (link to it in my last post) since you have to use exactly what is written there.
Yes, I did a copy/paste of your latest code and the only thing I had to change was to use ‘frequency’: ‘Every Week’. It ran with no errors.
I will check with the developer to see if there is another way to get the version. But you said you ran the pip install command, so I doubt it is a version issue.
Dan, I figured it out. It was a wrong version of p123api.
I have multiple versions of Python installed and if you run PIP in the wrong version, it will not work correctly even though the command line says its installed.
One other question.
If I want to run a screen rolling backtest, but I want to alter the ranking system each time via the API, I can use client.rank_update(data) to update the ApiRankingSystem and then reference that RS in the rolling backtest via this?
Yes. Your script would first use client.rank.update to change the ApiRankingSystem to whatever you want for that iteration.
In you example earlier, your screen_rolling_backtest script defined the screen using the id: ‘screen’: 1094,
If you do that, just set the ranking system in that screen (a screen that is not a p123 screen like 1094) to be use the ApiRankingSystem ranking system.
You could also define the screen inside the screen_rolling_backtest script. But that is more complicated, so only do it if you need to change other settings in the screen.
Dan, here is the rolling back test example in the docs. It is unclear to me how to specify a ranking system into this. There is no ranking system parameter listed here. Can you provide an example of this using the APIRankingSystem? Do I need to first use another API function to define the screen and its ranking system? Maybe something like screen_backtest() ?
I moved this conversation with Tony to email. But this example could be helpful to others. It answers the question above and also has a section at the end that gives an example of how to handle the output from screen_rolling_backtest.
data = client.screen_rolling_backtest(
{
#'screen': 1094, #Could have used a screen id instead IF you don’t need to make any changes to the screen settings during the tests you are running.
'screen': {
'type': 'stock',
"universe": 'SP500',
'maxNumHoldings': 10, #0 for all
'method': 'long',
'benchmark': 'SPY',
'rules': [{'formula': 'close(0) > 1'},
{'formula': 'Mktcap > 3000'},
],
'ranking': 'ApiRankingSystem' #another section of the script would make changes to ApiRankingSystem
},
'startDt': "2021-01-03",
# Optional parameters
'endDt': '2022-01-01',
}
)
# Run the line below to find the names of the keys to use in the code below.
#print(data.keys()) # Returns: dict_keys(['cost', 'quotaRemaining', 'columns', 'rows', 'average', 'upMarkets', 'downMarkets'])
# Output the column headers and the data:
pandas.set_option('display.width', None)
pandas.set_option('display.max_colwidth', 40)
df1 = pandas.DataFrame(data['columns']).transpose()
df2 = pandas.DataFrame(data['rows'])
df3 = df1.append(df2, ignore_index = True)
print(df3)
df1 = pandas.DataFrame(data['columns']).transpose()
df2 = pandas.DataFrame(data['average']).transpose()
df3 = pandas.DataFrame(data['upMarkets']).transpose()
df4 = pandas.DataFrame(data['downMarkets']).transpose()
df5 = df1.append([df2,df3,df4], ignore_index = True)
print(df5)
Hi Tony. One option is to give the screen id and maxNumHoldings (optional) and then also the parameters related to the rolling backtest settings. If I modify the example above, it would look like this:
import p123api
import pandas as pd
try:
client = p123api.Client(api_id='OUR ID', api_key='YOUR KEY')
data = client.screen_rolling_backtest(
{
'screen': {
'id': 1094,
'maxNumHoldings': 10 # 0 for all. Optional.
},
'startDt': '2022-01-01',
#Optional params
'endDt': '2024-01-01',
'pitMethod': 'Prelim', #'Complete'
'precision': 3,
'transPrice': 1, # 1 - Open | 3 - Avg of Hi and Low | 4 - Close
'maxPosPct': 0,
'slippage': 0.25,
'longWeight': 100,
'shortWeight': 100,
'frequency': 'Every 4 Weeks', #'Every Week',
'holdingPeriod': 182
})
# Run the line below to find the names of the keys to use in the code below.
# print(data.keys()) # Returns: dict_keys(['cost', 'quotaRemaining', 'columns', 'rows', 'average', 'upMarkets', 'downMarkets'])
# Output the column headers and the data:
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', 40)
df1 = pd.DataFrame(data['columns']).transpose()
df2 = pd.DataFrame(data['rows'])
df3 = pd.concat([df1, df2], ignore_index=True)
print(df3)
df1 = pd.DataFrame(data['columns']).transpose()
df2 = pd.DataFrame(data['average']).transpose()
df3 = pd.DataFrame(data['upMarkets']).transpose()
df4 = pd.DataFrame(data['downMarkets']).transpose()
df5 = pd.concat([df1, df2, df3, df4], ignore_index=True)
print(df5)
except p123api.ClientException as e:
print(e)
Ah! Thank you. That is exactly what I was looking for.
To clarify, if you specify a screen ID and some other parameters, those parameters will override the screen parameters? No limitation on which parameters work that way?
Those parameters are technically not screen parameters. They are the settings to use for the rolling backtest. Screen parameters would be things like universe, ranking system, etc.
There is no option available to give the screen id and then change the screen parameters of that existing screen.