687 lines
26 KiB
HTML
687 lines
26 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
|
|||
|
|
<head>
|
|||
|
|
|
|||
|
|
<meta charset="utf-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
|||
|
|
<title>PMM Algorithm Specification</title>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<style type="text/css">
|
|||
|
|
body {
|
|||
|
|
font-family: Helvetica, arial, sans-serif;
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
padding-top: 10px;
|
|||
|
|
padding-bottom: 10px;
|
|||
|
|
background-color: white;
|
|||
|
|
padding: 30px; }
|
|||
|
|
|
|||
|
|
body > *:first-child {
|
|||
|
|
margin-top: 0 !important; }
|
|||
|
|
body > *:last-child {
|
|||
|
|
margin-bottom: 0 !important; }
|
|||
|
|
|
|||
|
|
a {
|
|||
|
|
color: #4183C4; }
|
|||
|
|
a.absent {
|
|||
|
|
color: #cc0000; }
|
|||
|
|
a.anchor {
|
|||
|
|
display: block;
|
|||
|
|
padding-left: 30px;
|
|||
|
|
margin-left: -30px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
bottom: 0; }
|
|||
|
|
|
|||
|
|
h1, h2, h3, h4, h5, h6 {
|
|||
|
|
margin: 20px 0 10px;
|
|||
|
|
padding: 0;
|
|||
|
|
font-weight: bold;
|
|||
|
|
-webkit-font-smoothing: antialiased;
|
|||
|
|
cursor: text;
|
|||
|
|
position: relative; }
|
|||
|
|
|
|||
|
|
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
|||
|
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
|
|||
|
|
text-decoration: none; }
|
|||
|
|
|
|||
|
|
h1 tt, h1 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h2 tt, h2 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h3 tt, h3 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h4 tt, h4 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h5 tt, h5 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h6 tt, h6 code {
|
|||
|
|
font-size: inherit; }
|
|||
|
|
|
|||
|
|
h1 {
|
|||
|
|
font-size: 28px;
|
|||
|
|
color: black; }
|
|||
|
|
|
|||
|
|
h2 {
|
|||
|
|
font-size: 24px;
|
|||
|
|
border-bottom: 1px solid #cccccc;
|
|||
|
|
color: black; }
|
|||
|
|
|
|||
|
|
h3 {
|
|||
|
|
font-size: 18px; }
|
|||
|
|
|
|||
|
|
h4 {
|
|||
|
|
font-size: 16px; }
|
|||
|
|
|
|||
|
|
h5 {
|
|||
|
|
font-size: 14px; }
|
|||
|
|
|
|||
|
|
h6 {
|
|||
|
|
color: #777777;
|
|||
|
|
font-size: 14px; }
|
|||
|
|
|
|||
|
|
p, blockquote, ul, ol, dl, li, table, pre {
|
|||
|
|
margin: 15px 0; }
|
|||
|
|
|
|||
|
|
hr {
|
|||
|
|
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
|
|||
|
|
border: 0 none;
|
|||
|
|
color: #cccccc;
|
|||
|
|
height: 4px;
|
|||
|
|
padding: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body > h2:first-child {
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0; }
|
|||
|
|
body > h1:first-child {
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0; }
|
|||
|
|
body > h1:first-child + h2 {
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0; }
|
|||
|
|
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0; }
|
|||
|
|
|
|||
|
|
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0; }
|
|||
|
|
|
|||
|
|
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
|
|||
|
|
li p.first {
|
|||
|
|
display: inline-block; }
|
|||
|
|
li {
|
|||
|
|
margin: 0; }
|
|||
|
|
ul, ol {
|
|||
|
|
padding-left: 30px; }
|
|||
|
|
|
|||
|
|
ul :first-child, ol :first-child {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
|
|||
|
|
dl {
|
|||
|
|
padding: 0; }
|
|||
|
|
dl dt {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-style: italic;
|
|||
|
|
padding: 0;
|
|||
|
|
margin: 15px 0 5px; }
|
|||
|
|
dl dt:first-child {
|
|||
|
|
padding: 0; }
|
|||
|
|
dl dt > :first-child {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
dl dt > :last-child {
|
|||
|
|
margin-bottom: 0; }
|
|||
|
|
dl dd {
|
|||
|
|
margin: 0 0 15px;
|
|||
|
|
padding: 0 15px; }
|
|||
|
|
dl dd > :first-child {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
dl dd > :last-child {
|
|||
|
|
margin-bottom: 0; }
|
|||
|
|
|
|||
|
|
blockquote {
|
|||
|
|
border-left: 4px solid #dddddd;
|
|||
|
|
padding: 0 15px;
|
|||
|
|
color: #777777; }
|
|||
|
|
blockquote > :first-child {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
blockquote > :last-child {
|
|||
|
|
margin-bottom: 0; }
|
|||
|
|
|
|||
|
|
table {
|
|||
|
|
padding: 0;border-collapse: collapse; }
|
|||
|
|
table tr {
|
|||
|
|
border-top: 1px solid #cccccc;
|
|||
|
|
background-color: white;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 0; }
|
|||
|
|
table tr:nth-child(2n) {
|
|||
|
|
background-color: #f8f8f8; }
|
|||
|
|
table tr th {
|
|||
|
|
font-weight: bold;
|
|||
|
|
border: 1px solid #cccccc;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 6px 13px; }
|
|||
|
|
table tr td {
|
|||
|
|
border: 1px solid #cccccc;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 6px 13px; }
|
|||
|
|
table tr th :first-child, table tr td :first-child {
|
|||
|
|
margin-top: 0; }
|
|||
|
|
table tr th :last-child, table tr td :last-child {
|
|||
|
|
margin-bottom: 0; }
|
|||
|
|
|
|||
|
|
img {
|
|||
|
|
max-width: 100%; }
|
|||
|
|
|
|||
|
|
span.frame {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden; }
|
|||
|
|
span.frame > span {
|
|||
|
|
border: 1px solid #dddddd;
|
|||
|
|
display: block;
|
|||
|
|
float: left;
|
|||
|
|
overflow: hidden;
|
|||
|
|
margin: 13px 0 0;
|
|||
|
|
padding: 7px;
|
|||
|
|
width: auto; }
|
|||
|
|
span.frame span img {
|
|||
|
|
display: block;
|
|||
|
|
float: left; }
|
|||
|
|
span.frame span span {
|
|||
|
|
clear: both;
|
|||
|
|
color: #333333;
|
|||
|
|
display: block;
|
|||
|
|
padding: 5px 0 0; }
|
|||
|
|
span.align-center {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden;
|
|||
|
|
clear: both; }
|
|||
|
|
span.align-center > span {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden;
|
|||
|
|
margin: 13px auto 0;
|
|||
|
|
text-align: center; }
|
|||
|
|
span.align-center span img {
|
|||
|
|
margin: 0 auto;
|
|||
|
|
text-align: center; }
|
|||
|
|
span.align-right {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden;
|
|||
|
|
clear: both; }
|
|||
|
|
span.align-right > span {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden;
|
|||
|
|
margin: 13px 0 0;
|
|||
|
|
text-align: right; }
|
|||
|
|
span.align-right span img {
|
|||
|
|
margin: 0;
|
|||
|
|
text-align: right; }
|
|||
|
|
span.float-left {
|
|||
|
|
display: block;
|
|||
|
|
margin-right: 13px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
float: left; }
|
|||
|
|
span.float-left span {
|
|||
|
|
margin: 13px 0 0; }
|
|||
|
|
span.float-right {
|
|||
|
|
display: block;
|
|||
|
|
margin-left: 13px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
float: right; }
|
|||
|
|
span.float-right > span {
|
|||
|
|
display: block;
|
|||
|
|
overflow: hidden;
|
|||
|
|
margin: 13px auto 0;
|
|||
|
|
text-align: right; }
|
|||
|
|
|
|||
|
|
code, tt {
|
|||
|
|
margin: 0 2px;
|
|||
|
|
padding: 0 5px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
border: 1px solid #eaeaea;
|
|||
|
|
background-color: #f8f8f8;
|
|||
|
|
border-radius: 3px; }
|
|||
|
|
|
|||
|
|
pre code {
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 0;
|
|||
|
|
white-space: pre;
|
|||
|
|
border: none;
|
|||
|
|
background: transparent; }
|
|||
|
|
|
|||
|
|
.highlight pre {
|
|||
|
|
background-color: #f8f8f8;
|
|||
|
|
border: 1px solid #cccccc;
|
|||
|
|
font-size: 13px;
|
|||
|
|
line-height: 19px;
|
|||
|
|
overflow: auto;
|
|||
|
|
padding: 6px 10px;
|
|||
|
|
border-radius: 3px; }
|
|||
|
|
|
|||
|
|
pre {
|
|||
|
|
background-color: #f8f8f8;
|
|||
|
|
border: 1px solid #cccccc;
|
|||
|
|
font-size: 13px;
|
|||
|
|
line-height: 19px;
|
|||
|
|
overflow: auto;
|
|||
|
|
padding: 6px 10px;
|
|||
|
|
border-radius: 3px; }
|
|||
|
|
pre code, pre tt {
|
|||
|
|
background-color: transparent;
|
|||
|
|
border: none; }
|
|||
|
|
|
|||
|
|
sup {
|
|||
|
|
font-size: 0.83em;
|
|||
|
|
vertical-align: super;
|
|||
|
|
line-height: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
kbd {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 3px 5px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
line-height: 10px;
|
|||
|
|
color: #555;
|
|||
|
|
vertical-align: middle;
|
|||
|
|
background-color: #fcfcfc;
|
|||
|
|
border: solid 1px #ccc;
|
|||
|
|
border-bottom-color: #bbb;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
box-shadow: inset 0 -1px 0 #bbb
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
* {
|
|||
|
|
-webkit-print-color-adjust: exact;
|
|||
|
|
}
|
|||
|
|
@media screen and (min-width: 914px) {
|
|||
|
|
body {
|
|||
|
|
width: 854px;
|
|||
|
|
margin:0 auto;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
@media print {
|
|||
|
|
table, pre {
|
|||
|
|
page-break-inside: avoid;
|
|||
|
|
}
|
|||
|
|
pre {
|
|||
|
|
word-wrap: break-word;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<style type="text/css">
|
|||
|
|
/**
|
|||
|
|
* prism.js default theme for JavaScript, CSS and HTML
|
|||
|
|
* Based on dabblet (http://dabblet.com)
|
|||
|
|
* @author Lea Verou
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
code[class*="language-"],
|
|||
|
|
pre[class*="language-"] {
|
|||
|
|
color: black;
|
|||
|
|
background: none;
|
|||
|
|
text-shadow: 0 1px white;
|
|||
|
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
|||
|
|
text-align: left;
|
|||
|
|
white-space: pre;
|
|||
|
|
word-spacing: normal;
|
|||
|
|
word-break: normal;
|
|||
|
|
word-wrap: normal;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
|
|||
|
|
-moz-tab-size: 4;
|
|||
|
|
-o-tab-size: 4;
|
|||
|
|
tab-size: 4;
|
|||
|
|
|
|||
|
|
-webkit-hyphens: none;
|
|||
|
|
-moz-hyphens: none;
|
|||
|
|
-ms-hyphens: none;
|
|||
|
|
hyphens: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
|||
|
|
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
|||
|
|
text-shadow: none;
|
|||
|
|
background: #b3d4fc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
|||
|
|
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
|||
|
|
text-shadow: none;
|
|||
|
|
background: #b3d4fc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media print {
|
|||
|
|
code[class*="language-"],
|
|||
|
|
pre[class*="language-"] {
|
|||
|
|
text-shadow: none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Code blocks */
|
|||
|
|
pre[class*="language-"] {
|
|||
|
|
padding: 1em;
|
|||
|
|
margin: .5em 0;
|
|||
|
|
overflow: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:not(pre) > code[class*="language-"],
|
|||
|
|
pre[class*="language-"] {
|
|||
|
|
background: #f5f2f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Inline code */
|
|||
|
|
:not(pre) > code[class*="language-"] {
|
|||
|
|
padding: .1em;
|
|||
|
|
border-radius: .3em;
|
|||
|
|
white-space: normal;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.comment,
|
|||
|
|
.token.prolog,
|
|||
|
|
.token.doctype,
|
|||
|
|
.token.cdata {
|
|||
|
|
color: slategray;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.punctuation {
|
|||
|
|
color: #999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.namespace {
|
|||
|
|
opacity: .7;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.property,
|
|||
|
|
.token.tag,
|
|||
|
|
.token.boolean,
|
|||
|
|
.token.number,
|
|||
|
|
.token.constant,
|
|||
|
|
.token.symbol,
|
|||
|
|
.token.deleted {
|
|||
|
|
color: #905;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.selector,
|
|||
|
|
.token.attr-name,
|
|||
|
|
.token.string,
|
|||
|
|
.token.char,
|
|||
|
|
.token.builtin,
|
|||
|
|
.token.inserted {
|
|||
|
|
color: #690;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.operator,
|
|||
|
|
.token.entity,
|
|||
|
|
.token.url,
|
|||
|
|
.language-css .token.string,
|
|||
|
|
.style .token.string {
|
|||
|
|
color: #a67f59;
|
|||
|
|
background: hsla(0, 0%, 100%, .5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.atrule,
|
|||
|
|
.token.attr-value,
|
|||
|
|
.token.keyword {
|
|||
|
|
color: #07a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.function {
|
|||
|
|
color: #DD4A68;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.regex,
|
|||
|
|
.token.important,
|
|||
|
|
.token.variable {
|
|||
|
|
color: #e90;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.important,
|
|||
|
|
.token.bold {
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
.token.italic {
|
|||
|
|
font-style: italic;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.token.entity {
|
|||
|
|
cursor: help;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<style type="text/css">
|
|||
|
|
pre.line-numbers {
|
|||
|
|
position: relative;
|
|||
|
|
padding-left: 3.8em;
|
|||
|
|
counter-reset: linenumber;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre.line-numbers > code {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.line-numbers .line-numbers-rows {
|
|||
|
|
position: absolute;
|
|||
|
|
pointer-events: none;
|
|||
|
|
top: 0;
|
|||
|
|
font-size: 100%;
|
|||
|
|
left: -3.8em;
|
|||
|
|
width: 3em; /* works for line-numbers below 1000 lines */
|
|||
|
|
letter-spacing: -1px;
|
|||
|
|
border-right: 1px solid #999;
|
|||
|
|
|
|||
|
|
-webkit-user-select: none;
|
|||
|
|
-moz-user-select: none;
|
|||
|
|
-ms-user-select: none;
|
|||
|
|
user-select: none;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.line-numbers-rows > span {
|
|||
|
|
pointer-events: none;
|
|||
|
|
display: block;
|
|||
|
|
counter-increment: linenumber;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.line-numbers-rows > span:before {
|
|||
|
|
content: counter(linenumber);
|
|||
|
|
color: #999;
|
|||
|
|
display: block;
|
|||
|
|
padding-right: 0.8em;
|
|||
|
|
text-align: right;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
|
|||
|
|
</head>
|
|||
|
|
|
|||
|
|
<body>
|
|||
|
|
|
|||
|
|
<h1 id="toc_0">DODO PMM 算法详解</h1>
|
|||
|
|
|
|||
|
|
<p>DODO 是新一代由算法驱动的纯链上流动性提供方案。很多人误以为这是对 AMM(Auto Market Maker)算法的改进,事实上 DODO 提出了全新的算法,其运作机制与 AMM 截然不同,我们将其命名为 PMM(Proactive Market Maker)算法。</p>
|
|||
|
|
|
|||
|
|
<p>本文将全面对算法进行解析,尤其是其链上实现版本。</p>
|
|||
|
|
|
|||
|
|
<h2 id="toc_1">资金池回归模型</h2>
|
|||
|
|
|
|||
|
|
<p>PMM 的资金池由四个参数描述</p>
|
|||
|
|
|
|||
|
|
<ul>
|
|||
|
|
<li>\(B_0\) BaseToken 回归目标</li>
|
|||
|
|
<li>\(Q_0\) QuoteToken 回归目标</li>
|
|||
|
|
<li>\(B\) BaseToken 余额</li>
|
|||
|
|
<li>\(Q\) QuoteToken 余额</li>
|
|||
|
|
</ul>
|
|||
|
|
|
|||
|
|
<blockquote>
|
|||
|
|
<p>以ETH-USDT交易对为例,BaseToken指ETH,QuoteToken指USDT</p>
|
|||
|
|
</blockquote>
|
|||
|
|
|
|||
|
|
<p>价格曲线由下面的公式给出</p>
|
|||
|
|
|
|||
|
|
<p>\[P_{margin}=iR\]
|
|||
|
|
\[if \ B<B_0, \ R=1-k+(\frac{B_0}{B})^2k\]
|
|||
|
|
\[if \ Q<Q_0, \ R=1/(1-k+(\frac{Q_0}{Q})^2k)\]
|
|||
|
|
\[else \ R=1\]</p>
|
|||
|
|
|
|||
|
|
<p>可以发现PMM有三种工作场景:均衡、BaseToken匮乏、QuoteToken匮乏。</p>
|
|||
|
|
|
|||
|
|
<ul>
|
|||
|
|
<li>没有用户交易时,资金池处于均衡状态,BaseToken和QuoteToken都处于其回归目标</li>
|
|||
|
|
<li>当用户卖出BaseToken时,资金池BaseToken余额高于回归目标,而QuoteToken余额小于回归目标。此时PMM算法会尝试卖出多余的BaseToken,以使QuoteToken余额回归均衡状态。</li>
|
|||
|
|
<li>当用户买入BaseToken时,资金池QuoteToken余额高于回归目标,而BaseToken余额小于回归目标。此时PMM算法会尝试卖出多余的QuoteToken,以使BaseToken余额回归均衡状态。</li>
|
|||
|
|
</ul>
|
|||
|
|
|
|||
|
|
<p><img src="/Users/leimingda/Documents/dodo/docs/img/img.006.jpeg" alt=""></p>
|
|||
|
|
|
|||
|
|
<p>参数\(R\)在此过程中起到了促进回归的作用,资金池偏离均衡状态越多,\(R\)越偏离1,PMM算法给出的价格便越偏离市场价,吸引套利者帮助资金池回归均衡状态。</p>
|
|||
|
|
|
|||
|
|
<p>至此我们介绍完毕了PMM算法的运作机制,下一节将进入合约实现环节。</p>
|
|||
|
|
|
|||
|
|
<h2 id="toc_2">交易者</h2>
|
|||
|
|
|
|||
|
|
<p>对交易者来说,最重要的就是平均成交价。平均成交价是边际价格\(P_{margin}\)的积分</p>
|
|||
|
|
|
|||
|
|
<h3 id="toc_3">单一工作场景下的价格</h3>
|
|||
|
|
|
|||
|
|
<h4 id="toc_4">BaseToken匮乏场景</h4>
|
|||
|
|
|
|||
|
|
<p><img src="/Users/leimingda/Documents/dodo/docs/img/img.002.jpeg" alt=""></p>
|
|||
|
|
|
|||
|
|
<p>\[\Delta Q =\int^{B_2}_{B_1}P_{margin}dB= \int^{B_2}_{B_1}(1-k)i+i(\frac{B_0}{B})^2kdB \]
|
|||
|
|
\[= i(B_2-B_1)*(1-k+k\frac{B_0^2}{B_1B_2})\]</p>
|
|||
|
|
|
|||
|
|
<p>平均成交价为:</p>
|
|||
|
|
|
|||
|
|
<p>\[P=\frac{\Delta Q}{B_2-B_1}=i*(1-k+k\frac{B_0^2}{B_1B_2})\]</p>
|
|||
|
|
|
|||
|
|
<p>我们发现,平均成交价只与成交前后系统的状态有关,所以买卖两种操作的价格计算方式是一样的——都是对\(P_{margin}\)进行积分。</p>
|
|||
|
|
|
|||
|
|
<h4 id="toc_5">QuoteToken匮乏场景</h4>
|
|||
|
|
|
|||
|
|
<p>为了简化用户理解,我们只提供sellBaseToken和buyBaseToken两个接口,下面来推导quoteToken匮乏场景下,给出想要买卖的baseToken数量,如何计算价格。</p>
|
|||
|
|
|
|||
|
|
<p>\[\Delta B = \frac{1}{i}(Q_2-Q_1)*(1-k+k\frac{Q_0^2}{Q_1Q_2})\]</p>
|
|||
|
|
|
|||
|
|
<p>已知\(\Delta B, Q_0, Q_1\),求解\(Q_2\),这是一个二次方程,转化为标准形式后:</p>
|
|||
|
|
|
|||
|
|
<p>\[(1-k)Q_2^2+(\frac{kQ_0^2}{Q_1}-Q_1+kQ_1-i\Delta B)Q_2-kQ_0^2=0\]</p>
|
|||
|
|
|
|||
|
|
<p>\[let \ a=1-k, \ b=\frac{kQ_0^2}{Q_1}-Q_1+kQ_1-i\Delta B, \ c=-kQ_0^2\]</p>
|
|||
|
|
|
|||
|
|
<p>因为\(Q_2>=0\),所以舍掉一个负数根,得到\(Q_2\)的公式为</p>
|
|||
|
|
|
|||
|
|
<p>\[Q_2=\frac{-b+\sqrt{b^2-4ac}}{2a}\]</p>
|
|||
|
|
|
|||
|
|
<p>在\(\Delta B>0\)时,\(Q_2>Q_1\); 在\(\Delta B<0\)时,\(Q_2<Q_1\); 在\(\Delta B=0\)时,\(Q_2=Q_1\)</p>
|
|||
|
|
|
|||
|
|
<h3 id="toc_6">Oracle价格变化的影响</h3>
|
|||
|
|
|
|||
|
|
<p>当系统不处于均衡态时,Oracle变化将带来盈利或亏损。举例来讲,假设现在系统处于BaseToken匮乏场景,Oracle价格提高,将导致多余的QuoteToken无法买入足够的BaseToken,提供BaseToken的lp出现亏损。同理如果Oracle价格下降,多余的QuoteToken可以买入更多的BaseToken,使BaseToken余额超过回归目标,提供BaseToken的lp有盈余。</p>
|
|||
|
|
|
|||
|
|
<p>为了计算新Oracle价格下的回归目标,我们进行如下推导:</p>
|
|||
|
|
|
|||
|
|
<p>\[\Delta Q = i(B_2-B_1)*(1-k+k\frac{B_0^2}{B_1B_2})\]</p>
|
|||
|
|
|
|||
|
|
<p>回归时,\(B_2=B_0\),整理出关于\(B_0\)的二次方程</p>
|
|||
|
|
|
|||
|
|
<p>\[\frac{k}{B_1}B_0^2+(1-2k)B_0-[(1-k)B_1+\frac{\Delta Q}{i}] = 0\]</p>
|
|||
|
|
|
|||
|
|
<p>舍弃掉负数解,得到\(B_0\)的解为</p>
|
|||
|
|
|
|||
|
|
<p>\[B_0=B_1+B_1*\frac{\sqrt{1+\frac{4k\Delta Q}{B_1 i}}-1}{2k}\]</p>
|
|||
|
|
|
|||
|
|
<p>这里\(\Delta Q=Q-Q_0\). 可以严格证明,在\(\Delta Q \ge 0\)时,\(B_0\ge B_1\). 这一性质非常重要,因为它保证了BaseToken余额和QuoteToken余额不会同时大于回归目标,或同时小于回归目标。这样一来,PMM只会在文章开头提到的三种状态中转换。</p>
|
|||
|
|
|
|||
|
|
<p>同理我们直接给出\(Q_0\)的计算公式</p>
|
|||
|
|
|
|||
|
|
<p>\[Q_0=Q_1+Q_1*\frac{\sqrt{1+\frac{4k\Delta B i}{Q_1}}-1}{2k}\]</p>
|
|||
|
|
|
|||
|
|
<h3 id="toc_7">跨工作场景时的价格</h3>
|
|||
|
|
|
|||
|
|
<p>由于PMM给出的价格曲线是分段的,如果一笔交易跨越了工作场景(比如在BaseToken匮乏时用户卖出大量BaseToken,直接进入QuoteToken匮乏场景),就需要分段计算价格,并累计。</p>
|
|||
|
|
|
|||
|
|
<p>这一累计要求精度很高,但计算机有精度损失,所以处理起来要非常小心。合约里提供了三种工作场景下的买卖函数,总计6个。其组合方式是关键。</p>
|
|||
|
|
|
|||
|
|
<h2 id="toc_8">流动性提供商</h2>
|
|||
|
|
|
|||
|
|
<p>流动性提供商又称为lp(liquidity provider)。lp有两种操作,充值&提现。</p>
|
|||
|
|
|
|||
|
|
<p>在BaseToken匮乏场景下充提BaseToken,或在QuoteToken匮乏场景下充提QuoteToken,都会使得改变价格曲线。这要求我们合理地处理充提操作,以使资金池保持健康和公平。</p>
|
|||
|
|
|
|||
|
|
<h3 id="toc_9">充值</h3>
|
|||
|
|
|
|||
|
|
<p>我们以BaseToken匮乏场景为例,研究充值如何影响lp收益。</p>
|
|||
|
|
|
|||
|
|
<p>根据上文推导出的\(B_0\)计算公式</p>
|
|||
|
|
|
|||
|
|
<p>\[B_0=B_1+B_1*\frac{\sqrt{1+\frac{4k\Delta Q}{B_1 i}}-1}{2k}\]</p>
|
|||
|
|
|
|||
|
|
<p>充值\(b\)后,\(B_1\)增加\(b\),而\(B_0\)的增量大于\(b\).就说明充值使得所有提供BaseToken的lp都获得了收益。这一收益的来源是,充值使价格曲线变得平缓,同样数量的\(\Delta Q\)可以购买更多BaseToken。</p>
|
|||
|
|
|
|||
|
|
<p>在这种情况下,lp一旦充值就会获得收益,我们称之为“充值奖励”,因为充值使得系统更接近均衡状态了。这笔奖励的本质来源是交易者付出的滑点。</p>
|
|||
|
|
|
|||
|
|
<p>但“充值奖励”并不是无风险套利机会,请继续阅读下一节。</p>
|
|||
|
|
|
|||
|
|
<h3 id="toc_10">提现</h3>
|
|||
|
|
|
|||
|
|
<p>与上一节同理,提现\(b\)后,\(B_1\)减少\(b\),而\(B_0\)减少的量多于\(b\)。说明提现使所有BaseToken的lp都蒙受损失。这一损失的来源是,提现使价格曲线变得陡峭,同样数量的\(\Delta Q\)只能购买更少的BaseToken。</p>
|
|||
|
|
|
|||
|
|
<p>PMM算法规定,这种情况下提现需要缴纳一笔“流动性罚金”。罚金数额等于提币后造成的lp损失,这笔罚金将直接充入资金池,分给所有未撤资的lp。</p>
|
|||
|
|
|
|||
|
|
<p>回到上一节中的情况,如果lp充值后立刻提现,罚金将大于收益,所以不会有无风险套利的机会。这一设计倾向于未撤资lp,可以最大限度留存资金。</p>
|
|||
|
|
|
|||
|
|
<blockquote>
|
|||
|
|
<p>值得注意的是,不论是充值奖励,还是流动性罚金,都只有在系统偏离均衡状态较远,且充提金额很大的时候才显著。
|
|||
|
|
普通用户往往感知不到这一收益(损失)的存在。当然也欢迎羊毛党,在系统偏离均衡时充值赚取充值奖励,在系统回归平衡时提现来避免流动性罚金</p>
|
|||
|
|
</blockquote>
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
<script type="text/javascript">
|
|||
|
|
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var l=r[e];if(2==arguments.length){a=arguments[1];for(var i in a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==t)for(var i in a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var l in e)e.hasOwnProperty(l)&&(t.call(e,l,e[l],a||l),"Object"!==n.util.type(e[l])||r[n.util.objId(e[l])]?"Array"!==n.util.type(e[l])||r[n.util.objId(e[l])]||(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,l,r)):(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var r,l=a.elements||document.querySelectorAll(a.selector),i=0;r=l[i++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var l,i,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1],i=n.languages[l]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var s=t.textContent,u={element:t,language:l,grammar:i,code:s};if(!s||!i)return n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var c=new Worker(n.filename);c.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},c.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var l=n.tokenize(e,t);return a.stringify(n.util.encode(l),r)},tokenize:function(e,t){var a=n.Token,r=[e],l=t.rest;if(l){for(var i in l)t[i]=l[i];delete t.rest}e:for(var i in t)if(t.hasOwnProperty(i)&&t[i]){var o=t[i];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],c=u.inside,g=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var m=r[p];if(r.length>e.length)break e;if(!(m instanceof a)){u.lastIndex=0;var y=u.exec(m),v=1;if(!y&&h&&p!=r.length-1){var b=r[p+1].matchedStr||r[p+1],k=m+b;if(p<r.length-2&&(k+=r[p+2].matchedStr||r[p+2]),u.lastIndex=0,y=u.exec(k),!y)continue;var w=y.index+(g?y[1].length:0);if(w>=m.length)continue;var _=y.index+y[0].length,P=m.length+b.length;if(v=3,P>=_){if(r[p+1].greedy)continue;v=2,k=k.slice(0,P)}m=k}if(y){g&&(f=y[1].length);var w=y.index+f,y=y[0].slice(f),_=w+y.length,S=m.slice(0,w),O=m.slice(_),j=[p,v];S&&j.push(S);var A=new a(i,c?n.tokenize(y,c):y,d,y,h);j.push(A),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script type="text/javascript">
|
|||
|
|
!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var n,a=e.code.match(/\n(?!$)/g),l=a?a.length+1:1,m=new Array(l+1);m=m.join("<span></span>"),n=document.createElement("span"),n.className="line-numbers-rows",n.innerHTML=m,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(n)}}})}();
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script type="text/x-mathjax-config">
|
|||
|
|
(function () {
|
|||
|
|
|
|||
|
|
MathJax.Hub.Config({
|
|||
|
|
'showProcessingMessages': false,
|
|||
|
|
'messageStyle': 'none'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (typeof MathJaxListener !== 'undefined') {
|
|||
|
|
MathJax.Hub.Register.StartupHook('End', function () {
|
|||
|
|
MathJaxListener.invokeCallbackForKey_('End');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
|||
|
|
|
|||
|
|
|
|||
|
|
</body>
|
|||
|
|
|
|||
|
|
</html>
|